3 पॉइंट द्वारा GN⁺ 2024-04-27 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Increase मानता है कि API resources यह तय करते हैं कि उपयोगकर्ता product को कैसे समझते हैं, इसलिए payment networks की जटिलता को छिपाने के बजाय उसे सामने रखने वाला No Abstractions सिद्धांत अपनाता है
  • Stripe-शैली की abstraction तेज़ integration में मज़बूत है, लेकिन Increase के users payment network knowledge के आधार पर direct connectivity और deep integration चाहते हैं
  • API, Nacha specification जैसे underlying network terms को जस का तस इस्तेमाल करता है, और ACH transfer की progression को immutable sub-objects के रूप में model करता है
  • अगर user द्वारा किए जा सकने वाले actions काफ़ी अलग हों, तो ach_transfer और inbound_ach_transfer की तरह resources को अलग किया जाता है; शुरुआत में यह verbose लगे, फिर भी लंबे समय में predictability बढ़ती है
  • abstraction level को integration developer के domain experience और commitment के हिसाब से तय करना चाहिए, और यदि low abstraction चुना है तो बाद में भी उसी सिद्धांत को बनाए रखना चाहिए

API resources users का mental model बनाते हैं

  • API resource, API के nouns होते हैं, और उनका नाम व model तय करना API design के सबसे कठिन और महत्वपूर्ण हिस्सों में आता है
  • कौन-से resources expose किए जाते हैं, यही users के लिए product कैसे काम करता है और वे क्या कर सकते हैं, इसका mental model बनाता है
  • Increase इस निर्णय में मदद के लिए “No Abstractions” नामक design principle इस्तेमाल करता है
  • Stripe-शैली की abstraction और Increase का अंतर

    • Stripe की ताकत जटिल payments domain को ऐसे API में बदलने वाली abstraction में है, जिसे users आसानी से इस्तेमाल कर सकें
    • वह कई payment networks को PaymentIntent नामक API resource के रूप में model करता है, और Visa व Mastercard के chargeback reason code के अंतर को एक enum में मिलाकर users को दोनों networks के बारे में अलग-अलग सोचने से बचाता है
    • Stripe users में बड़ी संख्या शुरुआती startups की होती है जो payments नहीं, बल्कि अपना product बना रहे होते हैं; वे credit card details को गहराई से जानने के बजाय जल्दी integration करके अपने मूल product development पर लौटना चाहते हैं
    • Increase users के पास payment networks के बारे में पहले से गहरा ज्ञान होता है, वे financial technology पर लगातार काम करते हैं, और direct network connectivity व deep integration के लिए Increase का इस्तेमाल करते हैं
    • वे ठीक-ठीक जानना चाहते हैं कि FedACH window कब बंद होती है और transfer कब पहुंचेगा, और समझते हैं कि ACH transfer का Standard Entry Class code बदलने पर return timing भी बदल सकती है
    • ACH transfer और wire transfer को एक ही API resource में बांधकर underlying network complexity छिपाना, Increase users के लिए simplification नहीं बल्कि असुविधा बन जाता है

No Abstractions API में कैसे दिखाई देता है

  • वास्तविक network terms का उपयोग

    • Increase, API resource और attribute names नए गढ़ने के बजाय underlying network की vocabulary इस्तेमाल करना पसंद करता है
    • ACH transfer के लिए API बनाते समय expose किए जाने वाले parameters Nacha specification के field names का अनुसरण करते हैं
  • Immutable resources और lifecycle object

    • Resources को भी real-world events या messages के अनुरूप model किया जाता है, और यह approach अधिक API resources को immutable बनाती है
    • ACH transfer lifecycle में भेजे जा सकने वाले network messages के समूहों की तरह, immutable resources को state machine के रूप वाले lifecycle object के नीचे group किया जाता है
    • ach_transfer object में समय के साथ बदलने वाला status field होता है, और lifecycle progression के साथ बनने वाले कई immutable sub-objects होते हैं
    • नया ach_transfer का status pending_approval हो सकता है और approval, submission, acknowledgement null हो सकते हैं
    • FedACH में submit होने के बाद status submitted हो जाता है, और approval, submission, acknowledgement क्रमशः approval, submission और acknowledgement के समय की immutable जानकारी से भर जाते हैं
    • submission में trace_number और submitted_at जैसी values शामिल होती हैं
  • Use case के हिसाब से resources अलग करना

    • एक ही API resource होने पर भी अगर अलग-अलग instances के possible actions का set बहुत अलग हो, तो Increase उन्हें कई resources में बांटना पसंद करता है
    • originated ACH transfer और received ACH transfer में possible actions वास्तव में लगभग उल्टे होते हैं, इसलिए इन्हें ach_transfer और inbound_ach_transfer में अलग किया जाता है
    • इस approach में API documentation के बाईं ओर बहुत सारे resources दिख सकते हैं, इसलिए शुरुआत में यह ज़्यादा verbose और intimidating लग सकता है
    • लेकिन बदले में लंबे समय में resources और actions का संबंध अधिक predictable हो जाता है

सिद्धांत छोटे design decisions को कम करते हैं

  • जब complex API को कई वर्षों में design किया जाता है, तो छोटे decisions लगातार आते रहते हैं; शुरुआत में बनाए गए foundational principles ऐसे decisions की cognitive load घटाते हैं
  • wire transfer को Federal Reserve को भेजते समय आवश्यक Input Message Accountability Data उस transfer की globally unique ID की तरह काम करता है
  • अधिक abstraction वाले API में engineer सोच सकता है कि इसे अधिक “user-friendly” बनाने के लिए trace_number, reference_number, या id में से क्या कहा जाए
  • Increase में field name input_message_accountability_data तय कर दिया जाता है और बात आगे बढ़ती है
  • user जब इस field को पहली बार देखे, तो यह तुरंत पहचानने में आसान नाम न हो सकता है, लेकिन इससे यह समझने में मदद मिलती है कि यह underlying system से कैसे map होता है

abstraction level तय करते समय criteria

  • No Abstractions हर API के लिए सही सिद्धांत नहीं है
  • सही abstraction level integration developer के domain experience, product area की समझ, और integration में लगाने वाली energy पर निर्भर करता है
  • ज़्यादा abstraction वाला API बनाने पर नई feature जोड़ने से पहले गहराई से सोचना पड़ता है
  • कम abstraction वाला API बनाने पर उस दिशा के लिए commit करना होता है और abstraction जोड़ने के प्रलोभन से बचना होता है

1 टिप्पणियां

 
GN⁺ 2024-04-27
Hacker News पर टिप्पणियां
  • हमेशा दोनों उपलब्ध कराए जा सकते हैं
    एक low-level API दें जो बारीक control देता हो लेकिन गहरी expertise मांगता हो, और उसके ऊपर एक high-level API बनाएं जो common use cases को कुछ सरल operations में map कर दे। वैसे भी कुछ customers शायद ऐसे high-level layer को खुद ही अधकचरे तरीके से implement कर रहे होंगे
    दोनों layers को साफ़-साफ़ अलग रखने से low-level API में abstraction डालने, या high-level API में खरोंचों और special cases जोड़ने का दबाव कम होता है। क्योंकि अगर customers को वह चाहिए, तो वह पहले से दूसरे API में मौजूद है
    अगर आप ऐसा material भी दें जिससे customers एक layer से दूसरी layer में जाना सीख सकें, तो और बेहतर होगा। इससे वे customers भी आकर्षित हो सकते हैं जो payment networks की internal structure को अभी गहराई से नहीं जानते, लेकिन उस दिशा में grow करना चाहते हैं

    • दुर्लभ complex cases संभालने के लिए low-level API और उसके ऊपर बने common cases के लिए एक सरल high-level API होना चाहिए
      आज Web File System API इस्तेमाल कर रहा था; एक string को file में लिखने के लिए 7 function calls चाहिए थीं और उनमें से ज़्यादातर asynchronous थीं। इसमें error handling भी शामिल नहीं है, यह worker में करना पड़ता है, और worker setup खुद भी लगभग उतना ही झंझटभरा है। IndexedDB, WebRTC, साधारण DOM manipulation में भी ऐसी ही भयावहता दिखती है, और Vulkan, DirectX, ffmpeg तो इससे भी कहीं ज़्यादा खराब हैं
      हर तरह के special cases संभालने के लिए complexity कुछ हद तक जायज़ है, लेकिन ज़्यादातर cases ऐसे special cases नहीं होते
      API design की शुरुआत पहले यह sketch करने से होनी चाहिए कि common cases में API इस्तेमाल करने वाला code कैसा दिखेगा, और वे cases जितने संभव हों उतने simple होने चाहिए। उदाहरण के लिए fetch API ने यह काफी अच्छी तरह किया, XMLHttpRequest ने बिल्कुल नहीं
      https://developer.mozilla.org/en-US/docs/Web/API/FileSystemS...
      मैंने कई बार सोचा है कि सभी Web APIs के लिए एक unified convenience layer API हो तो अच्छा होगा। सभी powerful features को एक consistent “standard library” wrapper में wrap किया जाए, और कम से कम सबसे common use cases को support किया जाए। Modern browsers बहुत powerful हैं, लेकिन हर API का design अलग-अलग है और उन्हें सीखना या इस्तेमाल करना अनावश्यक रूप से मुश्किल है, इसलिए उनकी power ठीक से जानी नहीं जाती या कम इस्तेमाल होती है
      यह कुछ वैसा हो जैसा jQuery ने DOM के लिए किया था, लेकिन कम magic और कम extra features के साथ। node.js में कुछ हद तक consistent API है, लेकिन वह थोड़ा पुराना है; उदाहरण के लिए Promise support असमान है। यह Python के “Pythonic” API अपनाने के तरीके से भी मिलता-जुलता है
    • यह pattern मुझे खास तौर पर तब पसंद है जब इच्छित high-level API को library के बाहर implement किया जा सकता हो। इससे यह जांचा जा सकता है कि low-level API पर्याप्त flexible है या नहीं, और आप खुद भी user के नजरिए से अपनी ही API का इस्तेमाल करते हैं
      tools की internal implementation वाली सोच की आदत पड़ जाए, तो यह भूलना बहुत आसान है कि लोग वास्तव में उनका इस्तेमाल कैसे करते हैं
    • Git इसका उदाहरण है
      branch और checkout जैसे high-level “porcelain” commands हैं, और commit-tree और update-ref जैसे low-level “plumbing” commands हैं
      https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
    • .NET भी इस तरीके का काफी इस्तेमाल करता है। हाल में file I/O पर एक developer blog post है: https://devblogs.microsoft.com/dotnet/the-convenience-of-sys...
    • लेकिन बदले में API surface area दोगुना हो जाता है, इसलिए यह एक trade-off है जिस पर विचार करना होगा। कई मामलों में यह सही फैसला हो सकता है
  • Increase ने अलग approach क्यों चुनी, यह समझाने वाला हिस्सा अच्छा है। बुनियादी चीज़ें design करते समय context बहुत अहम होता है, लेकिन आम तौर पर लोग इसे पर्याप्त महत्व नहीं देते

  • यहाँ “कोई abstraction नहीं” का मतलब असल में बुनियादी सिस्टम की terminology को ज्यों का त्यों इस्तेमाल करो है, और यह आम तौर पर अच्छे naming का सिद्धांत है
    समस्या समय के साथ अनिवार्य रूप से तब पैदा होती है जब बुनियादी सिस्टम कई हो जाते हैं, और एक ही चीज़ को अलग-अलग नाम दिए जाने लगते हैं, या उससे भी बुरा, एक ही नाम अलग-अलग चीज़ों के लिए इस्तेमाल होने लगता है। इस उदाहरण में अगर underlying payment providers के models अलग हों तो क्या करें? और अगर Federal Reserve, Input Message Accountability Data को हटाकर किसी नए concept से बदल दे तो क्या होगा?
    Payment industry शायद transportation या network protocols से कहीं सरल हो सकती है। अगर आपने X.25-based packet switching product बनाया और बाद में TCP/IP भी support करना चाहें, तो सही abstraction क्या होगी?

    • ध्यान से पढ़ने के लिए धन्यवाद
      Deprecation वाली समस्या में सौभाग्य से कोई दिक्कत नहीं, क्योंकि underlying system बहुत ज्यादा नहीं बदलता। Input Message Accountability Data गायब नहीं होगा। लेकिन, उदाहरण के लिए, अगर हम Visa के साथ-साथ Mastercard पर भी cards issue करना शुरू करें, तो हमें conflicts झेलने पड़ेंगे
      हमने कुछ abstractions के साथ प्रयोग भी किए हैं, और उस जगह पर भी ऐसा हो सकता है। एक नियम जिसे हमने लगातार निभाया है, वह यह है कि “underlying objects” को abstract नहीं करते, बल्कि सुविधा के लिए higher-level combinations लाते हैं। उदाहरण के लिए “Card Payment” नाम की कोई चीज़ वास्तव में मौजूद नहीं है (https://increase.com/documentation/api#card-payments). यह बस related card authorization और settlement messages को group करने का तरीका है। लेकिन यह users के लिए बहुत उपयोगी है और reconciliation खुद करना आसान नहीं है, इसलिए हमने इसे आज़माया। हालांकि मेरा मानना है कि underlying network messages, यानी “underlying objects” और सभी original fields भी API में accessible होने चाहिए
      अफसोस है कि जिन public APIs पर मैंने काम किया है वे 100% payments domain में हैं, काश कोई दूसरा perspective होता
    • लेख में यह भी साफ कहा गया है कि “similar objects को merge नहीं करना”, और वही naming decisions को संभव बनाता है
    • “underlying system की terminology को ज्यों का त्यों इस्तेमाल करना” थोड़ा Domain-Driven Design जैसा सुनाई देता है। हालांकि इस मामले में “underlying system” वास्तविक business domain के बजाय implementation-केंद्रित थोड़ा ज्यादा हो सकता है
      DDD में आम तौर पर business domain द्वारा पहले से बनाए गए names और conceptual model का पालन किया जाता है। अगर आप अपना “improved” [0] model या terminology लाने की कोशिश करते हैं, तो friction और गलतफहमियां पैदा होती हैं, integration bugs की संभावना बढ़ती है, और दशकों या सदियों से परखी गई विशेषज्ञता को नज़रअंदाज़ किया जाता है
      [0] https://xkcd.com/793/
  • लेख अच्छा है
    अगर आपको Stripe पसंद है, तो एक designer और technical founder के तौर पर मुझे भी Stripe की simplicity और front-end capability कमाल की लगती है, और उन्हें देखकर आप simplify करने और highly polished experience देने की उनकी क्षमता की नकल करने की कोशिश कर सकते हैं
    लेकिन Stripe की असली expertise इस बात में है कि वे अपने customers को अच्छी तरह जानते हैं। और वे यह भी अच्छी तरह जानते हैं कि customers किस simplicity की चाह रखते हैं
    इस लेख से Increase भी वैसा ही लगता है, और ऐसा लगता है कि customers को क्या चाहिए इस पर उसी तरह तेज़ focus करके उन्होंने बेहतरीन product design guidelines बनाई हैं। प्रेरक है

    • Stripe APIs और teams कैसे बनाता है: https://www.youtube.com/watch?v=IEe-5VOv0Js
    • Stripe API में भी कुछ जगहों पर “इसे potentially universal बनाएं” और “मान लें कि यह शायद किसी एक market के एक payment method पर ही लागू होगा” के बीच tension दिखता है
      व्यक्तिगत रूप से, जब दूसरा वाला होता है तो मुझे ज्यादा अच्छा लगता है, लेकिन उसमें aesthetic decision भी शामिल होता है
  • यह Domain-Driven Design के Ubiquitous Language design pattern जैसा है। इसमें implementation में domain experts द्वारा इस्तेमाल की जाने वाली real-world terminology को ज्यों का त्यों इस्तेमाल करवाया जाता है
    https://thedomaindrivendesign.io/developing-the-ubiquitous-l...

    • DDD आने से बहुत पहले भी मैंने ऐसा ही concept सुना था। बात यह थी कि अगर code के nouns और verbs problem domain से match नहीं करते, तो यह impedance mismatch है और कभी न कभी समस्या पैदा करेगा
      यह लेख मुझे एक तरह की शर्म से बचने वाली प्रतिक्रिया जैसा लगता है। लोग pathological रूप से “मैं गलत था” या “हम गलत थे” कहना नापसंद करते हैं, इसलिए वे metaphors को इधर-उधर धकेलते रहते हैं, जैसे कोई बच्चा प्लेट में रखी सब्जियां इधर-उधर कर दे ताकि लगे कि उसने खा ली हैं
      Hoare के Turing Award भाषण में कही गई “कोई स्पष्ट खामियां नहीं हैं” वाली बात भी याद आती है
  • यह Domain-Driven Design के Ubiquitous Language concept का अच्छा उदाहरण है
    आपको वही भाषा इस्तेमाल करनी चाहिए जिसे domain expert समझता है। अगर user NACHA file जानता है, तो जैसे ही आप कोई दूसरा term इस्तेमाल करते हैं, उसे अपने दिमाग में mapping बनाए रखनी पड़ती है
    इसके उलट Stripe के मामले में users domain experts नहीं हैं, इसलिए ऐसी abstraction बनाना मूल्यवान है जो समझ में आए और अनावश्यक details छिपाए। अगर आपको users को भाषा सिखानी ही है, तो उसे जितना हो सके सरल बनाना चाहिए

    • दूसरे शब्दों में, वे जिस transaction type को करना चाहते हैं उसके domain experts हैं, न कि financial systems में transaction कैसे implement होती है इसके experts
  • अगर POSIX जैसी abstraction नहीं होती, तो applications को हर supported file system के लिए adapter लिखना पड़ता

  • दिलचस्प
    इस concept का title भ्रम पैदा करता है। यहाँ “कोई abstraction नहीं” का शाब्दिक अर्थ abstraction का न होना नहीं है, बल्कि “इस specific set of abstractions का इस्तेमाल करो, और दूसरी abstractions का नहीं” है। उन्होंने जो specific subset बताया है, वह चर्चा के लायक है, लेकिन जाहिर है कि वह abstractions का set है
    उदाहरण के लिए, उन्होंने कहा कि “ACH transfer को API बनाते समय expose किए जाने वाले parameter names को Nacha specification के field names के आधार पर नाम देते हैं”, लेकिन specification खुद एक abstraction है
    उन्होंने कहा, “network terminology इस्तेमाल करने की तरह, हम resources को real events, जैसे किए गए actions या भेजे गए messages, के हिसाब से model करने की कोशिश करते हैं। नतीजतन ज्यादा API resources immutable हो जाते हैं और state machine ‘lifecycle objects’ के नीचे group हो जाते हैं”, लेकिन इस अर्थ में immutability और “lifecycle objects” भी abstractions हैं
    “किसी specific API resource में अगर users द्वारा हर instance पर लिए जा सकने वाले actions का set बहुत बदलने लगे, तो हम उसे कई resources में बांटने की ओर झुकते हैं” भी एक और abstraction है। यह बस Stripe API से अलग level पर विभाजन है
    आखिरकार यह design decisions और abstractions का set है, “कोई abstraction नहीं” principle नहीं। सबसे महत्वपूर्ण decision शायद जितना हो सके कम generalize करना है, और generalization भी abstraction का एक प्रकार है। शायद “कम generalization” ज्यादा सटीक title होता

  • “Increase के ऊपर बनाए जाने वाले प्रति-यूज़र मासिक शुल्क use case के हिसाब से अलग होते हैं” वाला हिस्सा देखा
    अभी मैं RAG-सपोर्टेड AI text-to-SQL endpoint में public API access जोड़ रहा हूँ, और सबसे बड़ी समस्या pricing है। क्या किसी को अंदाज़ा है कि लगभग किस price range की बात हो रही है? Pricing में OpenAI tokens, या यूज़र को अपना OpenAI token डालने देने का तरीका, database usage, और आगे चलकर caching व rate limit settings तक शामिल करनी होंगी

    • मूल रूप से pricing लागत नहीं, बल्कि value के आधार पर तय होनी चाहिए[1], इसलिए ग्राहक के लिए क्या value है यह सोचकर वहीं से शुरू करना चाहिए
      उदाहरण के लिए, मेरी जानकारी में Gong कई organizations से सालाना 100,000 डॉलर से ज़्यादा charge करता है; storage, CPU और बाकी operating costs को ध्यान में रखें तब भी उसकी cost compute cost के आसपास होने की संभावना नहीं है। शायद कम-से-कम कई गुना का अंतर होगा। लेकिन sales teams revenue बहुत सीधे तौर पर लाती हैं, इसलिए Gong जैसे tool के रूप में खरीदा जा सकने वाला leverage तुरंत और साफ़ तौर पर valuable है
      [1]: cost-plus pricing से बचने के सिद्धांत का अपवाद तब है जब आप commodity बेच रहे हों। लेकिन आपकी situation वैसी नहीं है!