3 poin oleh GN⁺ 2024-04-27 | 1 komentar | Bagikan ke WhatsApp
  • Increase memandang resource API sebagai faktor yang membentuk pemahaman pengguna terhadap produk, dan mengadopsi prinsip No Abstractions dengan menampilkan, bukan menyembunyikan, kompleksitas jaringan pembayaran
  • Abstraksi ala Stripe kuat untuk integrasi cepat, tetapi pengguna Increase menginginkan koneksi langsung dan integrasi mendalam berdasarkan pengetahuan tentang payment network
  • API menggunakan istilah jaringan dasar seperti dalam Nacha specification apa adanya, dan memodelkan proses ACH transfer sebagai sub-objek immutable
  • Jika aksi yang dapat dilakukan pengguna sangat berbeda, resource dipisahkan seperti ach_transfer dan inbound_ach_transfer, sehingga meski awalnya terasa bertele-tele, dalam jangka panjang prediktabilitas meningkat
  • Tingkat abstraksi harus ditentukan sesuai pengalaman domain dan kemauan investasi dari pengembang integrasi, dan jika memilih abstraksi rendah, prinsip itu harus dijaga secara konsisten setelahnya

Resource API membentuk model mental pengguna

  • Resource API adalah kata benda dalam API, dan menentukan nama serta modelnya termasuk bagian paling sulit dan penting dalam desain API
  • Resource apa yang diekspos membentuk model mental pengguna tentang cara kerja produk dan tindakan apa yang dimungkinkan
  • Untuk membantu pengambilan keputusan ini, Increase menggunakan prinsip desain “No Abstractions”
  • Perbedaan antara abstraksi ala Stripe dan pendekatan Increase

    • Stripe unggul dalam abstraksi yang mengekstrak domain pembayaran yang kompleks menjadi API yang mudah digunakan
    • Berbagai jaringan pembayaran dimodelkan sebagai resource API PaymentIntent, dan perbedaan chargeback reason code antara Visa dan Mastercard digabungkan ke dalam satu enum sehingga pengguna tidak perlu mempertimbangkan kedua jaringan secara terpisah
    • Banyak pengguna Stripe adalah startup tahap awal yang membangun produk selain pembayaran itu sendiri, sehingga mereka ingin cepat terintegrasi lalu kembali mengembangkan produk utama, alih-alih memahami detail kartu kredit secara mendalam
    • Pengguna Increase sudah memiliki pengetahuan yang mendalam tentang payment network, terus bekerja dengan teknologi finansial, dan menggunakan Increase untuk koneksi jaringan langsung serta integrasi yang lebih dalam
    • Mereka ingin tahu secara tepat kapan FedACH window ditutup dan kapan transfer tiba, serta memahami bahwa jika Standard Entry Class code dari ACH transfer berubah, return timing juga bisa berubah
    • Jika ACH transfer dan wire transfer digabung menjadi satu resource API untuk menyembunyikan kompleksitas jaringan dasar, bagi pengguna Increase itu bukan penyederhanaan melainkan ketidaknyamanan

Cara No Abstractions muncul di API

  • Menggunakan istilah jaringan yang sebenarnya

    • Increase cenderung menggunakan kosakata dari jaringan dasar alih-alih menciptakan nama baru untuk resource API dan attribute
    • Saat membuat API untuk ACH transfer, parameter yang diekspos mengikuti nama field dari Nacha specification
  • Resource immutable dan lifecycle object

    • Resource juga dimodelkan sesuai event atau pesan di dunia nyata, dan pendekatan ini membuat lebih banyak resource API menjadi immutable
    • Seperti kumpulan pesan jaringan yang dapat dikirim dalam lifecycle ACH transfer, resource immutable dikelompokkan di bawah lifecycle object berbentuk state machine
    • Objek ach_transfer memiliki field status yang berubah seiring waktu, serta beberapa sub-objek immutable yang dibuat sesuai perkembangan lifecycle
    • ach_transfer baru dapat memiliki status pending_approval, dan approval, submission, serta acknowledgement bisa bernilai null
    • Setelah dikirim ke FedACH, status menjadi submitted, dan approval, submission, serta acknowledgement masing-masing terisi dengan informasi immutable pada saat persetujuan, pengiriman, dan konfirmasi
    • submission mencakup nilai seperti trace_number dan submitted_at
  • Memisahkan resource berdasarkan use case

    • Bahkan untuk resource API yang sama, jika himpunan aksi yang dimungkinkan berbeda besar antar instance, Increase cenderung membaginya menjadi beberapa resource
    • Karena aksi yang dimungkinkan pada originated ACH transfer dan received ACH transfer pada dasarnya berlawanan, keduanya dipisahkan menjadi ach_transfer dan inbound_ach_transfer
    • Pendekatan ini pada awalnya bisa terlihat lebih bertele-tele dan mengintimidasi, sampai-sampai banyak resource terlihat di sisi kiri dokumentasi API
    • Namun dalam jangka panjang, hubungan antara resource dan aksi menjadi lebih dapat diprediksi

Prinsip mengurangi keputusan desain kecil

  • Saat merancang API kompleks selama bertahun-tahun, keputusan kecil terus bermunculan, dan prinsip dasar yang ditetapkan sejak awal mengurangi beban kognitif dari keputusan-keputusan itu
  • Input Message Accountability Data yang diperlukan saat mengirim wire transfer ke Federal Reserve berfungsi sebagai ID unik global untuk transfer tersebut
  • Dalam API dengan banyak abstraksi, engineer mungkin akan memikirkan apakah harus menamainya lebih “ramah pengguna” sebagai trace_number, reference_number, atau id
  • Di Increase, nama field itu langsung ditetapkan sebagai input_message_accountability_data
  • Saat pertama kali melihat field ini, pengguna mungkin tidak langsung merasa namanya mudah dikenali, tetapi nama tersebut membantu mereka segera memahami bagaimana field itu dipetakan ke sistem dasarnya

Kriteria saat menentukan tingkat abstraksi

  • No Abstractions bukan prinsip yang cocok untuk semua API
  • Tingkat abstraksi yang tepat bergantung pada pengalaman domain pengembang integrasi, pemahaman mereka terhadap area produk, dan energi yang ingin mereka curahkan untuk integrasi
  • Jika membuat API dengan abstraksi tinggi, fitur baru harus dipikirkan secara mendalam sebelum ditambahkan
  • Jika membuat API dengan abstraksi rendah, Anda harus berkomitmen pada arah itu dan menahan godaan untuk menambahkan abstraksi

1 komentar

 
GN⁺ 2024-04-27
Opini Hacker News
  • Selalu bisa juga menyediakan keduanya
    Sediakan API tingkat rendah yang memungkinkan kontrol terperinci tetapi membutuhkan keahlian mendalam, lalu di atasnya buat API tingkat tinggi yang memetakan kasus penggunaan umum menjadi beberapa operasi sederhana. Bagaimanapun, sebagian pelanggan mungkin sudah menerapkan lapisan tingkat tinggi seperti ini sendiri dengan kurang rapi
    Jika kedua lapisan dipisahkan dengan bersih, tekanan untuk memasukkan abstraksi ke API tingkat rendah, atau menambahkan cacat dan kasus khusus ke API tingkat tinggi, akan berkurang. Sebab jika pelanggan menginginkannya, hal itu sudah ada di API lain
    Akan lebih baik lagi jika disediakan materi yang membantu pelanggan belajar berpindah dari satu lapisan ke lapisan lain. Ini juga bisa menarik pelanggan yang belum benar-benar memahami struktur internal jaringan pembayaran, tetapi ingin berkembang ke arah itu

    • Harus ada API tingkat rendah yang bisa menangani kasus langka yang kompleks, dan API tingkat tinggi sederhana untuk kasus umum yang dibangun di atasnya
      Hari ini saya memakai Web File System API, dan untuk menulis satu string ke sebuah file dibutuhkan 7 pemanggilan fungsi, kebanyakan asinkron. Itu belum termasuk penanganan error, harus dilakukan di worker, dan pengaturan worker itu sendiri juga sama merepotkannya. Kengerian serupa bisa dilihat pada IndexedDB, WebRTC, manipulasi DOM biasa, sementara Vulkan, DirectX, dan ffmpeg jauh lebih parah
      Untuk menangani segala macam kasus khusus, kompleksitas sampai batas tertentu memang dapat dibenarkan, tetapi sebagian besar kasus bukanlah kasus khusus seperti itu
      Desain API seharusnya dimulai dengan membuat sketsa seperti apa kode yang menggunakan API untuk kasus umum, dan kasus-kasus itu harus dibuat sesederhana mungkin. Misalnya, fetch API melakukannya cukup baik, sedangkan XMLHttpRequest sama sekali tidak
      https://developer.mozilla.org/en-US/docs/Web/API/FileSystemS...
      Saya beberapa kali berpikir alangkah baiknya jika ada API lapisan kenyamanan terpadu untuk semua Web API. Semua fitur kuat dibungkus dengan wrapper “standard library” yang konsisten, setidaknya mendukung kasus penggunaan paling umum. Browser modern sangat kuat, tetapi desain tiap API berbeda-beda dan tidak perlu sulit dipelajari atau digunakan, sehingga kekuatan itu kurang dikenal atau kurang dimanfaatkan
      Mirip dengan apa yang dilakukan jQuery untuk DOM, tetapi dengan lebih sedikit keajaiban dan lebih sedikit fitur tambahan. node.js sampai batas tertentu punya API yang konsisten, tetapi agak menua, misalnya dukungan Promise tidak merata. Ini juga mirip dengan cara Python mengejar API yang “Pythonic”
    • Saya khususnya menyukai pola ini ketika API tingkat tinggi yang diinginkan bisa diimplementasikan di luar library. Dengan begitu kita bisa memastikan API tingkat rendah cukup fleksibel, dan kita sendiri juga menggunakan API sendiri secara langsung dari sudut pandang pengguna
      Ketika terbiasa dengan sudut pandang implementasi internal sebuah alat, terlalu mudah untuk lupa bagaimana orang benar-benar menggunakannya
    • Git adalah contoh seperti ini
      Ada perintah “porcelain” tingkat tinggi seperti branch dan checkout, serta perintah “plumbing” tingkat rendah seperti commit-tree dan update-ref
      https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
    • .NET juga banyak memakai pendekatan seperti ini. Ada tulisan blog pengembang terbaru tentang I/O file: https://devblogs.microsoft.com/dotnet/the-convenience-of-sys...
    • Sebagai gantinya, permukaan API menjadi dua kali lebih besar, jadi ini adalah trade-off yang perlu dipertimbangkan. Dalam banyak kasus, ini bisa menjadi keputusan yang tepat
  • Saya suka bagian yang menjelaskan mengapa Increase memilih pendekatan yang berbeda. Saat merancang hal-hal mendasar, konteks sangat penting, tetapi biasanya orang tidak cukup mengakuinya

  • Di sini, “tanpa abstraksi” pada dasarnya berarti gunakan istilah dari sistem yang mendasarinya apa adanya, dan secara umum itu adalah prinsip penamaan yang baik.
    Masalahnya pasti muncul seiring waktu ketika sistem yang mendasari menjadi lebih dari satu, lalu hal yang sama diberi nama berbeda, atau lebih buruk lagi, nama yang sama mulai dipakai untuk hal yang berbeda. Dalam contoh ini, apa yang harus dilakukan jika model para penyedia pembayaran dasar berbeda? Lalu bagaimana jika Federal Reserve menghentikan Input Message Accountability Data dan menggantinya dengan konsep baru?
    Industri pembayaran mungkin jauh lebih sederhana daripada transportasi atau protokol jaringan. Jika Anda membuat produk packet switching berbasis X.25 lalu kemudian ingin mendukung TCP/IP juga, abstraksi yang benar itu apa?

    • Terima kasih sudah membaca dengan cermat.
      Untuk masalah penghentian, untungnya sistem yang mendasari tidak banyak berubah, jadi tidak apa-apa. Input Message Accountability Data tidak akan hilang. Namun, misalnya jika kami mulai menerbitkan kartu tidak hanya lewat Visa tetapi juga Mastercard, kami akan menghadapi benturan.
      Kami juga pernah bereksperimen dengan beberapa abstraksi, dan di titik itu pun bisa saja demikian. Satu aturan yang terus kami pegang adalah tidak mengabstraksikan “objek dasar”, melainkan memperkenalkan komposisi tingkat lebih tinggi demi kenyamanan. Misalnya, “Card Payment” sebenarnya tidak ada(https://increase.com/documentation/api#card-payments). Itu hanya cara mengelompokkan otorisasi kartu dan pesan settlement yang terkait. Namun karena sangat berguna bagi pengguna dan rekonsiliasi secara langsung tidak mudah, kami mencobanya. Meski begitu, menurut kami pesan jaringan yang mendasari, yaitu “objek dasar”, beserta semua field aslinya juga harus bisa diakses di API.
      Sayangnya, API publik yang pernah saya kerjakan 100% berada di bidang pembayaran, jadi saya berharap punya sudut pandang lain.
    • Tulisan itu juga jelas mengatakan “tidak menyatukan objek yang mirip”, dan itulah yang memungkinkan keputusan penamaan tersebut.
    • “Menggunakan istilah dari sistem yang mendasarinya apa adanya” terdengar agak mirip dengan domain-driven design. Hanya saja, “sistem yang mendasari” dalam kasus ini mungkin sedikit lebih condong ke implementasi daripada benar-benar domain bisnis.
      Dalam DDD, biasanya kita mengikuti nama dan model konsep yang sudah dibuat oleh domain bisnis. Jika mencoba memperkenalkan model atau istilah [0] “yang lebih baik” versi sendiri, akan timbul friksi dan salah paham, kemungkinan bug integrasi meningkat, dan kita mengabaikan keahlian yang sudah teruji selama puluhan atau ratusan tahun.
      [0] https://xkcd.com/793/
  • Tulisannya bagus.
    Jika Anda menyukai Stripe, saya juga sebagai desainer sekaligus pendiri teknis merasa kesederhanaan dan kemampuan frontend Stripe luar biasa, dan melihat mereka bisa membuat kita ingin meniru kemampuan mereka menyederhanakan serta memberikan pengalaman yang sangat matang.
    Namun keahlian Stripe yang sebenarnya ada pada memahami pelanggan dengan baik. Dan mereka juga sangat memahami kesederhanaan yang didambakan pelanggan.
    Dari tulisan ini, Increase tampaknya juga demikian, dan dengan fokus yang sama tajamnya pada apa yang dibutuhkan pelanggan, mereka sepertinya menghasilkan pedoman desain produk yang hebat. Ini menggembirakan.

    • Cara Stripe membangun API dan tim: https://www.youtube.com/watch?v=IEe-5VOv0Js
    • Di Stripe API juga ada titik-titik yang menunjukkan ketegangan antara “mari jadikan ini berpotensi universal” dan “mari terima bahwa ini mungkin hanya berlaku untuk satu metode pembayaran di satu pasar”.
      Secara pribadi saya lebih suka ketika yang kedua terjadi, tetapi di sana juga ada keputusan estetika.
  • Ini mirip dengan pola desain ubiquitous language dalam domain-driven design. Caranya adalah membuat implementasi memakai istilah dunia nyata yang digunakan para pakar domain apa adanya.
    https://thedomaindrivendesign.io/developing-the-ubiquitous-l...

    • Jauh sebelum DDD muncul, saya sudah mendengar konsep serupa. Intinya, jika kata benda dan kata kerja dalam kode tidak cocok dengan domain masalah, itu adalah impedance mismatch dan suatu saat akan menimbulkan masalah.
      Tulisan ini bagi saya terbaca seperti semacam reaksi menghindari rasa malu. Orang-orang secara patologis tidak suka mengatakan “saya salah” atau “kita salah”, sehingga mereka terus mendorong metafora ke sana kemari seperti anak kecil yang memindah-mindahkan sayur di piring agar terlihat sudah memakannya.
      Saya juga teringat frasa “tidak ada cacat yang jelas” dari pidato Turing Award Hoare.
  • Ini adalah contoh yang bagus dari konsep ubiquitous language dalam domain-driven design.
    Kita harus memakai bahasa yang dipahami pakar domain. Jika pengguna mengenal file NACHA, begitu Anda memakai istilah lain, mereka harus mempertahankan pemetaan di kepala.
    Sebaliknya, dalam kasus Stripe, penggunanya bukan pakar domain, sehingga bernilai untuk membuat abstraksi yang dapat dipahami sekaligus menyembunyikan detail yang tidak perlu. Jika Anda harus mengajarkan bahasa kepada pengguna, buatlah sesederhana mungkin.

    • Dengan kata lain, mereka adalah pakar domain untuk jenis transaksi yang ingin mereka lakukan, bukan pakar tentang bagaimana transaksi diimplementasikan dalam sistem keuangan.
  • Tanpa abstraksi seperti POSIX, aplikasi harus menulis adaptor untuk setiap sistem file yang didukungnya.

  • Menarik.
    Judul konsep ini menyesatkan. “Tanpa abstraksi” di sini bukan berarti secara harfiah tidak ada abstraksi, melainkan “gunakan himpunan abstraksi tertentu ini, dan jangan gunakan abstraksi lain”. Subhimpunan spesifik yang mereka jelaskan layak dibahas, tetapi jelas itu tetap merupakan himpunan abstraksi.
    Misalnya, mereka mengatakan “saat membuat transfer ACH sebagai API, nama parameter yang diekspos diberi nama mengikuti nama field dalam spesifikasi Nacha”, tetapi spesifikasi itu sendiri adalah abstraksi.
    Mereka juga mengatakan, “seperti memakai istilah jaringan, kami berusaha memodelkan resource agar sesuai dengan peristiwa nyata, misalnya tindakan yang dilakukan atau pesan yang dikirim. Hasilnya, lebih banyak resource API menjadi immutable dan dikelompokkan di bawah ‘objek siklus hidup’ state machine.” Immutable dalam pengertian ini dan “objek siklus hidup” juga abstraksi.
    “Jika pada resource API tertentu kumpulan tindakan yang dapat dilakukan pengguna untuk tiap instance sangat berbeda, kami cenderung memecahnya menjadi beberapa resource” juga merupakan abstraksi lain. Hanya saja pemisahannya berada pada tingkat yang berbeda dari Stripe API.
    Pada akhirnya ini adalah kumpulan keputusan desain dan abstraksi, bukan prinsip “tanpa abstraksi”. Keputusan terpenting tampaknya adalah melakukan generalisasi sesedikit mungkin, dan generalisasi juga salah satu jenis abstraksi. Mungkin “generalisasi yang lebih sedikit” akan menjadi judul yang lebih akurat.

  • Saya melihat bagian “biaya bulanan per pengguna yang dibangun di atas Increase berbeda-beda tergantung kasus penggunaan”
    Saat ini saya sedang menambahkan akses API publik ke endpoint AI text-to-SQL dengan dukungan RAG, dan masalah terbesarnya adalah penetapan harga. Ada yang tahu kira-kira kisaran harganya seperti apa? Harga harus mencerminkan token OpenAI, atau opsi agar pengguna memasukkan token OpenAI mereka sendiri, penggunaan database, serta ke depannya caching dan pengaturan rate limit

    • Pada dasarnya, harga harus ditetapkan berdasarkan nilai, bukan biaya[1], jadi pikirkan nilai apa yang didapat pelanggan dan mulailah dari sana
      Misalnya, setahu saya Gong menagih banyak organisasi lebih dari 100 ribu dolar per tahun, dan meski memperhitungkan penyimpanan, CPU, serta biaya operasional lainnya, biayanya tidak mungkin mendekati biaya komputasi. Kemungkinan selisihnya setidaknya beberapa kali lipat. Namun karena tim penjualan menghasilkan pendapatan dengan sangat langsung, leverage yang bisa dibeli dalam bentuk alat seperti Gong memiliki nilai yang langsung terasa dan jelas
      [1]: Pengecualian terhadap prinsip bahwa Anda harus menghindari penetapan harga cost-plus adalah ketika menjual komoditas. Namun Anda tidak berada dalam situasi seperti itu!