No Abstractions: Prinsip desain API Increase
(increase.com)- 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_transferdaninbound_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_transfermemiliki fieldstatusyang berubah seiring waktu, serta beberapa sub-objek immutable yang dibuat sesuai perkembangan lifecycle ach_transferbaru dapat memilikistatuspending_approval, danapproval,submission, sertaacknowledgementbisa bernilainull- Setelah dikirim ke FedACH,
statusmenjadisubmitted, danapproval,submission, sertaacknowledgementmasing-masing terisi dengan informasi immutable pada saat persetujuan, pengiriman, dan konfirmasi submissionmencakup nilai sepertitrace_numberdansubmitted_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_transferdaninbound_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 Datayang 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, atauid - 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
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
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”
Ketika terbiasa dengan sudut pandang implementasi internal sebuah alat, terlalu mudah untuk lupa bagaimana orang benar-benar menggunakannya
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...
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?
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.
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.
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...
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.
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
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!