3 คะแนน โดย GN⁺ 2024-04-27 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • การตั้งชื่อและการทำโมเดลของทรัพยากร API เป็นส่วนที่ยากและสำคัญที่สุดในการออกแบบ API ทรัพยากรเหล่านี้ประกอบขึ้นเป็น mental model ของผู้ใช้ต่อวิธีการทำงานและความสามารถของผลิตภัณฑ์
  • ทีม Increase ใช้หลักการ "ไม่มีการทำ abstraction" เพื่อช่วยกำหนดการออกแบบ API

แนวทางการออกแบบ API ของ Stripe

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

ผู้ใช้ของ Increase และหลักการออกแบบ API

  • ในทางกลับกัน ผู้ใช้ของ Increase มีความรู้เชิงลึกเกี่ยวกับเครือข่ายการชำระเงิน และเลือก Increase เพราะต้องการการเชื่อมต่อกับเครือข่ายโดยตรงและการผสานรวมในระดับลึก
  • พวกเขาต้องการรู้เวลาที่หน้าต่าง FedACH ปิดและเวลาที่จะเกิดการโอนอย่างแม่นยำ และเข้าใจว่าการตั้งค่า SEC code ที่ต่างกันในการโอน ACH อาจส่งผลต่อช่วงเวลาของการตีกลับ
  • ความพยายามที่จะซ่อนความซับซ้อนพื้นฐานของเครือข่ายเหล่านี้ไม่ได้ทำให้ชีวิตง่ายขึ้น มีแต่จะทำให้ผู้ใช้หงุดหงิด
  • จากการพูดคุยกับผู้ใช้กลุ่มแรก จึงได้หลักการ "ไม่มีการทำ abstraction" และนำมาใช้กับการออกแบบ API

หลักการ "ไม่มีการทำ abstraction" ส่งผลต่อการออกแบบ API อย่างไร

  • ใช้คำศัพท์ที่มีการใช้งานจริง: แทนที่จะสร้างชื่อเฉพาะขึ้นมาสำหรับทรัพยากรและพร็อพเพอร์ตีของ API ก็ใช้คำศัพท์ของเครือข่ายต้นทางโดยตรง (เช่น พารามิเตอร์ของการโอน ACH ใช้ชื่อฟิลด์ตามข้อกำหนดของ Nacha)
  • ความไม่เปลี่ยนแปลง: ใช้เหตุการณ์จริงเป็นโมเดลเพื่อให้ทรัพยากร API จำนวนมากขึ้นเป็น immutable การจัดกลุ่มคลัสเตอร์ของทรัพยากรที่ immutable ให้เป็น state machine แบบ "วัตถุวงจรชีวิต" เป็นวิธีที่ได้ผล (เช่น ฟิลด์ status ของอ็อบเจ็กต์ ach_transfer และอ็อบเจ็กต์ย่อยแบบ immutable หลายตัวที่ถูกสร้างขึ้นตามวงจรชีวิตของการโอน)
  • แยกทรัพยากรตามกรณีใช้งาน: หากชุดการกระทำที่ผู้ใช้ทำได้แตกต่างกันมากตามอินสแตนซ์ของทรัพยากร ก็จะแยกออกเป็นหลายทรัพยากร (เช่น การโอน ACH ขาออกกับการโอน ACH ขาเข้าแยกเป็นทรัพยากรคนละตัว)

การยึดถือแนวทางนี้ของทีมวิศวกรรม

  • ทีมวิศวกรรมให้คำมั่นว่าจะยึดถือแนวทางนี้
  • เมื่อออกแบบ API ที่ซับซ้อนต่อเนื่องหลายปี จำเป็นต้องตัดสินใจแบบเพิ่มทีละน้อยอยู่เสมอ การให้คำมั่นล่วงหน้าว่าจะยึดหลักการพื้นฐานช่วยลดภาระทางความคิดในการตัดสินใจเหล่านั้น
  • ตัวอย่างเช่น ในกรณีของฟิลด์ Input Message Accountability Data ที่จำเป็นเมื่อโอนเงินไปยังธนาคารกลางสหรัฐฯ หากเป็น API ที่มี abstraction สูง ก็ต้องมาคิดว่าจะตั้งชื่อฟิลด์นี้อย่างไรให้ "เป็นมิตรกับผู้ใช้" แต่ที่ Increase วิศวกรสามารถตั้งชื่อฟิลด์เป็น input_message_accountability_data แล้วไปต่อได้เลย

ความเห็นของ GN⁺

  • ระดับของ abstraction ใน API อาจแตกต่างกันไปตามระดับประสบการณ์ของนักพัฒนากับโดเมนของผลิตภัณฑ์นั้น และพลังงานที่พร้อมจะทุ่มให้กับการผสานรวม ดังนั้นเมื่อต้องออกแบบ API จึงสำคัญที่จะต้องพิจารณาระดับ abstraction ที่เหมาะสมสำหรับนักพัฒนาที่จะนำไปผสานรวม
  • หากจะสร้าง API ที่มี abstraction สูง ก็ควรคิดให้รอบคอบก่อนเพิ่มฟีเจอร์ใหม่ ในทางกลับกัน หากสร้าง API ที่มี abstraction ต่ำ ก็ต้องยึดแนวทางนั้นไว้และต้านทานความยั่วยวนที่จะเพิ่ม abstraction
  • การใช้คำศัพท์ของเครือข่ายหรือโปรโตคอลต้นทางโดยตรงอาจช่วยให้นักพัฒนาเข้าใจ underlying system ได้ดีขึ้น แต่ในทางกลับกันก็อาจเป็นกำแพงสำหรับนักพัฒนาที่เพิ่งเริ่มต้นเช่นกัน ดังนั้นการใส่คำอธิบายหรือเอกสารประกอบให้ดีจึงน่าจะสำคัญ
  • การใช้ immutable object ในการออกแบบ API อาจมีประสิทธิภาพในการรักษาความสอดคล้องของข้อมูลและป้องกัน side-effect แต่ในอีกด้านหนึ่งก็อาจไม่สะดวกเมื่อจำเป็นต้องอัปเดตข้อมูล จึงต้องพิจารณา trade-off ให้ดี
  • การแยกทรัพยากรตามกรณีใช้งานอาจเพิ่มความซับซ้อนของ API แต่ในระยะยาวอาจช่วยเพิ่มความคาดเดาได้ อย่างไรก็ตาม หากแยกย่อยมากเกินไปก็อาจทำให้ใช้งานยากลง จึงสำคัญที่จะหาระดับที่เหมาะสม

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

 
GN⁺ 2024-04-27
ความคิดเห็นบน Hacker News
  • ควรมีทั้ง API ที่ใช้การนามธรรมระดับต่ำและ API ที่ใช้การนามธรรมระดับสูง

    • API ระดับต่ำให้การควบคุมอย่างละเอียด แต่ต้องอาศัยความรู้เฉพาะทาง
    • API ระดับสูงให้การทำงานที่เรียบง่ายซึ่งเหมาะกับกรณีใช้งานทั่วไป
    • หากคงเส้นแบ่งระหว่าง API ทั้งสองแบบไว้อย่างชัดเจน ก็จะช่วยลดแรงกดดันในการเพิ่มชั้นนามธรรมหรือกรณีพิเศษเข้าไปในแต่ละ API
    • ควรมีเอกสารช่วยให้ไคลเอนต์เรียนรู้วิธีย้ายจาก API หนึ่งไปยังอีก API หนึ่ง
  • ชอบส่วนที่อธิบายว่าทำไม Increase ถึงเลือกแนวทางที่ต่างออกไป

    • เวลาออกแบบสิ่งพื้นฐาน บริบทมีความสำคัญมาก แต่คนส่วนใหญ่มักตระหนักเรื่องนี้ไม่มากพอ
  • ความสามารถที่แท้จริงของ Stripe คือการเข้าใจลูกค้าและมอบความเรียบง่ายที่ลูกค้าต้องการ

    • Increase ก็เข้าใจสิ่งที่ลูกค้าต้องการได้ดี และแสดงให้เห็นถึงการโฟกัสในลักษณะคล้ายกันในการสร้างแนวทางการออกแบบเพื่อทำผลิตภัณฑ์ที่ยอดเยี่ยม
  • คล้ายกับแพตเทิร์นการออกแบบ "ภาษาสากล (Ubiquitous Language)" ใน Domain-Driven Design ที่ใช้คำเดียวกับคำจริงที่ผู้เชี่ยวชาญโดเมนใช้ในการทำงานจริง

  • ควรใช้ภาษาที่ผู้เชี่ยวชาญโดเมนเข้าใจได้

    • หากผู้ใช้รู้จักไฟล์ NACHA อยู่แล้ว การใช้คำอื่นจะทำให้ต้องคอยแมปความหมายไว้ในหัว
    • ในกรณีของ Stripe ผู้ใช้ไม่ใช่ผู้เชี่ยวชาญโดเมน ดังนั้นการสร้างชั้นนามธรรมที่เข้าใจได้และซ่อนรายละเอียดที่ไม่จำเป็นจึงมีคุณค่า
  • หากไม่มีนามธรรมแบบ POSIX แอปพลิเคชันก็ต้องเขียนอะแดปเตอร์สำหรับทุกไฟล์ซิสเต็มที่รองรับ

  • มีการบอกว่าโครงสร้างบางส่วนของ API ถูกสร้างแบบ 1:1 ตามสเปกที่ถูกกำหนดจากภายนอก

    • ถ้าสเปกเหล่านี้ค่อย ๆ พัฒนาหรือเปลี่ยนไป จะเกิดอะไรขึ้น? จะกลายเป็น API ใหม่หรือไม่?
  • สิ่งหนึ่งที่โมเดลให้ดูสะอาดได้ยากใน Payment API คือ ระบบการชำระเงินแสดงบทบาทของผู้จ่ายและผู้รับเงินต่างกันเมื่อมีการคืนเงิน

    • ตัวอย่างเช่น ในบางระบบ ผู้จ่ายและผู้รับเงินอาจคงอยู่ในตำแหน่งเดียวกับการชำระเงินครั้งแรก
    • ขณะที่ในบางระบบ บทบาทดังกล่าวจะสลับกัน