3 คะแนน โดย GN⁺ 2024-04-27 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Increase มองว่า API resource เป็นตัวกำหนดความเข้าใจของผู้ใช้ที่มีต่อผลิตภัณฑ์ จึงเลือกใช้หลักการ No Abstractions ที่เปิดเผยความซับซ้อนของเครือข่ายการชำระเงินแทนที่จะซ่อนไว้
  • abstraction แบบ Stripe มีจุดแข็งในการผสานระบบได้รวดเร็ว แต่ผู้ใช้ Increase ต้องการการเชื่อมต่อโดยตรงและการผสานเชิงลึกบนพื้นฐานของ ความรู้เรื่อง payment network
  • API ใช้ คำศัพท์ของเครือข่ายพื้นฐาน เช่น Nacha specification ตามเดิม และโมเดลกระบวนการดำเนินของ ACH transfer เป็นอ็อบเจ็กต์ย่อยแบบเปลี่ยนแปลงไม่ได้
  • หาก action ที่ผู้ใช้ทำได้แตกต่างกันมาก จะ แยก resource เช่น ach_transfer กับ inbound_ach_transfer แม้ช่วงแรกจะดูยืดยาว แต่ช่วยเพิ่มความคาดเดาได้ในระยะยาว
  • ระดับ abstraction ควรกำหนดให้สอดคล้องกับประสบการณ์ในโดเมนและความตั้งใจทุ่มเทของนักพัฒนาที่ผสานระบบ และหากเลือก abstraction ระดับต่ำแล้ว ก็ควรรักษาหลักการนั้นต่อไป

API resource สร้าง mental model ของผู้ใช้

  • API resource คือ คำนาม ของ API และการกำหนดชื่อกับโมเดลเป็นหนึ่งในส่วนที่ยากและสำคัญที่สุดของการออกแบบ API
  • การเลือกว่าจะเปิดเผย resource ใด จะประกอบสร้าง mental model ที่ผู้ใช้ใช้ทำความเข้าใจวิธีทำงานของผลิตภัณฑ์และงานที่ทำได้
  • Increase ใช้หลักการออกแบบที่เรียกว่า “No Abstractions” เพื่อช่วยในการตัดสินใจนี้
  • ความแตกต่างระหว่าง abstraction แบบ Stripe กับ Increase

    • Stripe มีจุดแข็งด้าน abstraction ที่ดึงโดเมนการชำระเงินอันซับซ้อนออกมาเป็น API ที่ผู้ใช้จัดการได้ง่าย
    • โมเดลเครือข่ายการชำระเงินหลายแบบเป็น API resource ชื่อ PaymentIntent และรวมความแตกต่างของ chargeback reason code ระหว่าง Visa กับ Mastercard ให้เป็น enum เดียว ทำให้ผู้ใช้ไม่ต้องพิจารณาสองเครือข่ายแยกกัน
    • ผู้ใช้ Stripe จำนวนมากเป็นสตาร์ทอัพระยะเริ่มต้นที่กำลังสร้างผลิตภัณฑ์ซึ่งไม่ใช่ระบบชำระเงินเอง และต้องการผสานระบบอย่างรวดเร็วแล้วกลับไปพัฒนาผลิตภัณฑ์หลัก มากกว่าจะเรียนรู้รายละเอียดบัตรเครดิตอย่างลึกซึ้ง
    • ผู้ใช้ Increase มีความรู้เดิมเกี่ยวกับ payment network อย่างลึกซึ้ง ทำงานกับเทคโนโลยีการเงินอย่างต่อเนื่อง และใช้ Increase เพื่อเชื่อมต่อกับเครือข่ายโดยตรงและผสานระบบเชิงลึก
    • ผู้ใช้กลุ่มนี้ต้องการรู้แน่ชัดว่า FedACH window ปิดเมื่อใดและ transfer จะมาถึงเมื่อใด และเข้าใจว่าหาก Standard Entry Class code ของ ACH transfer เปลี่ยนไป return timing ก็อาจเปลี่ยนตามได้
    • หากรวม ACH transfer กับ wire transfer เป็น API resource เดียวเพื่อซ่อนความซับซ้อนของเครือข่ายพื้นฐาน สำหรับผู้ใช้ Increase แล้วนั่นไม่ใช่การทำให้ง่ายขึ้น แต่เป็นความไม่สะดวก

No Abstractions ปรากฏใน API อย่างไร

  • ใช้คำศัพท์ของเครือข่ายจริง

    • Increase มักใช้ คำศัพท์ของเครือข่ายพื้นฐาน แทนที่จะตั้งชื่อ API resource และ attribute ขึ้นใหม่
    • เมื่อต้องสร้าง ACH transfer เป็น API parameter ที่เปิดเผยจะอิงตามชื่อ field ใน Nacha specification
  • resource แบบเปลี่ยนแปลงไม่ได้และ lifecycle object

    • resource ก็ถูกโมเดลให้สอดคล้องกับเหตุการณ์หรือข้อความในโลกจริง และแนวทางนี้ทำให้ API resource จำนวนมากขึ้นเป็นแบบ เปลี่ยนแปลงไม่ได้
    • จัดกลุ่ม resource แบบเปลี่ยนแปลงไม่ได้ไว้ใต้ lifecycle object ในรูปแบบ state machine คล้ายชุดของข้อความเครือข่ายที่ส่งได้ใน lifecycle ของ ACH transfer
    • อ็อบเจ็กต์ ach_transfer มี field status ที่เปลี่ยนไปตามเวลา และมี sub-object แบบเปลี่ยนแปลงไม่ได้หลายตัวที่ถูกสร้างขึ้นตามความคืบหน้าของ lifecycle
    • ach_transfer ใหม่อาจมี status เป็น pending_approval และ approval, submission, acknowledgement เป็น null
    • หลังจากส่งไปยัง FedACH แล้ว status จะกลายเป็น submitted และ approval, submission, acknowledgement จะถูกเติมด้วยข้อมูลแบบเปลี่ยนแปลงไม่ได้ ณ เวลาที่อนุมัติ ส่ง และรับทราบตามลำดับ
    • submission มีค่าต่าง ๆ เช่น trace_number และ submitted_at
  • แยก resource ตาม use case

    • หากแม้จะเป็น API resource เดียวกัน แต่ชุด action ที่ทำได้แตกต่างกันมากในแต่ละ instance Increase มักจะแยกออกเป็นหลาย resource
    • action ที่ทำได้ใน originated ACH transfer กับ received ACH transfer แทบจะตรงข้ามกัน จึงแยกเป็น ach_transfer และ inbound_ach_transfer
    • วิธีนี้ในช่วงแรกอาจดูยืดยาวและน่าเกรงขามกว่า เพราะมี resource จำนวนมากแสดงอยู่ทางซ้ายของเอกสาร API
    • แต่ในระยะยาว ความสัมพันธ์ระหว่าง resource กับ action จะ คาดเดาได้ มากขึ้น

หลักการช่วยลดการตัดสินใจออกแบบเล็ก ๆ

  • เมื่อออกแบบ API ที่ซับซ้อนตลอดหลายปี จะมีการตัดสินใจเล็ก ๆ เกิดขึ้นอย่างต่อเนื่อง และหลักการพื้นฐานที่วางไว้ตั้งแต่ต้นช่วยลด ภาระทางความคิด ของการตัดสินใจเหล่านี้
  • Input Message Accountability Data ที่จำเป็นเมื่อต้องส่ง wire transfer ไปยัง Federal Reserve ทำหน้าที่เป็น ID ที่ไม่ซ้ำกันทั่วทั้งระบบของ transfer นั้น
  • หากเป็น API ที่มี abstraction มาก วิศวกรอาจต้องพิจารณาว่าจะเรียกสิ่งนี้ให้ “เป็นมิตรกับผู้ใช้” มากขึ้นว่า trace_number, reference_number หรือ id
  • ที่ Increase จะกำหนดชื่อ field เป็น input_message_accountability_data แล้วเดินหน้าต่อ
  • ชื่อนี้อาจไม่ใช่ชื่อที่ผู้ใช้เห็นครั้งแรกแล้วเข้าใจได้ทันที แต่ช่วยให้เข้าใจได้โดยตรงว่ามันแมปกับระบบพื้นฐานอย่างไร

เกณฑ์ในการกำหนดระดับ abstraction

  • No Abstractions ไม่ใช่หลักการที่เหมาะกับทุก API
  • ระดับ abstraction ที่เหมาะสมขึ้นอยู่กับ ประสบการณ์ในโดเมน ของนักพัฒนาที่ผสานระบบ ความเข้าใจต่อพื้นที่ผลิตภัณฑ์ และพลังงานที่จะทุ่มให้กับการผสานระบบ
  • หากสร้าง API ที่มี abstraction มาก ต้องคิดอย่างลึกซึ้งก่อนเพิ่มฟีเจอร์ใหม่
  • หากสร้าง API ที่มี abstraction น้อย ต้องยึดมั่นกับแนวทางนั้น และต้านทานสิ่งล่อใจที่จะเพิ่ม abstraction

1 ความคิดเห็น

 
GN⁺ 2024-04-27
ความคิดเห็นบน Hacker News
  • อาจ มีให้ทั้งสองแบบเสมอ ก็ได้
    ให้ API ระดับต่ำที่ควบคุมได้ละเอียดแต่ต้องอาศัยความเชี่ยวชาญเชิงลึก แล้วสร้าง API ระดับสูงทับอยู่ด้านบนเพื่อแมปกรณีใช้งานทั่วไปให้เป็นงานง่าย ๆ ไม่กี่อย่าง เพราะอย่างไรเสีย ลูกค้าบางรายก็อาจกำลังทำเลเยอร์ระดับสูงแบบนี้เองแบบไม่ค่อยเรียบร้อยอยู่แล้ว
    ถ้าแยกสองเลเยอร์ออกจากกันอย่างสะอาด แรงกดดันที่จะใส่ abstraction ลงใน API ระดับต่ำ หรือเพิ่มรอยขรุขระและกรณีพิเศษลงใน API ระดับสูงก็จะลดลง เพราะถ้าลูกค้าต้องการสิ่งเหล่านั้น มันก็มีอยู่แล้วใน API อีกตัวหนึ่ง
    ถ้ามีเอกสารให้ลูกค้าเรียนรู้วิธีย้ายจากเลเยอร์หนึ่งไปยังอีกเลเยอร์หนึ่งได้ด้วยก็ยิ่งดี อาจดึงดูดลูกค้าที่ยังไม่รู้โครงสร้างภายในของเครือข่ายการชำระเงินอย่างลึกซึ้ง แต่ต้องการเติบโตไปในทิศทางนั้นได้ด้วย

    • ควรมี API ระดับต่ำ ที่จัดการกรณีซับซ้อนซึ่งพบไม่บ่อยได้ และมี API ระดับสูง แบบง่ายสำหรับกรณีทั่วไปที่สร้างทับอยู่ด้านบน
      วันนี้ผมใช้ Web File System API แล้วการเขียนสตริงหนึ่งตัวลงไฟล์ต้องเรียกฟังก์ชัน 7 ครั้ง และส่วนใหญ่เป็นแบบ asynchronous ยังไม่นับการจัดการ error อีกทั้งต้องทำใน worker และการตั้งค่า worker เองก็ยุ่งยากพอ ๆ กัน ความน่ากลัวคล้าย ๆ กันพบได้ใน IndexedDB, WebRTC และการจัดการ DOM ธรรมดา ส่วน Vulkan, DirectX, ffmpeg นั้นหนักกว่านี้มาก
      หากต้องรองรับกรณีพิเศษสารพัด ความซับซ้อนบางส่วนก็พอมีเหตุผล แต่กรณีส่วนใหญ่ไม่ได้เป็นกรณีพิเศษแบบนั้น
      การออกแบบ API ควรเริ่มจากการสเก็ตช์ก่อนว่าโค้ดที่ใช้ API ในกรณีทั่วไปจะหน้าตาเป็นอย่างไร และกรณีเหล่านั้นควรเรียบง่ายที่สุดเท่าที่เป็นไปได้ ตัวอย่างเช่น fetch API ทำได้ค่อนข้างดี ส่วน XMLHttpRequest ไม่ได้เป็นแบบนั้นเลย
      https://developer.mozilla.org/en-US/docs/Web/API/FileSystemS...
      ผมเคยคิดหลายครั้งว่าอยากให้มี API เลเยอร์อำนวยความสะดวก แบบรวมศูนย์สำหรับ Web API ทั้งหมด คือห่อความสามารถทรงพลังทั้งหมดด้วย wrapper “standard library” ที่สอดคล้องกัน และอย่างน้อยรองรับกรณีใช้งานที่พบบ่อยที่สุด เบราว์เซอร์สมัยใหม่มีพลังมาก แต่พลังนั้นไม่ค่อยเป็นที่รู้จักหรือถูกใช้น้อยลง เพราะการออกแบบ API แต่ละตัวไม่เหมือนกัน และยากต่อการเรียนรู้หรือใช้งานโดยไม่จำเป็น
      คล้ายกับสิ่งที่ jQuery เคยทำกับ DOM แต่ควรมีเวทมนตร์น้อยลงและมีฟีเจอร์เสริมน้อยกว่า node.js มี API ที่สอดคล้องกันอยู่บ้าง แต่ค่อนข้างเก่าแล้ว เช่น การรองรับ Promise ไม่สม่ำเสมอ คล้ายกับแนวทางของ Python ที่พยายามทำ API ให้ “เป็นแบบไพธอน” ด้วย
    • ผมชอบแพตเทิร์นนี้เป็นพิเศษเมื่อ API ระดับสูงที่ต้องการสามารถนำไปใช้งานนอกไลบรารีได้ แบบนั้นจะช่วยยืนยันได้ว่า API ระดับต่ำยืดหยุ่นพอ และตัวเราเองก็จะได้ ใช้ API ของตัวเองโดยตรง ในฐานะผู้ใช้
      เมื่อคุ้นเคยกับมุมมองการใช้งานภายในของเครื่องมือแล้ว มันง่ายมากที่จะลืมว่าผู้คนใช้งานจริงกันอย่างไร
    • Git เป็นตัวอย่างแบบนี้
      มีคำสั่ง “porcelain” ระดับสูงอย่าง branch และ checkout และมีคำสั่ง “plumbing” ระดับต่ำอย่าง commit-tree และ update-ref
      https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
    • .NET ก็ใช้แนวทางนี้อยู่มาก มีบทความในบล็อกนักพัฒนาล่าสุดเกี่ยวกับ file I/O: https://devblogs.microsoft.com/dotnet/the-convenience-of-sys...
    • แต่ในทางกลับกัน พื้นที่ผิวของ API จะเพิ่มเป็นสองเท่า จึงเป็น trade-off ที่ต้องพิจารณา ในหลายกรณีอาจเป็นการตัดสินใจที่ถูกต้อง
  • ชอบส่วนที่อธิบายว่าทำไม Increase ถึงเลือกแนวทางที่ต่างออกไป เวลาออกแบบสิ่งพื้นฐาน บริบท สำคัญมาก แต่โดยทั่วไปผู้คนไม่ค่อยยอมรับเรื่องนี้มากพอ

  • ในที่นี้ “ไม่มี abstraction” แท้จริงแล้วหมายถึง ให้ใช้คำศัพท์ของระบบพื้นฐานตามเดิม ซึ่งโดยทั่วไปก็เป็นหลักการตั้งชื่อที่ดี
    ปัญหาจะเกิดขึ้นอย่างหลีกเลี่ยงไม่ได้เมื่อเวลาผ่านไป ระบบพื้นฐานมีหลายระบบขึ้น และเริ่มเรียกสิ่งเดียวกันด้วยชื่อต่างกัน หรือแย่กว่านั้นคือใช้ชื่อเดียวกันกับคนละสิ่งกัน ในตัวอย่างนี้ ถ้าโมเดลของผู้ให้บริการชำระเงินพื้นฐานต่างกันจะทำอย่างไร? แล้วถ้า Federal Reserve ยกเลิก Input Message Accountability Data แล้วเปลี่ยนเป็นแนวคิดใหม่จะทำอย่างไร?
    อุตสาหกรรมการชำระเงินอาจเรียบง่ายกว่าการขนส่งหรือโปรโตคอลเครือข่ายมากก็ได้ ถ้าคุณสร้างผลิตภัณฑ์ packet switching ที่อิง X.25 แล้วภายหลังต้องการรองรับ TCP/IP ด้วย abstraction ที่ถูกต้องคืออะไร?

    • ขอบคุณที่อ่านอย่างละเอียด
      เรื่องการยกเลิกนั้น โชคดีที่ระบบพื้นฐานไม่ได้เปลี่ยนแปลงมากนักเลยยังไม่เป็นไร Input Message Accountability Data จะไม่หายไป แต่ถ้าเช่นเราเริ่มออกบัตรกับ Mastercard นอกเหนือจาก Visa ก็จะเจอการชนกันแน่นอน
      เราเคยทดลอง abstraction อยู่บ้าง และตรงจุดนั้นก็อาจทำแบบนั้นได้ กฎหนึ่งที่ยึดมาตลอดคือไม่ abstract “อ็อบเจกต์พื้นฐาน” แต่แนะนำการประกอบในระดับที่สูงขึ้นเพื่อความสะดวก ตัวอย่างเช่น สิ่งที่เรียกว่า “Card Payment” ไม่มีอยู่จริง (https://increase.com/documentation/api#card-payments) มันเป็นเพียงวิธีผูกการอนุมัติบัตรกับข้อความการชำระบัญชีที่เกี่ยวข้องเข้าด้วยกันเท่านั้น แต่เพราะมีประโยชน์มากสำหรับผู้ใช้ และการกระทบยอดด้วยตัวเองก็ไม่ง่าย เราจึงลองทำดู อย่างไรก็ตาม ผมคิดว่าข้อความเครือข่ายพื้นฐาน หรือ “อ็อบเจกต์พื้นฐาน” พร้อมฟิลด์ดั้งเดิมทั้งหมดก็ควรเข้าถึงได้ผ่าน API ด้วย
      น่าเสียดายที่ API สาธารณะที่ผมเคยทำงานด้วยเป็นสายการชำระเงิน 100% เลยอยากมีมุมมองอื่นบ้าง
    • บทความยังพูดชัดเจนด้วยว่าหมายถึง “ไม่รวมอ็อบเจกต์ที่คล้ายกันเข้าด้วยกัน” และนั่นทำให้การตัดสินใจเรื่องการตั้งชื่อเป็นไปได้
    • “ใช้คำศัพท์ของระบบพื้นฐานตามเดิม” ฟังดูคล้ายกับ Domain-Driven Design อยู่บ้าง เพียงแต่ในกรณีนี้ “ระบบพื้นฐาน” อาจเอนเอียงไปทางการ implement มากกว่าจะเป็นโดเมนธุรกิจจริง
      ใน DDD โดยทั่วไปจะทำตามชื่อและโมเดลแนวคิดที่โดเมนธุรกิจสร้างไว้อยู่แล้ว หากพยายามนำโมเดลหรือคำศัพท์ที่ “ปรับปรุงแล้ว” [0] ของตัวเองเข้ามา ก็จะเกิดแรงเสียดทานและความเข้าใจผิด เพิ่มโอกาสเกิดบั๊กในการผสานระบบ และเป็นการมองข้ามความเชี่ยวชาญที่ผ่านการพิสูจน์มาเป็นสิบหรือเป็นร้อยปี
      [0] https://xkcd.com/793/
  • บทความดี
    ถ้าคุณชอบ Stripe ผมเองในฐานะดีไซเนอร์และผู้ก่อตั้งสายเทคนิคก็มองว่าความเรียบง่ายและความสามารถด้าน frontend ของ Stripe น่าทึ่ง และเวลาเห็นพวกเขา เราอาจพยายามเลียนแบบความสามารถในการทำให้สิ่งต่าง ๆ เรียบง่ายและมอบประสบการณ์ที่ขัดเกลามาอย่างดี
    แต่ความชำนาญที่แท้จริงของ Stripe อยู่ที่ รู้จักลูกค้าเป็นอย่างดี และรู้ดีด้วยว่าลูกค้าถวิลหาความเรียบง่ายแบบไหน
    จากบทความนี้ Increase ก็ดูเป็นแบบเดียวกัน และดูเหมือนว่าจะใช้ความมุ่งเน้นที่เฉียบคมคล้ายกันต่อสิ่งที่ลูกค้าต้องการ มาสร้างแนวทางการออกแบบผลิตภัณฑ์ที่ยอดเยี่ยม น่าชื่นใจ

    • วิธีที่ Stripe สร้าง API และทีม: https://www.youtube.com/watch?v=IEe-5VOv0Js
    • ใน Stripe API ก็มีจุดที่เห็นความตึงระหว่าง “ทำสิ่งนี้ให้เป็นสากลได้ในอนาคต” กับ “ยอมรับเถอะว่าสิ่งนี้น่าจะใช้ได้เฉพาะกับวิธีชำระเงินแบบหนึ่งในตลาดหนึ่งเท่านั้น”
      โดยส่วนตัวผมชอบเวลาที่อย่างหลังเกิดขึ้นมากกว่า แต่ตรงนั้นก็มีการตัดสินใจเชิงสุนทรียะอยู่ด้วย
  • สิ่งนี้คล้ายกับแพตเทิร์นการออกแบบ Ubiquitous Language ใน Domain-Driven Design เป็นแนวทางที่ทำให้ implementation ใช้คำศัพท์จากโลกจริงตามที่ผู้เชี่ยวชาญโดเมนใช้
    https://thedomaindrivendesign.io/developing-the-ubiquitous-l...

    • ผมเคยได้ยินแนวคิดคล้าย ๆ กันมานานก่อน DDD จะเกิดขึ้นเสียอีก คือถ้าคำนามและกริยาในโค้ดไม่ตรงกับโดเมนของปัญหา นั่นคือ impedance mismatch และสักวันมันจะก่อปัญหา
      บทความนี้อ่านแล้วให้ความรู้สึกเหมือนปฏิกิริยาหลีกเลี่ยงความอับอายบางอย่าง ผู้คนไม่ชอบพูดอย่างผิดปกติว่า “ผมผิด” หรือ “เราผิด” จึงลากอุปมาไปมาเหมือนเด็กที่ย้ายผักในจานไปมาให้ดูเหมือนว่ากินแล้ว
      ยังทำให้นึกถึงคำว่า “ไม่มีข้อบกพร่องที่เห็นได้ชัด” ในสุนทรพจน์รางวัลทัวริงของ Hoare ด้วย
  • นี่เป็นตัวอย่างที่ดีของแนวคิด Ubiquitous Language ใน Domain-Driven Design
    ควรใช้ภาษาที่ผู้เชี่ยวชาญโดเมนเข้าใจ ถ้าผู้ใช้รู้จักไฟล์ NACHA อยู่แล้ว ทันทีที่ใช้คำอื่น พวกเขาต้องคอยรักษาการแมปในหัว
    ในทางกลับกัน ในกรณีของ Stripe ผู้ใช้ไม่ใช่ผู้เชี่ยวชาญโดเมน ดังนั้นจึงมีคุณค่าที่จะสร้าง abstraction ที่เข้าใจได้และซ่อนรายละเอียดที่ไม่จำเป็นไว้ ถ้าต้องสอนภาษาให้ผู้ใช้ ก็ควรทำให้ง่ายที่สุดเท่าที่จะทำได้

    • พูดอีกอย่างคือ พวกเขาเป็นผู้เชี่ยวชาญโดเมนด้าน ประเภทของธุรกรรม ที่ต้องการทำ ไม่ใช่ผู้เชี่ยวชาญว่าธุรกรรมถูก implement อย่างไรในระบบการเงิน
  • ถ้าไม่มี abstraction อย่าง POSIX แอปพลิเคชันก็คงต้องเขียน adapter สำหรับทุก filesystem ที่รองรับ

  • น่าสนใจ
    ชื่อของแนวคิดนี้ชวนให้เข้าใจผิด ในที่นี้ “ไม่มี abstraction” ไม่ได้หมายถึงไม่มี abstraction ตามตัวอักษร แต่หมายถึง “ใช้ชุด abstraction เฉพาะชุดนี้ และไม่ใช้ abstraction อื่น” ชุดย่อยเฉพาะที่พวกเขาอธิบายนั้นคุ้มค่าต่อการอภิปราย แต่แน่นอนว่ามันคือชุดของ abstraction
    ตัวอย่างเช่น พวกเขาบอกว่า “เมื่อทำ ACH transfer เป็น API ให้ตั้งชื่อพารามิเตอร์ที่เปิดเผยตามชื่อฟิลด์ในสเปก Nacha” แต่ สเปก เองก็เป็น abstraction
    พวกเขาบอกว่า “เหมือนกับการใช้คำศัพท์ของเครือข่าย เราพยายามโมเดล resource ให้สอดคล้องกับเหตุการณ์จริง เช่น การกระทำที่เกิดขึ้นแล้วหรือข้อความที่ถูกส่ง ผลคือ API resource จำนวนมากขึ้นกลายเป็น immutable และถูกผูกไว้ใต้ ‘อ็อบเจกต์ lifecycle’ แบบ state machine” ซึ่งทั้งความ immutable ในความหมายนี้และ “อ็อบเจกต์ lifecycle” ก็เป็น abstraction
    “ถ้าชุดการกระทำที่ผู้ใช้สามารถทำกับแต่ละ instance ใน API resource หนึ่ง ๆ แตกต่างกันมาก เรามักจะแยกออกเป็นหลาย resource” นี่ก็เป็น abstraction อีกแบบหนึ่ง เพียงแต่แบ่งในระดับที่ต่างจาก Stripe API
    ท้ายที่สุดแล้ว นี่คือชุดของการตัดสินใจด้านการออกแบบและ abstraction ไม่ใช่หลักการ “ไม่มี abstraction” การตัดสินใจที่สำคัญที่สุดดูเหมือนจะเป็นการ generalize ให้น้อยที่สุดเท่าที่เป็นไปได้ และ generalization ก็เป็น abstraction ประเภทหนึ่ง บางทีชื่อที่ถูกต้องกว่าน่าจะเป็น “generalization ที่น้อยลง”

  • เห็นส่วนที่บอกว่า “ค่าบริการรายเดือนต่อผู้ใช้ที่สร้างบน Increase แตกต่างกันไปตามกรณีการใช้งาน”
    ตอนนี้กำลังเพิ่มการเข้าถึง API สาธารณะให้กับ เอนด์พอยต์ AI text-to-SQL ที่รองรับ RAG อยู่ และปัญหาใหญ่ที่สุดคือการตั้งราคา มีใครพอรู้ไหมว่าประมาณช่วงราคาเท่าไร? ราคาต้องสะท้อนทั้งโทเคนของ OpenAI หรือวิธีให้ผู้ใช้ใส่โทเคน OpenAI ของตัวเอง, ปริมาณการใช้งานฐานข้อมูล และในอนาคตรวมถึงการตั้งค่าแคชกับการจำกัดอัตราด้วย

    • โดยพื้นฐานแล้ว ราคาควรกำหนดจาก คุณค่า ไม่ใช่ต้นทุน[1] ดังนั้นต้องคิดก่อนว่าลูกค้าได้รับคุณค่าอะไร แล้วเริ่มจากตรงนั้น
      ตัวอย่างเช่น เท่าที่รู้ Gong เรียกเก็บเงินจากหลายองค์กรปีละมากกว่า 100,000 ดอลลาร์ ต่อให้คำนึงถึงสตอเรจ, CPU และต้นทุนดำเนินงานอื่น ๆ ต้นทุนก็ไม่น่าจะใกล้เคียงกับต้นทุนคอมพิวต์เลย น่าจะต่างกันอย่างน้อยหลายเท่าตัวในระดับเลขหลักเดียว แต่ทีมขายสร้างรายได้อย่างตรงไปตรงมามาก ดังนั้นเลเวอเรจที่ซื้อได้ในรูปแบบเครื่องมืออย่าง Gong จึงมีคุณค่าที่เห็นได้ทันทีและชัดเจน
      [1]: ข้อยกเว้นของหลักการที่ว่าควรหลีกเลี่ยงการตั้งราคาแบบบวกกำไรจากต้นทุน คือกรณีที่ขายสินค้าโภคภัณฑ์ทั่วไป แต่คุณไม่ได้อยู่ในสถานการณ์นั้น!