3 คะแนน โดย GN⁺ 4 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ระบบที่จัดการเงินเป็นสถานะหลักควรออกแบบบนหลักการว่า ไม่สร้างข้อมูลขึ้นมาเอง ไม่ทำข้อมูลสูญหาย และไม่เชื่อถือสิ่งใดโดยไม่ตรวจสอบ
  • การแทนจำนวนเงินควรหลีกเลี่ยง float และผสมผสาน BigDecimal, จำนวนเต็มหน่วยย่อยที่สุด, จำนวนตรรกยะ ฯลฯ ให้เหมาะกับความรับผิดชอบของแต่ละส่วน อีกทั้งการ serialize ตัวเลข JSON ก็อาจสร้างปัญหา IEEE-754 double ขึ้นมาอีกครั้งได้
  • บัญชีแยกประเภทควรรักษาให้สามารถสร้างยอดคงเหลือและรายงานขึ้นใหม่ได้ผ่าน การบัญชีคู่, audit trail ที่แก้ไขไม่ได้, การแยก value time·booking time·settlement time และบันทึกการแก้ไข·ยกเลิก
  • กระแสเงินจริงต้องป้องกันการจ่ายซ้ำและการตกหล่นด้วยการจองเงิน, idempotency, state machine ที่เริ่มใหม่ได้, การตรวจสอบ API ภายนอก·webhook, outbox·CDC และ การกระทบยอด (reconciliation)
  • การควบคุมการเข้าถึง, การอนุมัติแบบ four-eyes, การติดตามการเปลี่ยนแปลงใน SDLC, การทดสอบแบบ property-based และ fault injection ทำให้ต้องถือว่าผู้ปฏิบัติงานภายในและการเปลี่ยนแปลงโค้ดก็อยู่ใน ขอบเขตความเชื่อถือ เช่นกัน

หลักการพื้นฐานของระบบฟินเทค

  • ในวิศวกรรมซอฟต์แวร์ที่เงินเป็นประเด็นหลัก ความสามารถในการติดตาม ความไม่เปลี่ยนรูป และความสามารถในการตรวจสอบ มีความสำคัญมากกว่า CRUD ทั่วไปอย่างมาก
  • ผู้อ่านเป้าหมายคือผู้ที่เพิ่งเข้าร่วมวงการฟินเทค ผู้ที่ทำงานในฟินเทคอยู่แล้ว และผู้ที่อยู่นอกฟินเทคแต่ต้องการเข้าใจว่าระบบเงินแตกต่างจากระบบทั่วไปอย่างไร
  • แพตเทิร์นทั้งหมดเป็นเครื่องมือเพื่อรักษาหลักการสามข้อ
    • No invented data: เงินไม่สามารถถูกสร้างจากที่ที่ไม่มีมาก่อนได้ จึงไม่อนุญาตให้ประมวลผลซ้ำหรือเปลี่ยนยอดคงเหลือตามอำเภอใจ
    • No lost data: ทุกสิ่งที่เกิดขึ้นกับเงินต้องถูกติดตามและจัดเก็บอย่างถาวร
    • No trust: ไม่เชื่อถือผู้ให้บริการภายนอก คอมโพเนนต์ภายใน หรือโลกจริง แต่ต้องตรวจสอบ

วิธีแทนเงิน

  • การแทนจำนวนเงินเป็นการตัดสินใจพื้นฐานที่สุดของระบบการเงิน และหากเลือกผิด ชั้นบนทั้งหมดจะรับช่วงข้อผิดพลาดไปด้วย
  • float/double แทบไม่ใช่ตัวเลือกที่ดี เพราะอาจทำให้สูญเสียความแม่นยำแบบคาดเดาได้ยาก
    • อย่างไรก็ตามมีข้อดีคือเร็ว ใช้หน่วยความจำมีประสิทธิภาพ และไม่ต้องใช้ไลบรารีหรือโครงสร้างข้อมูลเพิ่มเติม
  • ประเภท ความแม่นยำไม่จำกัด เช่น BigDecimal ช่วยควบคุมความแม่นยำในการคำนวณและตำแหน่งการปัดเศษได้อย่างชัดเจน
    • เหมาะกับการคำนวณระหว่างทางที่มีหลาย operation ต่อเนื่องกัน เช่น FX หรือการคำนวณราคา
  • การจัดเก็บเป็น จำนวนเต็มของหน่วยย่อยที่สุด เป็นวิธีใช้ความแม่นยำคงที่แบบเดียวกับระบบธนาคารกลางสำหรับเงินตราตามกฎหมายส่วนใหญ่
    • €12.34 จะถูกเก็บเป็น 1234
    • ต้องทำตามจำนวนหลักของ ISO 4217 และไม่ควรสมมติว่าเป็น 2 หลักเสมอ
    • คริปโทเคอร์เรนซีก็ใช้จำนวนเต็มหน่วยย่อยที่สุด เช่น satoshi, wei แต่ความแม่นยำแตกต่างกันตามสินทรัพย์ และถูกกำหนดโดยโทเคน เช่น decimals ของ ERC-20
    • จำนวนเงินคริปโทอาจเกินจำนวนเต็ม 64 บิตได้ จึงอาจต้องใช้จำนวนเต็มความกว้างไม่จำกัด
  • จำนวนตรรกยะ ทรงพลังที่สุดเมื่อไม่อนุญาตให้สูญเสียความแม่นยำ แต่ช้า และเมื่อแปลงเป็นรูปแบบอื่นก็ยากที่จะทำโดยไม่สูญเสียความแม่นยำ อีกทั้งมักต้องใช้ custom type หรือไลบรารี
  • วิธีจัดเก็บและวิธีคำนวณเป็นการตัดสินใจคนละเรื่อง ระบบหนึ่งอาจใช้การจัดเก็บแบบจำนวนเต็มร่วมกับการคำนวณระหว่างทางด้วย BigDecimal ได้
  • การจัดการขอบเขตก็สำคัญในการ serialize จำนวนเงินเช่นกัน
    • ตัวเลข JSON ทั่วไปเป็น IEEE-754 double ใน parser ส่วนใหญ่ ดังนั้นแม้จะระวังการแทนค่าภายในแล้ว ปัญหา float ก็อาจเกิดซ้ำที่ขอบเขตได้
    • เงินควรถูกส่งเป็นสตริง เช่น "12.34" หรือเป็นจำนวนเต็มของหน่วยย่อยที่สุด

การปัดเศษและการจัดการสกุลเงิน

  • การปัดเศษ หลีกเลี่ยงไม่ได้ในการหาร การแปลงสกุลเงิน ค่าธรรมเนียม ดอกเบี้ย การใช้อัตราส่วน และการย้ายความแม่นยำ จึงไม่ควรปล่อยให้เป็นนัย
  • กลยุทธ์การปัดเศษเป็นการตัดสินใจทางธุรกิจ
    • บางกรณีต้องปัดลงอย่างอนุรักษนิยม และอาจใช้ half-even เพื่อผลทางสถิติได้
    • ใครเป็นผู้รับเศษทศนิยมที่เหลืออาจมีผลทางกฎหมายและภาษี
  • ควรรักษาความแม่นยำเต็มไว้ให้นานที่สุดเท่าที่เป็นไปได้ และโดยทั่วไปปัดเศษเฉพาะที่ขอบเขต เช่น ก่อนจัดเก็บหรือก่อนแสดงให้ผู้ใช้เห็น
  • หากแบ่งค่าออกเป็นหลายส่วนแล้วปัดเศษ ผลรวมของส่วนย่อยอาจต่างจากค่าเดิม
    • อาจต้องมี rounding account อย่างชัดเจนตามสถานการณ์
  • เงินไม่สามารถแทนด้วยตัวเลขอย่างเดียวได้ และต้องจัดการคู่กับสกุลเงินเสมอ
    • การผูกจำนวนเงินกับสกุลเงินไว้ด้วยกันเป็น newtype, struct, class, record เช่น Money ช่วยลดโอกาสเกิดข้อผิดพลาด
    • ต้องห้ามการบวกเงินคนละสกุล และการแปลงต้องทำอย่างชัดเจนด้วยอัตราแลกเปลี่ยนที่ควบคุมอย่างเข้มงวด
    • อย่ารับรหัสสกุลเงินใด ๆ ตามอำเภอใจ แต่ต้องตรวจสอบกับชุดสกุลเงินที่ควบคุมไว้ที่ขอบเขตของระบบ
    • รหัสเงินตราตามกฎหมายใช้เป็นตัวระบุได้ แต่คริปโทเคอร์เรนซีต้องใช้ตัวระบุที่ซับซ้อนกว่า เช่น (network, contract address)
    • คริปโทเคอร์เรนซีแบบ pegged, bridged, wrapped ไม่เทียบเท่ากับสินทรัพย์อ้างอิง

อัตราแลกเปลี่ยน FX

  • FX rate มีทิศทางเสมอ
    • อัตรา EUR/USD ไม่ใช่ส่วนกลับอย่างง่ายของ USD/EUR
    • ในตลาดซื้อขาย การซื้อและการขายเป็นคำสั่งคนละราคาเนื่องจาก bid/ask spread
  • จุดเวลาของอัตราแลกเปลี่ยนก็เปลี่ยนผลลัพธ์เช่นกัน
    • อัตรา ณ ปัจจุบันใช้คำนวณมูลค่าของการถือครองปัจจุบันหรือธุรกรรมที่สมมติว่าเกิดขึ้นตอนนี้
    • อัตรา value-date ใช้ในการคำนวณการเปลี่ยนแปลงมูลค่าหรือภาษี
  • ในการแปลง มีอัตราแลกเปลี่ยนสองชนิดที่สำคัญ
    • Transactional rate คืออัตราที่การแปลงเกิดขึ้นจริง และอนุมานได้จากจำนวนเงินต้นทางและจำนวนเงินปลายทาง
    • Reference rate ใช้ในการประเมินและตัดสินความเทียบเท่า เช่น มูลค่าการถือครองหรือเกณฑ์ภาษี และไม่ใช่ราคาซื้อขายจริง
  • ไม่มีอัตราแลกเปลี่ยนมาตรฐานเดียว
    • อัตราแลกเปลี่ยนมาจากตลาด และแตกต่างกันตามสถานที่ซื้อขายหรือวิธีคำนวณ
    • อัตราของธนาคารกลางใกล้เคียงมาตรฐานที่สุด แต่ใช้ได้เฉพาะเป็น reference rate และแหล่งทางเลือกอื่นก็อาจใช้ได้เช่นกัน
  • ต้องเก็บแหล่งที่มาของจำนวนเงินและ reference rate ไว้ด้วยกัน เพื่อให้ตรวจสอบได้ภายหลัง

บัญชีแยกประเภทและการบัญชีคู่

  • การเคลื่อนย้ายของเงินต้องถูกบันทึกในรูปแบบที่ตรวจสอบได้และสร้างขึ้นใหม่ได้แม้ผ่านไปหลายปี
  • การบัญชีคู่ เป็นวิธีที่ใช้กันแพร่หลาย โดยบันทึกธุรกรรมทางการเงินเป็นรายการ entry ในรูปแบบ (credit account, debit account, amount)
    • การแสดงแบบคลาสสิกจะแยก debit row และ credit row สำหรับแต่ละการเคลื่อนย้าย
    • เพราะ entry ทั้งหมดย้ายจำนวนเงินเท่ากันจากบัญชีหนึ่งไปยังอีกบัญชีหนึ่ง บัญชีแยกประเภทจึงสมดุลเสมอ
  • เงินมีแหล่งที่มาและปลายทางเสมอ
    • ผู้ให้บริการภายนอกก็ควรมีบัญชีเฉพาะ เพื่อให้ติดตามเงินที่เข้าและออกจากระบบได้
  • ไม่ควรจัดเก็บยอดคงเหลือ แต่ให้อนุมานจากการเคลื่อนย้ายของเงิน
  • บัญชีมีประเภท เช่น asset, liability, equity
    • สมการบัญชี assets = liabilities + equity ต้องคงอยู่
    • ในทางปฏิบัติ ยังต้องมีบัญชี revenue และ expense เพื่อบันทึกรายได้ค่าธรรมเนียมหรือขาดทุนจาก write-off
    • สมการขยายคือ assets = liabilities + equity + revenue - expenses
  • ธุรกรรมหนึ่งมักสร้างการเคลื่อนย้ายหลายรายการ
    • อาจมีการเคลื่อนย้ายของยอดสุทธิและการเคลื่อนย้ายค่าธรรมเนียมแยกกัน
  • ตามธรรมเนียม posted entry จะไม่เปลี่ยนรูป และการแก้ไขทำโดยเพิ่ม entry ใหม่ที่หักล้างรายการเดิม

โมเดลเวลา: value, booking, settlement

  • ธุรกรรมมักมี timestamp สองรายการขึ้นไป และบางครั้งมีสามรายการ
    • Value time: เวลาที่ธุรกรรมเกิดขึ้นจริง
    • Booking time: เวลาที่ถูกบันทึกในระบบ
    • Settlement time: เวลาที่เงินถูกโอนจริงหรือเกิดเป็นรูปธรรม
  • Settlement time ไม่ได้มีอยู่ในทุกธุรกรรม และมักแสดงเป็น T+X
    • T+2 หมายถึง settlement เกิดขึ้น 2 วันหลังจาก value date
  • value time และ booking time แทบจะไม่ตรงกันเสมอ
    • หาก booking > value จะเป็น backdated และสำคัญเป็นพิเศษเมื่อรอบระยะเวลารายงานแตกต่างกัน
    • หาก booking < value จะเป็น forward-dated และเกิดในการชำระเงินที่จองไว้หรือการชำระเงินวันที่ในอนาคต
  • ตัวอย่างการชำระเงินด้วยบัตรคือกระแสที่การชำระเงินเกิดที่ T1 ระบบบันทึกที่ T2 และผู้ให้บริการชำระเงินโอนเงินเข้าบัญชีที่ T3
  • รายงานทางธุรกิจมักดู value time หรือ settlement time เป็นหลัก ส่วน booking time มีประโยชน์ต่อความสามารถในการติดตาม
  • หากรวมหลายจุดเวลาไว้ใน created_at เพียงค่าเดียว จะสูญเสียข้อมูลที่ไม่สามารถสร้างขึ้นใหม่ได้ในภายหลัง

บันทึกการตรวจสอบ, Event Sourcing, ความไม่เปลี่ยนรูป

  • ระบบการเงินอยู่ภายใต้การตรวจสอบตามกฎระเบียบ และอาจต้องพิสูจน์ประเด็นต่าง ๆ เช่น มีการปะปนกันระหว่างเงินของผู้ใช้กับเงินของบริษัทหรือไม่, อธิบายที่มาของรายได้ได้หรือไม่, ข้อมูลที่ให้แก่ภายนอกตรงกับความเป็นจริงหรือไม่, สถานะการคุ้มครองเงินเป็นอย่างไร เป็นต้น
  • audit trail คือประวัติทั้งหมด ไม่ใช่แค่สถานะปัจจุบัน แต่รวมถึงว่าสถานะนั้นถูกสร้างขึ้นมาอย่างไร
    • เกิดอะไรขึ้น
    • เกิดขึ้นเมื่อใด
    • ใครหรือสิ่งใดเป็นตัวกระตุ้น
    • ทำไมจึงเกิดขึ้น
  • ไม่เพียงการเคลื่อนย้ายเงินเท่านั้น แต่การแทรกแซงด้วยมือ, การเปลี่ยนแปลงการตั้งค่าอย่าง fee schedule, rate source, limit และการเปลี่ยนแปลงสิทธิ์ ก็จำเป็นต้องมีบันทึกการตรวจสอบ
  • การตัดสินใจอย่าง compliance check หรือ risk score อาจไม่เพียงพอหากเก็บไว้แค่ผลลัพธ์
    • หากอยู่ใน decision table หรือ rules engine เช่น DMN, Drools, Decisions4s ก็จะได้โครงสร้างที่เล่นซ้ำได้ว่า กฎใดถูกเรียกใช้กับอินพุตใดและได้ผลลัพธ์อะไร
  • Event sourcing คือแนวทางที่เป็นระบบสำหรับสร้าง audit trail
    • ไม่ได้แยกเก็บสถานะปัจจุบันกับ log แต่เก็บเฉพาะ event แล้วค่อยอนุมานสถานะ
    • ledger แบบบัญชีคู่เป็นตัวอย่างของแพตเทิร์นนี้ ในแง่ที่ไม่ได้เก็บ balance แต่คำนวณจาก entry
  • Event sourcing มีข้อจำกัดเชิงปฏิบัติมากเช่นกัน
    • ไม่จำเป็นต้องใช้ทุกที่ และถ้า ledger ครอบคลุมเรื่องเงินอยู่แล้ว โดเมนรอบข้างอาจเพียงพอด้วยโมเดลทั่วไปและ change log ที่เชื่อถือได้
    • เพื่อประสิทธิภาพ อาจแคชหรือทำ snapshot ของ balance และ projection ได้
    • แหล่งข้อมูล event มัก query อย่างมีประสิทธิภาพได้ยาก จึงอาจต้องทำงาน projection จำนวนมาก
    • event จะคงอยู่หลายปี ดังนั้นโค้ดของวันนี้ต้องอ่าน event เก่าในอดีตไกล ๆ ได้ด้วย
  • บันทึกการตรวจสอบต้องเป็น append-only เพราะหากแก้ไขได้ก็ใช้เป็นหลักฐานไม่ได้
    • เครื่องมือที่ใช้ได้ ได้แก่ append-only table, การเอา UPDATE·DELETE ออกจากสิทธิ์ของ DB, การบล็อก mutating operation ในชั้นแอปพลิเคชัน, และ tamper evidence ผ่าน checksum หรือ hash chain
  • ในระบบจริง บางครั้งต้องแก้ event log หรือ audit trail เพราะบั๊ก
    • โดยทั่วไป หลังจากข้อมูลถูกรายงานออกไปภายนอกแล้วควรถูกตรึงไว้ และหากยังไม่ถูกรายงาน อาจแก้ไข ณ จุดเดิมได้ก่อนจะตรวจพบปัญหาและปล่อยออกนอกระบบ

การยกเลิกและการแก้ไข

  • ความผิดพลาด เช่น posting จำนวนเงินผิดหรือ posting เข้าบัญชีผิด ย่อมเกิดขึ้นได้
  • ความไม่เปลี่ยนรูปกำหนดให้แก้ไปข้างหน้า
    • posting compensating entry ใหม่ และเชื่อมโยงกับ record ต้นฉบับแบบสองทาง
  • Reversal คือการหักล้างต้นฉบับอย่างสมบูรณ์ในเชิงเศรษฐกิจให้เหมือนไม่เคยเกิดขึ้น แต่ทั้งต้นฉบับและ reversal จะยังอยู่ในประวัติ
  • Correction หรือ adjustment คือการ booking ส่วนต่างระหว่างบันทึกจริงกับค่าที่ถูกต้อง หรือย้อนกลับก่อนแล้ว posting ใหม่ด้วยค่าที่ถูกต้อง
  • การแก้ไขอาจเข้าไปอยู่ในรอบระยะเวลารายงานที่ต่างจากต้นฉบับ
    • ต้องมีข้อมูลการเชื่อมโยง เพื่อให้รายงานสามารถจัดสรรการแก้ไขได้ถูกต้อง และแยกกิจกรรมจริงออกจาก cleanup ได้
  • โดยทั่วไปมักไม่อนุญาตให้ backdate ไปยังรอบระยะเวลารายงานที่ปิดแล้ว ดังนั้นในการแก้ไข จะกำหนด value time ในอดีตหรือไม่จึงขึ้นอยู่กับกำหนดการรายงาน

การดำเนินการกระแสเงิน: เงื่อนไขคงจริงและการสำรองเงิน

  • Invariant คือคุณสมบัติที่ต้องเป็นจริงเสมอในระบบ
    • accounting equation เป็นตัวอย่างหนึ่ง และผู้มีส่วนได้ส่วนเสียทางธุรกิจสามารถกำหนดเงื่อนไขได้หลายแบบ
  • วิธีบังคับใช้ invariant เป็นสิ่งที่เสริมกัน
    • ออกแบบให้สร้างได้เฉพาะ object ที่ถูกต้องตั้งแต่ขั้นตอนการสร้าง
    • ตรวจสอบใน runtime ด้วย assertion, test, property-based testing
    • วิเคราะห์ข้อมูลที่บันทึกแล้วภายหลังด้วย reconciliation job หรือ nightly check
  • ธุรกรรมที่โต้ตอบกับโลกภายนอกต้องหลีกเลี่ยง race condition
    • ต้องป้องกันสถานการณ์ที่เพิ่งพบว่ายอดเงินไม่พอหลังจากเรียกภายนอกไปแล้ว หรือใช้เงินก้อนเดียวกันสองครั้ง
  • Funds reservation หรือ hold-and-release คือแพตเทิร์นที่สำรองเงินไว้สำหรับธุรกรรมหนึ่งโดยเฉพาะก่อนโต้ตอบกับภายนอก
    • หากสำเร็จ ก็ settle reservation แล้วดำเนินธุรกรรมต่อ
    • หากล้มเหลว ก็ release กลับไปเป็น available balance
  • แพตเทิร์นนี้แยก total balance กับ available balance ออกจากกัน
    • available = total - reserved
    • การตรวจสอบยอดเงินและการสร้าง reservation ใหม่ทำโดยอิง available balance
  • จำนวนเงินสุดท้ายอาจต่างจากการประเมินล่วงหน้า
    • หากค่าธรรมเนียมหรืออัตราแลกเปลี่ยนเปลี่ยนไป ให้สำรองจำนวนที่คาดไว้, settle จำนวนจริง แล้ว release ส่วนที่เหลือ
  • reservation ต้องถูก settle หรือ release เสมอ
    • orphan reservation จะล็อกเงินผู้ใช้ไว้ แต่ไม่ได้ทำให้เงินหายไปหรือสร้างเงินขึ้นมา
    • expiry หรือ timeout อาจเป็น safety net ได้ แต่ไม่จำเป็นเสมอไป
  • การตรวจสอบยอดเงินและการบันทึก reservation ต้องเป็น linearizable
    • ใน stale read ธุรกรรมสองรายการอาจผ่านการตรวจสอบทั้งคู่และมีเงินก้อนเดียวกันรองรับได้

Overdraft และ Idempotency

  • Overdraft คือสถานการณ์ที่ยอดเงินในบัญชีติดลบ
  • overdraft โดยเจตนาเป็นผลิตภัณฑ์สินเชื่อที่มีวงเงินและดอกเบี้ย และโดยทั่วไปจะโมเดลเป็นบัญชี overdraft แยกต่างหาก
  • overdraft ที่ไม่ได้ตั้งใจอาจเกิดขึ้นได้แม้นโยบายจะห้าม
    • settlement อาจเข้ามาด้วยจำนวนที่มากกว่าการประมาณที่สำรองไว้ หรือ reversal อาจเข้ามาหลังจากเงินถูกถอนออกไปแล้ว
    • funds reservation ลด window ได้ แต่กำจัดไม่ได้
  • “ห้าม” กับ “แสดงแทนไม่ได้” เป็นคนละเรื่องกัน
    • หากทำให้ไม่สามารถแสดงยอดติดลบได้ด้วย unsigned integer หรือ CHECK (balance >= 0) เมื่อในความเป็นจริงต้องยอมรับยอดติดลบ อาจนำไปสู่ crash, zero clamp หรือการประมวลผลผิดพลาด
    • หาก clamp ยอดติดลบเป็น 0 จะเป็นการสร้างเงินขึ้นมา
  • เมื่อตรวจพบ overdraft ให้มองเป็นสัญญาณสำหรับการสืบสวน และต้องกู้คืนหรือจัดการอย่างชัดเจนด้วยวิธีต่าง ๆ เช่น future deposit และ netting, การขอ repayment, หรือ write-off ไปยังบัญชี expense/loss
  • ในระบบกระจายไม่สามารถรับประกัน exactly-once delivery ได้ จึงต้องมีการ retry และการ retry อาจทำให้เกิดการส่งซ้ำ
  • Idempotency คือคุณสมบัติที่แม้ข้อความเดียวกันจะถูกส่งสองครั้ง การประมวลผลก็มีผลเพียงครั้งเดียว
    • idempotency key ที่ระบุชัดเจนมักง่ายและปลอดภัยกว่า payload-based deduplication
    • key ควรถูกจำกัดไว้ในขอบเขตของ operation และ client เฉพาะ
    • ต้องตัดสินใจว่าจะ replay ข้อผิดพลาดหรือประมวลผลใหม่ และสำหรับข้อผิดพลาดถาวร โดยทั่วไปการ replay ตามเดิมจะง่ายกว่า
    • การตรวจสอบว่า payload ของการเรียกซ้ำเหมือนกับต้นฉบับเป็นแนวปฏิบัติที่ดี แต่มีต้นทุนด้านความซับซ้อนของการใช้งานและความยืดหยุ่น
    • ในสเกลใหญ่ ต้องมี atomic barrier สำหรับ deduplication ของคำขอระดับหลายหมื่นล้านและการเข้าถึงพร้อมกัน
    • idempotency window เช่น 24 ชั่วโมงทำให้การใช้งานง่ายขึ้น แต่มีต้นทุนด้าน correctness สูง
    • ต้องมีการทดสอบ retry และการจัดการ out-of-order retry ด้วย

Flow ที่เริ่มใหม่ต่อได้

  • ต้องสมมติว่ากระแสเงินมีหลายขั้นตอน และอาจตายได้ทุกจุดระหว่างขั้นตอน
  • Full resumability คือการออกแบบที่ทำให้ flow ที่เสร็จไปครึ่งหนึ่งอยู่ในสถานะที่กู้คืนได้เสมอ
  • สถานะความคืบหน้าต้องเก็บไว้ใน persistent storage ไม่ใช่ในหน่วยความจำ
    • โมเดล flow เป็น state machine ที่ชัดเจน และต้อง commit การจบแต่ละขั้นตอนก่อนเริ่มขั้นตอนถัดไป
  • ต้องมี driver อิสระที่ดัน flow ที่หยุดค้างให้เดินหน้าต่อ
    • scheduler, worker, poller ต้องจัดการ incomplete flow ได้แม้ orchestrator จะ crash ไปแล้ว
  • เมื่อ resume อาจต้องรันขั้นตอนที่เกิดขึ้นไปแล้วบางส่วนอีกครั้ง ดังนั้นแต่ละขั้นตอนต้องเป็น idempotent
  • ผลกระทบภายนอก rollback ไม่ได้
    • หลังจากเรียกโลกภายนอกแล้ว ไม่สามารถย้อนกลับไปเป็นสถานะที่ยังไม่ได้เรียกได้
    • ต้อง roll forward จนเสร็จ หรือหากขั้นตอนถัดไปล้มเหลวถาวร ต้อง posting compensating action แบบ saga pattern
  • สามารถใช้ durable-execution engine เช่น Temporal, Camunda, Workflows4s, AWS Step Functions หรือสร้าง persistent state machine เองก็ได้

การใช้ API ภายนอก

  • API ภายนอก เช่น ผู้ให้บริการชำระเงิน, custodian, blockchain node, ผู้ให้บริการ KYC เป็นสิ่งที่เราไม่สามารถควบคุมโค้ด คุณภาพ และ uptime ได้ จึงต้องจัดการแบบเชิงป้องกัน
  • ห้ามเชื่อถือ schema
    • อาจเกิดฟิลด์หายไป, type เปลี่ยน, หรือมี null ที่ไม่คาดคิด
    • ส่วนสำคัญควรตรวจสอบความถูกต้องที่ขอบเขต และหากพบข้อมูลที่ไม่คาดคิดควร fail อย่างชัดเจน
    • หากตรวจสอบความถูกต้องไปถึงส่วนที่ไม่จำเป็น อาจทำให้เกิด incident ที่ไม่จำเป็นเพราะบุคคลที่สามละเมิด contract
  • ใน API ภายนอก สิ่งอย่างการส่ง token ใน URL, การสูญเสีย precision, HTTP code ที่ไม่ตรงกับความหมาย, error body ภายใน 200, pagination ที่ไม่สม่ำเสมอ, custom date format เกิดขึ้นได้อย่างเพียงพอ
  • ทุก call อาจล้มเหลวได้ จึงต้องมี timeout และ retry
  • Circuit breaker ส่วนใหญ่เป็น courtesy ต่อเซิร์ฟเวอร์ที่ overload และเพิ่มความซับซ้อนฝั่ง client
    • อย่างไรก็ตาม อาจจำเป็นเมื่อต้องปกป้องทรัพยากรที่มีจำกัด เช่น latency, thread, connection
  • สำหรับ rate limit และ quota ควรคำนวณล่วงหน้าเพื่อเทียบปริมาณ call ที่คาดไว้กับ limit ของ provider
  • หากบันทึก request และ response ทั้งหมดในรูปแบบที่มีโครงสร้างและ query ได้ จะใช้เป็นข้อมูลสำหรับการสอบสวน, audit trail, หลักฐานในข้อพิพาทเรื่องพฤติกรรมของ provider, และวัสดุสำหรับประมวลผลซ้ำหลัง bug
  • ในพื้นที่สำคัญ อาจพิจารณา provider redundancy ได้
    • มีวิธีอย่างการตรวจสอบข้อมูลด้วย blockchain node สองตัว, backup bank partner, crypto custodian, ผู้ให้บริการ KYC
    • ต้นทุนด้านการพัฒนา ค่าธรรมเนียม และความซับซ้อนสูงมาก
  • sandbox อาจแตกต่างจาก production มาก จึงควรเตรียม production test ด้วย canary release หรือ controlled usage ที่มีผลกระทบน้อย

การประมวลผลเว็บฮุก

  • เว็บฮุกเป็นวิธีทั่วไปในการรับสัญญาณจากระบบภายนอก แต่การประมวลผลอย่างปลอดภัยไม่ใช่เรื่องง่าย
  • ห้ามสมมติลำดับ
    • ข้อความอาจมาถึงแบบ out-of-order หรือมี stale data
    • ห้ามมองว่าเว็บฮุกที่เพิ่งได้รับคือความจริงล่าสุดแล้วเขียนทับสถานะ
  • ห้ามสมมติความถูกต้อง
    • เว็บฮุกมาจาก subsystem อื่นของ issuer และอาจมีข้อมูลที่ stale หรือถูกแปลงผิด
    • ควรใช้ body ของเว็บฮุกเป็นเพียง trigger แล้ว query API เพื่อตรวจสอบ authoritative state
    • API เองก็อาจเป็น eventually consistent ได้ หาก query ทันทีอาจคืนสถานะก่อนหน้า จึงต้อง retry
  • ห้ามสมมติว่าจะส่งถึง
    • แม้ issuer จะสัญญา redelivery policy ที่แข็งแรง เว็บฮุกก็จะสูญหายในสักวันหนึ่ง
    • ต้องมี process อิสระอย่าง reconciliation มาช่วยเติมเต็มความสมบูรณ์ของข้อมูล
  • ห้ามสมมติว่าจะส่งเพียงครั้งเดียว
    • เว็บฮุกเดียวกันจะถูกส่งหลายครั้ง และการประมวลผลต้องเป็นแบบ idempotent
  • ต้อง acknowledge อย่างรวดเร็วและประมวลผลแบบ asynchronous
    • บันทึก raw event ลง durable store แล้วคืน 2xx ทันที ส่วนงานจริงทำแบบ asynchronous
  • ต้องบันทึก raw payload ไว้ตามเดิม
    • จำเป็นต่อการประมวลผลที่เสถียร, audit trail, และการประมวลผลซ้ำหลัง bug
  • ต้องตรวจสอบผู้เรียก
    • โดยทั่วไป issuer จะใส่ payload signature มา และ receiver ตรวจสอบด้วย HMAC จาก shared secret หรือ asymmetric signature แบบใช้ public key
    • การตรวจสอบลายเซ็นต้องทำบน raw bytes ที่ได้รับ ไม่ใช่ payload ที่ serialize ใหม่
  • เว็บฮุกควรถูกมองเป็น hint ว่ามีบางอย่างเกิดขึ้น ไม่ใช่ความจริงว่าเกิดอะไรขึ้น

การแจ้งเตือนที่เชื่อถือได้: Outbox และ CDC

  • เมื่อต้องแจ้งการเปลี่ยนแปลงของระบบไปยังช่องทางภายนอกอย่าง Kafka event, webhook call อย่างน่าเชื่อถือ transactionality จะกลายเป็นปัญหา
  • อาจเกิดสถานการณ์ที่ publish สำเร็จแต่ไม่ได้รับ response เพราะปัญหาเครือข่ายจึง rollback สถานะระบบ หรือ state change ถูก commit แล้วแต่ publish ล้มเหลว
  • คำตอบตาม textbook คือ 2-phase commit หรือ distributed transaction แต่ไม่ค่อยถูกใช้เพราะความซับซ้อนและความยากในการทำให้เป็นมาตรฐานที่ reuse ได้
  • ตัวเลือกเชิงปฏิบัติมีหลายแบบ
    • Outbox pattern: บันทึก publishing intent ลง store เฉพาะแบบ transactionally พร้อมกับการเปลี่ยนสถานะ แล้วค่อยประมวลผลจนกว่าจะสำเร็จ
    • Change Data Capture: อ่าน database write-ahead หรือ replication log แล้วเปลี่ยนการเปลี่ยนแปลงที่ commit แล้วให้เป็น event stream
    • Debezium และ AWS DMS ให้บริการ CDC
    • CDC ส่ง raw event ในรูปแบบ table row ออกมา จึงต้องมี postprocessing หากต้องการหลีกเลี่ยงการรั่วไหลของ internal schema
    • Listen-to-yourself คือ publish event ก่อน แล้วสร้างสถานะของตัวเองขึ้นใหม่จาก event นั้น
    • Event sourcing มี event log อยู่ใน DB แล้ว จึง publish จากตรงนั้นได้
  • ไม่ว่าจะเลือกกลไกใด delivery จะเป็นแบบ at-least-once
    • หาก relay หรือ connector crash หลัง publish แต่ก่อนบันทึก เมื่อ restart อาจส่งซ้ำได้
    • consumer ต้อง deduplicate ด้วย stable event id และทำงานแบบ idempotent

Reconciliation

  • ระบบที่พึ่งพาข้อมูลภายนอกเปราะบางต่อ data drift ซึ่งเป็นการที่สถานะของสองระบบไม่ตรงกัน
    • อาจพลาดเว็บฮุก หรือ transaction ถูก posting ใน ledger แล้วแต่ยังไม่สะท้อนในระบบ external provider
  • Reconciliation คือ process ที่ทำให้สองระบบตรงกัน
    • ในความเป็นจริง อาจมีตั้งแต่สามระบบขึ้นไป เช่น ledger, payment processor, bank
  • cadence อาจเป็น hourly, daily, monthly, yearly ตามบริบทและข้อจำกัด
  • drift อาจเป็น missing data หรืออาจเป็นความต่างที่ซับซ้อนกว่า เช่น จำนวนเงินของ transaction เดียวกันไม่ตรงกัน
  • timing ก็สำคัญ
    • หาก settlement เป็น T+3 record อาจอยู่ในสถานะ unreconciled เป็นเวลา 3 วัน ดังนั้นต้องสะท้อนเรื่องนี้ใน process เพื่อป้องกัน alert ที่ไม่จำเป็น
  • matching algorithm คือความยากหลัก
    • โดยปกติ หากบันทึก external provider id ไว้ภายใน matching จะง่ายขึ้น
    • หากไม่มี อาจต้องใช้ heuristic จาก amount และ time
  • จำเป็นต้องมี one-to-many reconciliation ด้วย
    • settlement transfer หนึ่งรายการอาจครอบคลุม transaction หลายรายการ
  • ห้ามแก้ discrepancy ด้วยการ overwrite ง่าย ๆ ให้ตรงกับ reconciliation
    • ต้องมีการรองรับระดับ first-class เช่น correction record, webhook data reprocessing เพื่อทำความเข้าใจและแก้ไขสาเหตุ

การควบคุมและการเข้าถึง

  • ระบบเงินต้องควบคุมไม่เพียงข้อมูล แต่รวมถึงว่าใครทำ action ใดได้ และต้องพิสูจน์การปฏิบัติตามขั้นตอนย้อนหลังได้ด้วย
  • Segregation of duties คือการควบคุมที่ป้องกันไม่ให้คนคนเดียวเป็นเจ้าของ process ทั้งหมด
  • Four-eyes, maker-checker, dual control คือวิธีที่ต้องมีคนที่สองอนุมัติก่อนที่ action บางอย่างจะถูกนำไปใช้
  • สิ่งที่ควรใช้กับการควบคุมนี้คือ action ที่ย้ายเงินหรือทำให้แสดงผลผิดได้ เช่น withdrawal ขนาดใหญ่หรือแบบ manual, manual ledger correction, การย้าย treasury และ cold-wallet, การเปลี่ยน fee schedule หรือ limit
  • การควบคุมแบบเดียวกันใช้กับงานวิศวกรรมด้วย
    • code merge, production deploy, infrastructure change เป็น action ที่อ่อนไหวในระบบเงิน จึงต้องมี review และ approval
  • approval เองก็เป็นส่วนหนึ่งของ trail
    • ต้องบันทึกว่าใคร request ใคร approve และทั้งสองคนแตกต่างกันหรือไม่ จึงจะพิสูจน์ control ได้
  • สำหรับ emergency ต้องมีเส้นทาง break-glass ที่ชัดเจนและถูก audit อย่างเข้มงวด
  • Access control เป็นส่วนหนึ่งของสถานะระบบและเปลี่ยนแปลงไปตามเวลา
    • ทั้ง human และ service ควรได้รับสิทธิ์เท่าที่จำเป็นขั้นต่ำ
    • หากเลือกใช้ RBAC แทน per-person grant การ review จะง่ายขึ้น
    • capability grant และ revoke ก็เป็น event ที่อ่อนไหว จึงต้องบันทึกว่าอะไร ใคร และทำไมจึงเปลี่ยน
    • ต้องมี scheduled access review เพื่อตรวจจับ permission drift ที่เก่าเกินไปหรือไม่ถูกต้อง

การติดตามการเปลี่ยนแปลงใน SDLC

  • ในสภาพแวดล้อมที่อยู่ภายใต้กฎระเบียบ ต้องสามารถ audit กระบวนการที่โค้ดไปถึง production ได้
  • source control คือบันทึกประวัติการเปลี่ยนแปลง
    • commit history ระบุให้ทุกการเปลี่ยนแปลงผูกกับ author และเชื่อมโยงเหตุผลผ่าน review กับ linked ticket
    • ต้องปกป้องด้วย signed commit, protected branch และห้าม force-push บน shared history
  • ต้องบังคับใช้ review และ pipeline
    • required review, status check และการห้าม push ตรงเข้า main branch เป็นสิ่งสำคัญ
  • deployment ต้องติดตามย้อนกลับได้
    • ต้องสามารถสร้างภาพย้อนหลังได้ว่า version ใดกำลังรันอยู่ ใคร release เมื่อใด เพื่อเชื่อม incident กับการเปลี่ยนแปลงที่เป็นสาเหตุได้

กลยุทธ์การทดสอบ

  • ในระบบเงิน พื้นที่ของ operation sequence มีขนาดใหญ่ และ failure ที่น่าสนใจมักเกิดจากการผสมผสานของหลายอย่าง ดังนั้นการทดสอบจึงสำคัญเป็นพิเศษ
  • Property-based testing ตรวจสอบ property ที่ต้องเป็นจริงสำหรับทุก input มากกว่าตรวจสอบ output เฉพาะกรณี
    • เหมาะกับ invariant และ money math
  • เมื่อสร้าง operation sequence ต้องตรวจสอบ invariant ไม่ใช่แค่ตอนสุดท้าย แต่หลังทุกขั้นตอนด้วย
    • เนื่องจากทำด้วยมือในสเกลใหญ่ได้ยาก จึงต้องมี testing harness ที่ inject assertion อัตโนมัติ
  • Generative idempotency testing ตรวจสอบว่า operation ทุกตัวที่แตะโลกภายนอกไม่มีผลกระทบเมื่อถูกเรียกครั้งที่สอง
  • Crash and resume injection ตรวจสอบว่า long flow สามารถฟื้นกลับมาได้แม้ตายในระหว่างขั้นตอนใด ๆ
  • Round-trip testing ตรวจสอบว่าหลัง encode/decode, serialize/deserialize, convert/convert back แล้วกลับมาที่จุดเริ่มต้นหรืออยู่ภายใน tolerance ที่ทราบหรือไม่
    • เป็นวิธีที่รวดเร็วในการจับ precision loss ที่ boundary ของ money และ currency type รวมถึง serialization bug
  • Golden testing เปรียบเทียบผลการคำนวณ เช่น fee breakdown, statement, report กับผลลัพธ์ที่คาดหวังซึ่งบันทึกไว้ เพื่อเผย diff ที่ไม่ได้ตั้งใจ
  • Backward-compatibility testing รักษา corpus ของ payload รูปแบบจริงเก่า ๆ ไว้ และตรวจสอบว่าโค้ดปัจจุบันยัง deserialize และ project ได้อย่างถูกต้อง
  • Production testing อาจจำเป็นเมื่อ sandbox แตกต่างจาก production อย่างมาก
    • ตัวอย่างเช่น canary release, controlled rollout ที่มี blast radius เล็ก และ synthetic transaction ที่ปล่อยเงินจริงจำนวนเล็ก ๆ ให้ไหลอย่างต่อเนื่อง
    • production test เคลื่อนเงินจริง ดังนั้นต้องผ่าน ledger, reconciliation และ audit trail แบบเดียวกัน และต้องเคลียร์ผ่านเส้นทาง correction/reversal ปกติ

คำศัพท์โดเมนและแหล่งอ้างอิง

  • ในการเริ่มต้นกับฟินเทค vocabulary และ concept อาจยากกว่าโค้ด จึงแยกสรุปคำศัพท์สำคัญไว้ต่างหาก
  • ด้านบัญชีและ ledger รวมถึง ledger, general ledger และ sub-ledger, debit/credit, posting, chart of accounts, receivable/payable, IOU, accrual vs cash basis, trial balance, suspense/clearing account, write-off, commingling, reconciliation break
  • ด้าน Money และ FX รวมถึง Money type, minor units, basis point, notional, fiat vs crypto, stablecoin, pegged/wrapped/bridged, bid/ask/spread, mid-market rate, reference rate, mark-to-market
  • ด้านธุรกรรมและ settlement รวมถึง value date, booking date, settlement date, T+X, clearing vs settlement, cut-off time, float, netting, backdating, reversal/correction
  • มีการสรุปคำศัพท์ด้าน payment, card, market, crypto และ compliance แยกไว้ด้วย
    • PSP, omnibus account, FBO account, chargeback, issuer/acquirer, authorization vs capture
    • order book, market vs limit order, maker/taker, slippage, liquidity, derivative, futures, perpetual, liquidation
    • custody, hot/cold wallet, private key, multisig, MPC, gas, confirmation/finality, reorg, UTXO vs account model
    • KYC, AML/CFT, sanctions screening, PEP, SoF/SoW, Travel Rule, VASP, MiCA, least privilege, RBAC, audit trail
  • แหล่งอ้างอิงแบ่งเป็นบัญชีและ ledger, payments และ cards, markets และ trading, crypto, engineering, KYC และ AML

ตัวอย่าง end-to-end สามแบบ

  • การถอนคริปโต

    • เป็นโฟลว์ที่ผู้ใช้ถอน 0.5 ETH ไปยังที่อยู่ภายนอก
    • คำขอมี idempotency key เพื่อให้การส่งซ้ำสร้าง withdrawal เพียงรายการเดียว
    • จอง 0.5 ETH และ network fee ที่คาดไว้จาก available balance
    • compliance gate ตรวจสอบ sanctions, AML และ destination address และอาจ sleep เป็นเวลาหลายวันเนื่องจากการเรียกภายนอกและการ review ด้วยคน
    • การ broadcast บนเชนต้องเป็น idempotent และหลัง crash ต้องกลับไปตรวจสอบ chain อีกครั้ง ไม่ใช่ broadcast รอบที่สอง
    • หลังมี confirmation เพียงพอแล้ว ให้ posting รายการใน ledger ได้แก่ user account debit, external on-chain account credit, network fee expense และ service fee revenue
    • nightly job ทำการ reconcile ระหว่าง ledger กับความเป็นจริงบน chain
  • การฝากผ่านบัตร

    • เป็นโฟลว์การเติมเงินผ่านบัตรโดยใช้ PSP
    • ผู้ใช้ส่งจำนวนเงินและข้อมูลบัตร แล้วเปิด deposit transaction กับ PSP ด้วย idempotency key
    • authorization สร้างเพียง hold เท่านั้น และเนื่องจากเงินยังไม่เป็นของบริษัท จึงไม่ credit เข้า user balance
    • เว็บฮุก captured จะตรวจสอบ signature ของ raw bytes, เก็บ raw payload, ตอบกลับ 2xx อย่างรวดเร็ว แล้วประมวลผลแบบ asynchronous
    • เว็บฮุกเป็นเพียง trigger ดังนั้นต้อง query authoritative state จาก PSP API
    • สถานะ captured but not settled จะ posting ผ่าน clearing account และ settlement อาจมาถึงเป็น batch หลัง T+X
    • chargeback จัดการด้วย linked compensating entry โดยไม่แก้ไขรายการต้นฉบับ
  • การแปลงในแอปพร้อม cashback

    • เป็นโฟลว์ที่เปลี่ยน 1,000 EUR เป็น USDC และให้ promotional cashback
    • quote EUR→USDC ไม่ใช่ส่วนกลับของ USDC→EUR แต่เป็น directional rate
    • EUR และ USDC ไม่ถูกนำมาบวกกัน และ USDC ระบุด้วย (network, contract address) ไม่ใช่สิ่งเดียวกับ pegged fiat
    • การคำนวณต้องรักษาความละเอียดเต็มรูปแบบ และปัดเศษเพียงครั้งเดียวที่ขอบเขตด้วยกลยุทธ์ที่ระบุชัดเจน
    • ต้อง booking spread อย่างชัดเจนเข้า revenue account และไม่ควรหายไปเป็น rounding residual
    • cashback ไม่ใช่การเพิ่ม balance ฟรี แต่เป็นเงินจริงที่ย้ายจาก company promotional/expense account ไปยัง user balance
    • การ publish ผลลัพธ์ต้องรับประกัน reliable delivery ด้วยกลไกอย่าง outbox, CDC หรือ event log และ downstream consumer จะ dedupe ด้วย stable event id

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

 
GN⁺ 4 시간 전
ความคิดเห็นบน Hacker News
  • ลองกวาดตาดูแล้ว ผมว่าแฮนด์บุ๊กนี้ค่อนข้างตื้น และในบางส่วนก็แทบจะเป็น คำแนะนำที่แย่
    เช่น ถ้าเห็นว่าเก็บจำนวนเงินในรูปแบบที่ไม่ใช่จำนวนเต็ม ผมคงกรีดร้องแล้ววิ่งหนี เพราะมีกรณีอย่าง decimal ของ Rust ที่ถูกแสดงเป็น floating point ใน JSON แบบนั้น เว้นแต่จะมีเหตุผลที่หนักแน่นมาก ควรเป็นจำนวนเต็มเสมอ ส่วน view ที่ export ออกไปจะเป็นรูปแบบเข้ารหัสบิตแปลก ๆ อะไรก็ได้
    อัตราแลกเปลี่ยนเงินตราต่างประเทศก็ไม่ใช่ปัญหาที่แก้ได้ด้วยอัตรา ณ เวลาใดเวลาหนึ่งเท่านั้น สิ่งต่าง ๆ อย่างอัตรา ณ เวลาฝั่งผู้ซื้อ, อัตรา ณ เวลาฝั่งผู้ขาย, การตกลงร่วมกัน, tolerance ของการตกลง, timestamp ยืนยันที่ตกลงกันไว้ ล้วนมีผล
    ด้วยเหตุผลเรื่อง immutability เลยทำให้อยากวาง event sourcing ไว้ทุกที่ที่เกี่ยวกับเงิน สตรีมที่สรุปสุดท้ายอาจดูเหมือน A -> B -> E แต่สตรีมจริงอาจเป็น A0 -> Edit(A0, A) -> B -> C -> D -> Rollback(B) -> E
    สุดท้ายแล้ว Fintech ก็ไม่ใช่ว่าเหมือนกันไปหมด บางที่ปฏิบัติกับเงินเหมือนสัมภาระก้อนหนึ่ง แต่อีกบางที่เงินคือศูนย์กลางของทุกอย่าง

    • ผมไม่แน่ใจว่าประเด็นจำนวนเต็ม/floating point นั้นหมายถึงอะไรกันแน่ ในบทความบอกชัดเจนในฐานะกฎจากประสบการณ์ว่า อย่าใช้ floating point อธิบายว่ามันแทบไม่เคยเป็นความคิดที่ดีและทำให้สูญเสียความแม่นยำแบบคาดเดาไม่ได้ และแนะนำชนิดข้อมูลจำนวนเต็มหรือ BigDecimal ในหลายจุด สงสัยว่าหมายถึงไปถึง rational number ด้วยหรือเปล่า
      เรื่อง FX ก็ดูเหมือนเป็นการเสริมคำพูดของแฮนด์บุ๊กที่ว่า “ไม่มี canonical rate” มากกว่า อีกอย่าง บทความกำลังพูดถึงการจัดการบันทึกหลังการยืนยัน ส่วนประเด็นนี้ผมคิดว่าน่าจะพูดถึงวิธีการยืนยัน เป็น nuance ที่ใช้ได้สำหรับเป้าหมายที่แยกกัน แต่ไม่ได้ดูเหมือนหลักฐานว่าบทความขาดหรือตรงไหนผิด
      ส่วน immutability ผมก็คิดว่าบทความพูดใจความเดียวกัน ไม่รู้ว่าต่างกันตรงไหน
    • นี่เป็นการขยายปัญหาเกินไปมาก หลายพื้นที่ในงานการเงินก็ใช้ double ได้ดี
      ถ้าคุณกำลังคำนวณราคา option ด้วย Monte Carlo บนเส้นทางอัตราดอกเบี้ย และสนใจตัวชี้วัดความเสี่ยงอย่าง duration, convexity, vega ก็ไม่มีใครสนใจกฎการปัดเศษว่าเป็นแบบไหน double ก็พอแล้ว จะบังคับ exp(-rt)cashflow หรือ cumulative distribution function แบบปกติให้เป็นจำนวนเต็มได้อย่างไร?
      มีบางพื้นที่ที่จำนวนเต็มถูกต้อง แต่ไม่ใช่หลักการสากล แค่เลือกแนวทางวิศวกรรมที่ถูกต้องก็พอ
    • ผมก็สะดุดกับส่วนนั้นเป็นอย่างแรก และทำให้สงสัยทั้งวิกินี้ วิธีที่ ถูกต้องเพียงวิธีเดียว ในการเก็บเงินก็คือจำนวนเต็ม[1] ตามที่บอก และควรทำให้ประเด็นนี้ชัดเจน
      ถ้าสภาพแวดล้อมที่ใช้รองรับ จะใช้ fixed-point ก็ได้ แต่ในเชิงเทคนิคแล้วมันก็ยังเป็นจำนวนเต็มอยู่ดี
    • คุณไม่สามารถทำทุกอย่างที่จำเป็นได้ด้วยจำนวนเต็มอย่างเดียว มีบางครั้งที่ต้องแสดงหรือคำนวณค่าที่เล็กกว่าเซนต์ และในบางที่ ส่วนใหญ่แล้วต้องใช้อะไรอย่าง BigDecimal ที่ปลอดภัยจากข้อผิดพลาดของ floating point
      สำหรับการแสดงผล การคืนค่า decimal ก็ปลอดภัย
    • JSON ไม่มี floating point มีแต่ตัวเลข หลังจาก parse แล้วจะนำไปใช้อย่างไรไม่ใช่ส่วนหนึ่งของสเปก
      จะวิ่งหนีจากระบบที่เก็บจำนวนเงินเป็นจำนวนเต็มก็ดี งั้นเราคงไม่ได้ทำงานบนระบบเดียวกัน ผมกลับรู้สึกบ่อยขึ้นทุกวันนี้ว่าอยากวิ่งหนีจาก ระบบที่จัดการจำนวนเงินเป็นจำนวนเต็ม ถ้าเป็น codebase อุดมคติที่มีแต่โปรแกรมเมอร์การเงินมากประสบการณ์แตะต้อง ก็อาจทำงานได้ดี แต่ระบบแบบนั้นมักเสี่ยงที่จะกลายเป็นระบบที่ปิดกั้นเกินไปหรือเปราะบาง
  • สำหรับคนที่กำลังพิจารณากลยุทธ์ minor-unit precision ในการแทนจำนวนเงิน ขอแนะนำว่าอย่าทำดีกว่า อย่างน้อยก็ไม่ควรใช้เป็นรูปแบบข้อมูลสำหรับแลกเปลี่ยน/API
    มันอาจดูฉลาด เช่น คำนวณจำนวนเต็มได้เร็ว ไม่มีปัญหาการปัดเศษจากการบวกและลบ แต่ทันทีที่ต้องทำงานกับพาร์ตเนอร์ที่สมมติจำนวนหลักทศนิยมของสกุลเงินหนึ่งไว้ไม่เหมือนกัน ก็อาจโดนกัดหนักมากได้ เรื่องนี้สำคัญเป็นพิเศษกับสเตเบิลคอยน์ เพราะบ่อยครั้งมันมีจำนวนหลักทศนิยมโดยนัยต่างจากเงินตรารัฐที่มันเป็นตัวแทน
    สำหรับ API ที่ใช้ JSON ก็น่าพิจารณาให้แทนจำนวนเงินด้วยชนิดสตริงด้วยเช่นกัน JSON ไม่ได้กำหนดความละเอียดของเลขฐานสิบไว้ ดังนั้นต้องตรวจสอบเสมอว่าทั้งตัวคุณเองและผู้ใช้/เวนเดอร์ทั้งหมดไม่ได้ให้ parser/serializer แปลงผ่าน floating point ภายในจนเสียความละเอียดไป เรื่องนี้เละเทะได้เร็ว และสตริงอาจดูไม่สะอาดในเชิงแนวคิดนัก แต่มันเลี่ยงปัญหานี้ได้ทั้งหมด บางคนอาจเรียกสิ่งนี้ว่า anti-pattern [1] แต่ผมไม่อยากสู้ศึกเพื่อความบริสุทธิ์ทางอุดมการณ์บนบ่าของผู้ใช้หรือผู้ถือหุ้น
    [1] https://blog.json-everything.net/posts/numbers-are-numbers-n...

    • ทางแก้ที่ถูกต้องจริง ๆ เพียงอย่างเดียวตรงนี้คือส่ง mantissa และ exponent เป็นจำนวนเต็มสองตัวแยกกัน การแปลงระหว่าง exponent ให้เข้ากับการคำนวณที่ต้องการเป็นเรื่องเล็กน้อย ทำให้แม่นยำได้เท่าที่ต้องการ และไม่กำกวม
      ในโลกการเทรดความถี่สูง ถ้ากำหนด exponent ให้คงที่ล่วงหน้าสำหรับ {slice} บางอย่างได้ ก็ประหยัดพื้นที่ส่งข้อมูลได้ เช่น ส่งเฉพาะ mantissa ภายในขอบเขตอย่างสินค้า/ขนาด tick/กลุ่มสินทรัพย์/ตลาดซื้อขาย/feed/server แล้วให้ไคลเอนต์ใช้ exponent ที่ hard-code ไว้
      แต่แม้ในโดเมนคล้าย ๆ กัน ก็มักคุ้มค่าที่จะส่ง exponent แบบ uint32 เพิ่มไปอีกตัวในข้อมูลที่ส่ง เพื่อให้เปลี่ยนได้ในภายหลัง และไม่ถูกการออกแบบช่วงแรกอย่าง “ตอนนี้ต้องการแค่เซนต์” มาผูกมัด เช่น อยู่ ๆ อาจต้องรองรับราคา bitcoin ด้วยความละเอียดเต็มรูปแบบ ผู้ใช้จะขอบคุณถ้าไม่ต้องประสานงาน breaking change ตอนอยากปรับ exponent แบบคงที่
    • จากมุมของคนที่เคยทำ การเทรดความถี่สูง/latency ต่ำ ด้วย C++ แล้วเอา front-end บริหารจัดการแบบเบราว์เซอร์ หรือก็คือ JavaScript มาต่อด้วย ใช้เซนต์เป็นจำนวนเต็มทั้งหมดไปเลยก็พอแล้ว มันแทบจะเป็นมาตรฐานอุตสาหกรรมและใช้งานได้ดี ตัวเลือกอื่นทั้งหมดเป็นการประนีประนอมที่แย่กว่า
    • ไม่เห็นว่ามันเป็นปัญหาตรงไหน เวลาโต้ตอบกับ API ฝั่งตรงข้ามก็แปลงค่าเอา
    • ถ้าบทความนั้นสรุปว่า “ต้องแก้ parser ให้สามารถดึงชนิดตัวเลขใด ๆ ความละเอียดใด ๆ ที่ต้องการจากตัวเลข JSON ได้” ผมเห็นด้วยเต็มที่ว่านั่นอย่างน้อยก็สุดโต่งและไม่สมจริง
      เกณฑ์คำว่า “ใด ๆ” นั้นไม่สมเหตุสมผล เป็นมาตรฐานที่บรรลุไม่ได้ ซึ่งอาจเรียกร้องงบวิศวกรรมแบบไม่จำกัดโดยไม่มีคุณค่าที่พิสูจน์ได้จริง
      การชี้ให้เห็นการขาดมาตรฐาน พูดถึงว่า parser จริง ๆ ทำงานอย่างไร และอภิปรายช่องว่างกับ use case ที่ยังไม่ถูกตอบสนองนั้นเป็นเรื่องดี การเสนอว่าจำเป็นต้องมีมาตรฐานที่สมเหตุสมผลกว่านี้ก็เป็นเรื่องดี แต่การเรียกร้องให้ทุกคนรองรับ “ความเป็นไปได้ทั้งหมด” ที่ไม่มีใครต้องการจริง ความหมายก็ไม่ชัด และในทางปฏิบัติก็ทำไม่ได้ ไม่ใช่ความคิดที่ดี
    • อยากรู้ว่าแทนที่จะทำแบบนั้น คุณแนะนำอะไร ใช้ floating point มาตรฐานอย่าง float/double, fixed-point arithmetic ที่ระดับหนึ่งในพันของ minor unit หรือเล็กกว่านั้น, เลขฐานสิบความละเอียดตามอำเภอใจ หรือวิธีที่ต่างไปเลย?
  • ในฐานะโปรแกรมเมอร์ พอเห็นโปรแกรมเมอร์ Fintech แต่ละคนพูดจากประสบการณ์และมุมมองที่ต่างกัน ก็ทำให้สงสัยว่าการเขียนโปรแกรมเก่งจริง ๆ คืออะไรกันแน่
    ที่ xlii บอกว่าอย่าเก็บจำนวนเงินเป็น floating point นั้นเป็นปัญหา IEEE 754 ที่พบบ่อย การติดตามทางการเงินควรทำด้วย log ที่เปลี่ยนแปลงไม่ได้หรือบันทึกแบบ event-based ก็จริง แต่ผมไม่คิดว่าจำเป็นต้องทำ service รอบข้างทั้งหมดให้เป็น event sourcing แค่ใช้กับ core logic อย่าง ledger, settlement, order, execution ก็น่าจะพอแล้ว อ่านของ xlii แล้วมันดูเหมือนเป็นเทคนิคที่จะทำได้ก็ต่อเมื่อ modeling สำเร็จเท่านั้น
    สิ่งที่ lxgr พูดชี้ไปที่ปัญหา minor-unit ถ้าเลข JSON ถูกภาษา หรือ parser แปลงเป็น floating point ก็อาจเสียความละเอียดได้ โดยทั่วไปจะส่งค่าพร้อมกับฟิลด์จำนวนตำแหน่งทศนิยมแยกต่างหาก แต่ได้ยินมาว่าในการเทรดความถี่สูง overhead นั้นแพงเกินไป จึงไม่ทำแบบนั้น
    คำพูดของ antonymoose สอดคล้องกับสิ่งที่หนังสือหลายเล่มพูด ดังนั้นในบริบทของ FX หรือ API การออกแบบแบบนี้จึงพบได้ทั่วไป มันให้ความรู้สึกเหมือนการออกแบบโปรโตคอลด้วย
    รวม ๆ แล้วทุกคนพูดถูกภายในโดเมนของตัวเอง ผมคิดว่าคงดีถ้ามีคนอย่าง xlii เป็น senior programmer แต่ในขณะเดียวกันก็รู้สึกว่าตัวเองคงออกแบบระบบซับซ้อนแบบนั้นไม่ได้ ในแง่นั้น คำพูดของแต่ละคนก็มีเหตุผล และการเห็นความเห็นแตกต่างกันตามโดเมนก็น่าสนใจ นี่แหละที่เรียกว่า ความเชี่ยวชาญ หรือเปล่า
    พอเห็นเรื่องแบบนี้ ก็พออนุมานได้คร่าว ๆ ว่าโปรแกรมเมอร์คนหนึ่งมาจากประสบการณ์แบบไหน บางครั้งการเขียนโปรแกรมให้ความรู้สึกไม่ใช่การหาคำตอบที่ถูกต้อง แต่เป็นการ เลือกโลกทัศน์
    การได้เห็นโปรแกรมเมอร์บน HN model โดเมนของตัวเองอย่างไรนั้นน่าสนใจเสมอ บางทีก็กดเข้าไปดูโปรไฟล์ แล้วเพิ่มความรู้โดเมนของพวกเขาลงในวิกิส่วนตัว เผื่อว่าสักวันอาจได้ใช้

    • อยากฟังเรื่องวิกินั้นเพิ่ม
  • ดี หนังสือเล่มนี้มีข้อมูลดี ๆ จำนวนมากที่หาได้จากที่อื่นอยู่แล้ว แต่แค่รวบรวมไว้ก็ถือว่าใช้งานได้จริงมาก ผมขอแนะนำ Designing Data-Intensive Applications ของ Kleppmann อย่างยิ่ง ฉบับพิมพ์ครั้งที่ 1 ก็ดีมาก และล่าสุดฉบับพิมพ์ครั้งที่ 2 ก็ออกมาแล้ว
    ผมเคยทำงานเป็น CTO ใน FinTech และสร้าง software stack ทั้งหมดตั้งแต่ศูนย์ บทเรียนในหนังสือโดยรวมถือว่าถูกต้อง ที่บอกว่าโดยรวมก็เพราะเช่นเคย ในโปรเจกต์เฉพาะต้องพิจารณา “แล้วแต่สถานการณ์” เยอะมาก เช่น ผมไม่ได้ใช้ event sourcing เพราะอยากเลี่ยงปัญหาการคำนวณสถานะทั้งหมด แค่ audit trail แบบ append-only มาตรฐานก็อาจเพียงพอแล้ว
    การส่งมอบ exactly once รับประกันไม่ได้ แต่สามารถประกอบให้เป็น การประมวลผล effectively once ได้ และในทางปฏิบัติสิ่งที่ต้องการก็เป็นอย่างหลัง
    คำแนะนำให้เก็บทุก request และ response นั้นถูกต้องอย่างยิ่ง ไม่ใช่แค่ตอน consume API เท่านั้น แต่รวมถึงตอนเก็บข้อมูลใด ๆ จากโลกภายนอกด้วย และถ้าเป็นไปได้ ควรบันทึกทุกขั้นตอนการแปลงระหว่างทางภายในขอบเขตของคุณด้วย การใช้ bucket แบบ content-addressed ร่วมกับตารางเชิงสัมพันธ์เป็นชุดที่ดี
    นอกจากนี้ เนื้อหาหลักไม่ได้พูดถึง data lineage เลย ถ้าเวนเดอร์อัปเดตข้อมูลบางอย่างตอนกลางวัน และคุณจำเป็นต้องรู้เรื่องนั้น คุณจะทำอย่างไร? คุณต้องอธิบายการเปลี่ยนแปลงนั้นได้ พร้อมกับให้การรันการคำนวณที่ใช้ค่าเก่าใหม่แล้วยังได้ผลลัพธ์เดิม ไม่ใช่ปัญหาที่ยากเป็นพิเศษ แต่ต้องคิดไว้

  • เรียกว่า “คำแนะนำที่แย่” นี่ยังถือว่าพูดสุภาพมากแล้ว พูดตรง ๆ “คู่มือ” นี้ส่วนใหญ่น่าจะเขียนโดย LLM
    ตัวอย่างเช่น ในส่วนเรื่องความไม่เปลี่ยนแปลงมีประโยคแบบนี้: “ถ้าแยก PII ออกจากข้อมูลทางการเงิน ก็จะเคารพสิทธิในการลบข้อมูลได้โดยไม่สูญเสียประวัติทางการเงินที่ต้องเก็บไว้”
    ในสถาบันการเงิน ทั้งสองอย่างต้องเดินไปด้วยกันด้วยเหตุผล KYC/AML ที่ชัดเจน
    หากลบชื่อลูกค้า ที่อยู่ ฯลฯ ทันทีตามคำขอก่อนที่ระยะเวลาที่เกี่ยวข้องจะหมดอายุ แล้วเหลือไว้แค่ข้อมูลทางการเงิน เมื่อหน่วยงานที่มีอำนาจตามกฎหมายมาขอข้อมูลเพื่อสืบสวนอาชญากรรม ทั้งองค์กรจะเจอกับวันที่เลวร้ายมาก
    คนที่อยากทำงานใน Fintech ไม่ควรพึ่ง “คู่มือ” สุ่ม ๆ ที่เขียนโดยใครก็ไม่รู้ในเขตอำนาจศาลไหนก็ไม่รู้
    คนที่ทำงานใน Fintech ควรทำงานตามคู่มือ/แนวทางภายในของนายจ้างเท่านั้น เอกสารแบบนั้นน่าจะถูกจัดทำร่วมกันโดยทนายของบริษัทและฝ่ายคอมพลายแอนซ์ เพื่อให้เป็นไปตามกฎหมายและข้อกำหนดการรายงานของเขตอำนาจศาลที่นายจ้างดำเนินงานอยู่

    • ไม่รู้ว่าบทความไปแนะนำตรงไหนให้ลบชื่อลูกค้าและที่อยู่ ฯลฯ ทันทีตามคำขอก่อนที่ระยะเวลาที่เกี่ยวข้องจะหมดอายุ
      เท่าที่ผมเห็น มันแนะนำให้แยก PII ที่ท้ายที่สุดต้องลบ ออกจากข้อมูลที่อยู่ในสมการบัญชี/เงื่อนไขคงสภาพ จึงเป็นข้อมูลที่ในทางปฏิบัติอยากเก็บไว้ถาวร เพื่อให้หลังพ้นช่วงเวลาการเก็บรักษาบันทึกที่เกี่ยวข้องแล้วจึงลบส่วนแรกได้
      ถูกต้องที่ไม่ควรพึ่ง “คู่มือ” ที่เขียนโดยใครก็ไม่รู้ในเขตอำนาจศาลไหนก็ไม่รู้ แต่ก็ไม่ควรเพิกเฉยต่อไอเดียและแนวปฏิบัติในนั้นแบบไม่ลืมหูลืมตา หรือไม่มองออกไปนอกองค์กรของตัวเองเลย อุดมคติคือดูมันแล้วนำมาเทียบกับความรู้ของตัวเองและกฎระเบียบท้องถิ่น แล้วปรับให้เหมาะสม
      ถ้าโลกนี้มีแต่องค์กรที่สมบูรณ์แบบไร้ข้อผิดพลาด แนวทางที่ทำตามเฉพาะแนวปฏิบัติภายในของนายจ้างก็ดูสมเหตุสมผล แต่ถ้าไม่มีบทสนทนาแบบนี้ เราจะไปถึงระดับนั้นได้อย่างไร?
  • ผมคิดว่าเนื้อหาส่วนใหญ่นี้ใช้ได้ไม่ใช่แค่กับ Fintech แต่กับ วิศวกรรมซอฟต์แวร์โดยรวม
    เช่น ส่วนที่พูดถึงการ retry, idempotency, ลำดับของอีเวนต์ ฯลฯ ใช้ได้กับทุกระบบที่ต้องการความถูกต้องในระดับหนึ่ง แม้จะไม่ได้เกี่ยวข้องกับเงินจริงโดยตรงก็ตาม ผมเห็นระบบจำนวนมากที่สร้างขึ้นบนสมมติฐานว่า “retry เมื่อไรก็ได้” แต่การ retry จะทำได้ก็ต่อเมื่อมันล้มเหลวอย่างสะอาดตั้งแต่แรก และระบบย่อยต้องให้ระดับ idempotency ตามที่ผมคิดไว้ เรื่องแบบนี้มักไม่ได้ถูกตรวจสอบจริง

    • เห็นด้วย ถ้าตัดเรื่องการประมวลผลบัญชีแยกประเภทกับการปัดเศษออกไป แทบไม่มีอะไรในนี้ที่ใช้กับ Fintech โดยเฉพาะ และแม้แต่ส่วนนั้นก็ยังค่อนข้างเบา
      ผมอยากอ่านบทความที่สนับสนุนแนวทางสุดโต่งกว่านี้ เช่น ฐานข้อมูลแยกตามบัญชี มากกว่า แบบที่มี trade-off เฉพาะตัวในโลก Fintech
      คำแนะนำที่อยากให้กับวิศวกรหรือผู้ก่อตั้งสาย Fintech มากที่สุดคือ ให้จริงจังกับความเสี่ยงและคอมพลายแอนซ์ตั้งแต่วันแรก
      ระบบการเงินตั้งอยู่บนความเชื่อมั่น ถ้าคุณลดความเสี่ยงแบบพิสูจน์ได้ไม่ได้ คุณก็จะเสียความเชื่อมั่น และสุดท้ายเสียทั้งธุรกิจ
  • คำบอกว่าให้หลีกเลี่ยง floating point นั้นไม่จริง ผมทำงานใน Fintech มา 20 ปี ส่วนใหญ่ใช้ double กัน Excel ก็ใช้ double, ฝั่ง frontend ก็จะใช้ double และฐานข้อมูลทุกตัวก็รองรับ double ไลบรารีมาตรฐานรู้วิธี parse double และ JSON แม้ตามทฤษฎีจะไม่ใช่ แต่ในทางปฏิบัติก็ใช้ double ระบบ ERP จำนวนมากก็ใช้ double
    ประเด็นสำคัญเมื่อใช้ double จัดการสกุลเงินคือ ต้องจำไว้ว่ามันเก็บความแม่นยำได้ทั้งหมด 15 หลัก ตราบใดที่ตัวเลขไม่ได้ใช้จำนวนหลักมากกว่านั้น เช่น 123456789.01 หรือ 123.456789 คุณก็สามารถมีความแม่นยำแบบทศนิยมสมบูรณ์ในการคำนวณทางการเงินได้ แค่ปัดเศษผลลัพธ์ให้กลับมาอยู่ในความแม่นยำ 15 หลักหลังการคำนวณแต่ละครั้ง และก่อนการเปรียบเทียบแต่ละครั้งเสมอ Excel ก็ทำแบบนั้น
    ข้อดีใหญ่ที่สุดของ double คือรองรับกันแพร่หลาย และสามารถผสมความแม่นยำต่าง ๆ ภายในระบบเดียวกันได้ หากทำงานกับการเงินระหว่างประเทศหรือผลิตภัณฑ์การเงินขั้นสูง เรื่องแบบนี้เกิดขึ้นได้ บัญชีบางแบบต้องการความแม่นยำถึงหนึ่งในพัน บางแบบต้องปัดเป็นทวีคูณของ 0.25 สุดท้ายคุณก็คงไม่ใช้เลขคณิตพื้นฐาน แต่ใช้ไลบรารีคณิตศาสตร์บัญชีเฉพาะทาง และไลบรารีนั้นก็สามารถใช้ floating point เป็น backend ได้อย่างสมบูรณ์แบบ

  • การตรวจสอบยอดคงเหลือของ Plaid ไม่ได้เป็นหลักประกันว่า ACH debit ที่กำลังจะส่งจะสำเร็จ
    ไม่ว่ายอดคงเหลือจะมีเป็นล้านดอลลาร์ก็ตาม ก่อน ACH จะถูกประมวลผล เงินทั้งหมดอาจ (a) ถูกโอนออกด้วย wire transfer, (b) ถูกหักชำระโดย ACH ของเมื่อวาน เช่น บิล การตัดบัญชีอัตโนมัติ ฯลฯ และเช็ค หรือ (c) ถูกใช้ผ่านบัตรเดบิต/ATM ได้
    ผมคงไม่ควรพูดว่ารู้ได้อย่างไรว่าทำไม Fintech บางรายถึงไม่จัดการเรื่องนี้

    • เป็นสัญญาณที่ดี แต่ไม่ใช่หลักประกันเด็ดขาด ผมเคยต้องอธิบายเรื่องนี้ให้ project manager ที่ไม่เข้าใจแนวคิดนี้ฟัง
  • แค่ส่วนเรื่อง idempotency key ก็คุ้มค่าแก่การอ่านแล้ว นักพัฒนาส่วนใหญ่ได้บทเรียนนี้มาอย่างเจ็บปวด

    • คงดีถ้าวงการการเงินเองรู้เรื่องนี้ตอนที่ระบบ core banking และโปรโตคอลสื่อสารทางการเงินหลัก ๆ จากยุค 60–70 ที่ยังใช้กันอยู่ถูกสร้างขึ้น
      หลายอย่างในนั้นมีมาก่อนที่ความรู้เรื่อง idempotency จะแพร่หลาย จึงมักต้องฝืนสร้าง idempotency key โดยนำฟิลด์หลายตัวที่ดูเหมือนจะ unique ระดับ global มาต่อกัน ปัญหาคือมันไม่เคย unique อย่างแท้จริง บางครั้งก็ได้เห็นสิ่งที่อยู่หลังม่าน เช่น ตอนที่ธนาคารไม่ยอมให้โอนเงินจำนวนเดียวกันในวันเดียวกันไปยังบัญชีผู้รับเดียวกัน
    • เห็นด้วย 100% และควรลงรายละเอียดมากกว่านี้ด้วย
      ผมใช้เวลามากในการอธิบายว่า idempotency ควรทำงานอย่างไร และทำไมจึงสำคัญ ทีมส่วนใหญ่เข้าใจว่าจำเป็น แต่แทบไม่มีทีมไหนคิดเรื่องนี้ไว้ตั้งแต่ต้น
    • audit trail ก็เช่นกัน audit trail ที่ดีช่วยบริษัทและตัวคุณเองได้ในยามฉุกเฉิน มีประโยชน์ต่อการดีบักด้วย และใช้เป็นแหล่งข้อมูลคอมพลายแอนซ์สุดท้ายได้ด้วย
  • “Fintech” กว้างมาก และสิ่งที่ถูกเรียกว่า Fintech ส่วนใหญ่จริง ๆ แล้วคือ การสื่อสาร เป็นการสื่อสารระหว่างบริษัท ระหว่าง trader ระหว่างระบบ และระหว่างบัญชีแยกประเภท ไม่มีวิธีเขียนโปรแกรมที่ “ถูกต้อง” ซึ่งใช้ได้ทั้งอุตสาหกรรม เพราะสุดท้ายแล้ว วิธีที่ถูกต้องคือวิธีที่อีกฝ่ายที่ผมสื่อสารด้วยเข้าใจได้
    ถ้าอีกฝ่ายติดตามสกุลเงินเป็นหน่วยเซนต์ การติดตามด้วยความแม่นยำสูงกว่านั้นจะทำให้เกิดความคลาดเคลื่อนจากการปัดเศษ ในทางกลับกัน ถ้าผมใช้หน่วยเซนต์ แต่อีกฝ่ายใช้ 0.1 เซนต์ ก็เหมือนกัน คำแนะนำอื่น ๆ ทั้งหมดในเอกสารนี้ก็ควรมองในทำนองนั้น