- exe ไม่ได้เรียก Payment API โดยตรงในทุกจุดของตรรกะผลิตภัณฑ์ แต่บันทึกการเปลี่ยนแปลงสถานะเป็น ข้อเท็จจริงที่เรียกเก็บเงินได้ (billable facts) และกระทบยอดสถานะที่ยืนยันแล้วกับ Stripe
- ในโครงสร้างเดิม ธุรกรรมฐานข้อมูลและการเรียก Payment API พันกัน ทำให้ข้อยกเว้นอย่าง ความล้มเหลวบางส่วน, สถานะ subscription ที่ผิดปกติ และการปฏิเสธการชำระเงิน กระทบไปถึง flow ของผลิตภัณฑ์
- เมื่อมีการเพิ่ม seat ของทีม จะทำเครื่องหมายสถานะเป็น dirty แล้ว worker ภายหลังจะคำนวณจำนวนตามกฎธุรกิจ และอัปเดตจำนวน subscription ใน Stripe เฉพาะเมื่อมีการเปลี่ยนแปลงเท่านั้น
- เมื่อแยกการชำระเงินออกมา การ onboarding สมาชิกทีมใหม่ จะไม่ขึ้นกับโค้ดการชำระเงิน และแม้เปลี่ยนกฎการคำนวณ seat ก็ไม่กระทบ flow การเชิญและการสมัครเข้าร่วม
- ใช้โครงสร้างการกระทบยอดแบบเดียวกันกับ การเรียกเก็บเงินตามการใช้งานจริง เช่น VM ที่ใช้งานอยู่และปริมาณการใช้ดิสก์ รวมถึงการซื้อในแอปบน iOS ทำให้คง event ของผลิตภัณฑ์ไว้ได้ และเปลี่ยนเฉพาะการเชื่อมต่อกับผู้ให้บริการชำระเงินแต่ละราย
การแยกตรรกะการชำระเงินออกจาก flow ของผลิตภัณฑ์
- หากตรรกะการชำระเงินปะปนกับตรรกะธุรกิจทั่วไป โค้ดที่เกี่ยวข้องจะกระจายไปอยู่ในทุกเส้นทางหลักที่ต้องมีการเรียกเก็บเงิน และโครงสร้างราคาก็จะเปราะบางจนเปลี่ยนแปลงได้ยาก
- exe มุ่งให้ไม่ใช่มีเพียงคนเดียวที่ผูกขาดความรู้ด้านการชำระเงิน แต่ทุกคนสามารถแก้ไขโค้ดที่เกี่ยวข้องได้ ขณะที่กรณียกเว้นที่ซับซ้อนให้ผู้เชี่ยวชาญรับผิดชอบ
- การเรียกเก็บเงิน seat ของทีมในช่วงแรกถูกผูกไว้เป็น flow ใหญ่เพียง flow เดียว ตั้งแต่การยอมรับคำเชิญไปจนถึงการชำระเงิน
- ผู้ใช้ยอมรับคำเชิญ ยืนยันบัญชี แล้วเข้าร่วมทีม
- ได้รับสิทธิ์เข้าถึง VM ที่แชร์ และทรัพยากร compute ตามแพ็กเกจ
- ในกระบวนการนี้มีการเรียก Payment API ด้วย
- เมื่อรวมการเปลี่ยนแปลงฐานข้อมูลกับการเรียก API ภายนอกเข้าด้วยกัน อาจเกิด ความล้มเหลวบางส่วน ที่สำเร็จเพียงฝั่งเดียว
- สถานะ subscription ของทีมอาจผิดปกติ
- การชำระเงินสำหรับ seat เพิ่มเติมอาจถูกปฏิเสธ
- ยิ่งมีข้อยกเว้นสะสมมากเท่าไร โครงสร้างทั้งหมดก็ยิ่งเปราะบางขึ้น
ข้อเท็จจริงที่เรียกเก็บเงินได้และการกระทบยอดภายหลัง
- ข้อเท็จจริงที่เรียกเก็บเงินได้ คือการดำเนินการแบบ atomic ที่บ่งชี้ว่าสถานะหนึ่ง ๆ เปลี่ยนไปแล้ว
- รันตรรกะผลิตภัณฑ์ก่อนเพื่อยืนยันสถานะใหม่ของทรัพยากร
- จากนั้นปรับสถานะของผู้ให้บริการชำระเงินตามข้อเท็จจริงที่ยืนยันแล้ว
- Stripe ต้องการเพียง จำนวนสุดท้าย ไม่ใช่กระบวนการที่ทำให้ไปถึงสถานะนั้น
-
การกระทบยอด seat ของทีม
- เมื่อคำเชิญถูกยอมรับ จะทำเครื่องหมายสถานะ seat ของทีมเป็น dirty
- worker ภายหลังตรวจพบสถานะ dirty และคำนวณจำนวน seat ที่เพิ่มหรือลดตามกฎธุรกิจ
- อัปเดตจำนวน subscription ใน Stripe เฉพาะเมื่อจำนวนเปลี่ยนไปเท่านั้น
- เนื่องจากการเพิ่มสมาชิกทีมถูกแยกออกจากโค้ดการชำระเงิน แม้เขียน flow คำเชิญใหม่ การเรียกเก็บเงินก็จะไม่พังไปด้วย และวิธีคำนวณ seat ก็เปลี่ยนได้อย่างอิสระ
-
การเรียกเก็บเงินตามการใช้งานจริงและการซื้อในแอป
- กระบวนการกระทบยอดเดียวกันนี้ใช้ได้กับ การเรียกเก็บเงินตามการใช้งานจริง ทุกประเภท
- ระบบบันทึกข้อเท็จจริงเกี่ยวกับ VM ที่ใช้งานอยู่และปริมาณการใช้ดิสก์
- worker ด้านการวัดปริมาณนำข้อมูลนี้ไปกระทบยอดกับสถานะของผู้ให้บริการชำระเงิน
- แม้เพิ่มวิธีเรียกเก็บเงินแบบใหม่ ข้อเท็จจริงก็ยังคงเดิม เปลี่ยนเพียงวิธีกระทบยอดกับแต่ละ API เท่านั้น
- แอป iOS ก็ส่งต่อเพียงข้อเท็จจริงว่ามีคนสมัคร subscription ผ่านการซื้อในแอป และปรับสถานะการชำระเงินจริงภายหลัง
- โครงสร้างการชำระเงินกลายเป็นพื้นที่ที่สมาชิกคนอื่น ๆ ก็จัดการได้ และลดโอกาสที่การเปลี่ยนฟีเจอร์ผลิตภัณฑ์ เช่น flow คำเชิญ จะทำให้ระบบเรียกเก็บเงินเสียหาย
- กระบวนการกระทบยอดเดียวกันนี้ใช้ได้กับ การเรียกเก็บเงินตามการใช้งานจริง ทุกประเภท
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
ประเด็นสำคัญของบทความคือ แนวทางตรวจจับและประมวลผลการเปลี่ยนแปลงแบบอะซิงโครนัส ซึ่งช่วยลดความเชื่อมโยงกันและเหมาะกับการทำ side effect
แต่ LLM เป็นเครื่องมือที่เร่งให้โค้ดกระจายไปทั่ว แทนที่จะป้องกันไม่ให้เกิดปรากฏการณ์นั้น จึงมีแนวโน้มจะกลายเป็นหนี้เชิงสถาปัตยกรรม อีกทั้งก็ยังน่าสงสัยว่าจะรีวิวโค้ดที่ถูกสร้างเร็วกว่าความเร็วในการทำความเข้าใจได้อย่างไร และยิ่งน่ากลัวขึ้นไปอีกเมื่อมีการบอกว่า Exe ไม่ทำ code review เลย
สถาปัตยกรรมนี้น่าสนใจอยู่ก็จริง แต่ยังไม่ชัดเจนว่ามันแก้ปัญหาที่ยกมาในช่วงต้นบทความได้อย่างไร ถ้าการชำระเงินถูกปฏิเสธ ดูเหมือนว่าจะปล่อยทรัพยากรที่ยังไม่ได้จ่ายก่อน แทนที่จะรับประกันว่าทรัพยากรทั้งหมดจะถูกชำระเงิน
ฝั่งผู้ดำเนินการ billing อาจ publish ข้อเท็จจริงว่า
declinedเพื่อเรียกคืนทรัพยากรได้ แต่เมื่อเทียบกับโฟลว์ทางเดียวที่สะอาด ก็จะกลายเป็นโครงสร้างแบบวนกลับ เหมาะกับบริการคอมพิวต์ที่เก็บเงินรายเดือนแบบ Exe แต่เป็นการประนีประนอมที่ยอมรับได้ยากสำหรับธุรกิจที่ต้องจัดส่งอุปกรณ์จริงหรือขายต่อที่นั่งของบริการอื่นอย่างไรก็ตาม บทความไม่ได้ตอบตรง ๆ ว่าจะจัดการกับกรณี API call หรือ database transaction ล้มเหลว, สถานะ subscription ผิดปกติ, หรือการถูกปฏิเสธการชำระเงินค่าที่นั่งอย่างไร ปัญหาอย่างการ refactor แล้วการเก็บ analytics พัง, แถวบางแถวไม่ถูกทำเครื่องหมายว่าอยู่ในสถานะเปลี่ยนแปลงอีกต่อไป, หรือเส้นทางใหม่ลืมใส่เครื่องหมายการเปลี่ยนแปลง ก็ยังคงอยู่เหมือนเดิม
และยังแก้ สถานะที่เกี่ยวข้องกับ billing ซึ่งอยู่ภายในตัวผลิตภัณฑ์ไม่ได้ เช่น ที่นั่ง, ขีดจำกัดโควต้าฟรี, การตั้งค่ายอดใช้จ่ายสูงสุด, หรือการหักจากยอดคงเหลือแบบเติมเงิน บริการผลิตภัณฑ์อาจ publish event และบริการ billing สะท้อนสถานะการปฏิบัติตามกลับไปยังตัวผลิตภัณฑ์ได้ แต่ถ้าทำแบบนั้นก็จะกลายเป็น distributed system แบบมีสถานะที่มีผู้กระทำอยู่สองฝ่าย
Stripe อาจดูเหมือนต้องการแค่ตัวเลขเดียว แต่เมื่อถึงระดับหนึ่ง การให้ รายละเอียดระดับรายการชำระเงิน ก็สามารถช่วยลด interchange fee และเพิ่มอัตราการอนุมัติได้
แนวทางของ Exe ที่ ไม่ทำ code review เป็นอะไรที่แปลกใหม่ ถ้าอย่างนั้นก็อยากรู้ว่าพวกเขาจัดการเรื่อง release และ test กันอย่างไร