หลักการทางวิศวกรรมสำหรับการสร้างระบบการเงิน
(substack.wasteman.codes)- การบัญชีแทบไม่ได้เปลี่ยนแปลงมากนักตลอดหลายร้อยปีที่ผ่านมา
- ถึงอย่างนั้น ก็ยังมีความสับสนมากเกี่ยวกับวิธีที่ถูกต้องในการสร้างซอฟต์แวร์สำหรับระบบการเงิน
- บทความนี้จะแชร์บทเรียนจากประสบการณ์การสร้างระบบการเงินในองค์กรขนาดใหญ่
- แม้จะเน้นที่การสร้างระบบบัญชีเป็นหลัก แต่หลักการเหล่านี้ก็ใช้ได้กับระบบการเงินโดยทั่วไปเช่นกัน
คำจำกัดความของคำศัพท์ทางการเงินพื้นฐาน
- บัญชีแยกประเภททั่วไป (General Ledger, GL): บันทึกทางบัญชีหลักของบริษัทที่สรุปรายการธุรกรรมทางการเงินทั้งหมดในช่วงเวลาที่กำหนด โดยอาจมองได้ว่าเป็นผลรวมจากบัญชีแยกประเภทย่อยที่เกี่ยวข้อง
- บัญชีแยกประเภทย่อย (Sub-ledger): มีรายละเอียดของธุรกรรมแต่ละรายการทั้งหมดที่เกี่ยวข้องกับ GL ใด GL หนึ่ง ระเบียนในบัญชีแยกประเภทย่อยจะมีข้อมูลที่ละเอียดกว่าบัญชีแยกประเภททั่วไปมาก (เช่น ลูกค้าเฉพาะราย รายการสินค้าเฉพาะในคำสั่งซื้อ ฯลฯ) ความแตกต่างของข้อมูลระหว่างบัญชีแยกประเภทย่อยกับ GL จะแตกต่างกันไปตามประเภทธุรกิจและปริมาณข้อมูลที่จัดการ ธุรกิจขนาดเล็กบางแห่งอาจดำเนินงานได้โดยไม่ต้องมีบัญชีแยกประเภทย่อย แต่ถ้าขนาดเล็กมากก็มีโอกาสน้อยที่จะต้องใช้ซอฟต์แวร์แบบกำหนดเอง
- บันทึกทางการเงิน (Financial Record): คำเรียกรวมของบัญชีแยกประเภททั่วไปและบัญชีแยกประเภทย่อย
- สาระสำคัญ (Material): บ่งชี้ว่าความคลาดเคลื่อนของข้อมูลในงบการเงินส่งผลต่อการตัดสินใจของผู้มีส่วนได้ส่วนเสียที่มีเหตุผลหรือไม่ คำจำกัดความนี้จงใจให้ค่อนข้างกำกวม เพราะแต่ละบริษัทมีเกณฑ์สาระสำคัญต่างกัน ตัวอย่างเช่น สิ่งที่สำคัญสำหรับบริษัทที่มีรายได้ต่อปี 250,000 ดอลลาร์ อาจไม่สำคัญสำหรับบริษัทที่มีรายได้ต่อปี 1 พันล้านดอลลาร์ จากมุมมองด้านการออกแบบ คุณค่าหลักของแนวคิดนี้คือการใช้จัดหมวดหมู่ข้อมูลทางการเงินประเภทต่าง ๆ
High Level Data Flow
Business System --(Financial Events)--> Sub Ledger(s) --(Summarized Accounting Entries)--> General Ledger
เป้าหมายหลัก 3 ประการของระบบบัญชี
- ความถูกต้อง (Accurate): บันทึกทางการเงินต้องสะท้อนสถานะที่ทราบของธุรกิจ
- ตัวอย่าง: หากขายสินค้า 10 ชิ้น ราคาชิ้นละ $9.99 ยอดรวมในบันทึกทางการเงินนั้นต้องเป็น $99.90
- เรื่องนี้อาจดูชัดเจน แต่เมื่อมีการรวมธุรกรรมหลายพันหรือหลายล้านรายการ ความคลาดเคลื่อนร้ายแรงอาจเกิดขึ้นได้จากการบวกข้ามระบบแบบง่าย ๆ หรือจากข้อผิดพลาดในการปัดเศษ
-
บันทึกจาก Wasteman
- คนมักพูดว่าการตั้งชื่อเป็นปัญหาที่ยากที่สุดในวิทยาการคอมพิวเตอร์ แต่ผมคิดว่าการบวกเลขยากเป็นอันดับถัดมา
- ช่วงหลายปีที่ทำงานกับระบบการเงินขนาดใหญ่ ผมเห็นมานับครั้งไม่ถ้วนว่า bug เล็กมากสามารถทำให้ข้อมูลต่างกันอย่างมหาศาลได้
- อย่าเริ่มพูดถึงการหาผลรวมด้วย float เลย ต้องใช้จำนวนเต็มเสมอ ผมเรียนรู้บทเรียนนั้นมาอย่างยากลำบาก
- บันทึกทางการเงินต้อง ครบถ้วน (complete)
- พูดให้ชัดเจนขึ้นคือ บัญชีแยกประเภทย่อยและบัญชีแยกประเภททั่วไปต้องแสดงกิจกรรมทางธุรกิจทั้งหมดที่เกิดขึ้น ณ ช่วงเวลาหนึ่งอย่างครบถ้วน
- หากมีเหตุการณ์ที่เกิดขึ้นแล้วแต่ไม่มีอยู่ในบันทึกทางการเงิน แสดงว่าระบบไม่ครบถ้วน
- นี่ไม่ได้หมายความว่าไม่อนุญาตให้มี eventual consistency
- แต่เราต้องรู้ว่าข้อมูลจะครบถ้วนเมื่อใด เพื่อจะได้บอกผู้มีส่วนได้ส่วนเสียว่านี่คือข้อมูลที่ยืนยันแล้ว
-
บันทึกจาก Wasteman
- การรับประกันความครบถ้วนก็เป็นปัญหาที่ยากอย่างน่าประหลาดใจเช่นกัน
- เมื่อระบบขยายใหญ่ขึ้น ข้อมูลที่ไหลผ่านหลายระบบอาจถูกเปลี่ยนรูปหรือหายไปโดยไม่ตั้งใจ
- ตรวจสอบได้ (Auditable): บันทึกทางการเงินต้องสามารถตรวจสอบได้ง่าย เพื่อให้ผู้มีส่วนได้ส่วนเสียตรวจพบข้อผิดพลาดและวัดผลการดำเนินงานของธุรกิจได้อย่างถูกต้อง
- ทันเวลา (Timely): ระบบบัญชีต้องตอบโจทย์ความต้องการเฉพาะของธุรกิจ
- สำหรับธุรกิจขนาดเล็ก การเทตัวเลขทั้งหมดตอนสิ้นเดือนอาจเพียงพอ แต่ธุรกิจขนาดใหญ่มักต้องการระบบที่เกือบเรียลไทม์
- สิ่งนี้ช่วยให้ติดตามสถานะทางการเงินได้ตลอดทั้งเดือน ตัดสินใจจากข้อมูลการเงินได้เร็วขึ้น และลดความเร่งรีบในการปิดงบช่วงต้นเดือนหรือต้นไตรมาส
- ไม่ว่าความต้องการนั้นคืออะไร ระบบบัญชีของเราต้องตอบโจทย์ความต้องการของธุรกิจ และต้องเป็นไปตามความหมายของคำว่า ทันเวลา สำหรับพวกเขา
-
บันทึกจาก Wasteman
- ผู้คนมักหลงประเด็นไปกับการถกเถียงเรื่องระบบ batch เทียบกับ streaming เมื่อพูดถึงความทันเวลา
- ในมุมมองของผม สำหรับระบบส่วนใหญ่ นี่ไม่ใช่ความแตกต่างที่สำคัญ
- มันสำคัญก็ต่อเมื่อคุณแคร์ latency ที่สั้นมากในระดับวินาทีถึงนาที
- แต่ก็น่าแปลกที่บ่อยครั้งผมได้ยินคนเถียงกันว่าควรทำแบบไหน ทั้งที่ผู้ใช้ปลายทางไม่จำเป็นต้องเห็นการอัปเดตเกินวันละไม่กี่ครั้ง
- การที่มีคนร้องขอ ไม่ได้แปลว่าจำเป็นจริงเสมอไป
หลักการทางวิศวกรรมหลัก 3 ประการที่ระบบบัญชีควรยึดถือ
- ความไม่เปลี่ยนแปลงของข้อมูล (Immutability) และความทนทานถาวร (Durability)
- ช่วยให้ตรวจสอบย้อนหลังได้ ซึ่งเป็นประโยชน์ต่อการดีบักและความถูกต้อง
- หากข้อมูลไม่เปลี่ยนแปลง เราจะสามารถบันทึกสถานะของระบบได้ทุกเมื่อ
- สิ่งนี้ทำให้การคำนวณโลกใหม่จากสถานะก่อนหน้าเป็นเรื่องง่ายมาก เพราะไม่มีสถานะใดสูญหาย
- เมื่อข้อมูลถูกบันทึกลงในบันทึกทางการเงินแล้ว จะลบออกไม่ได้
- การแก้ไขใด ๆ ต่อระบบต้องแสดงเป็นธุรกรรมทางการเงินใหม่
- ตัวอย่าง: หากระบบมี bug ทำให้บริการที่ควรมีราคา $900 ถูกบันทึกผิดว่าเป็น $1000
- ในการแก้ไขความผิดพลาดนี้ ต้องยกเลิกรายการบัญชีที่ผิดก่อน แล้วจึงลงรายการบัญชีใหม่ด้วยจำนวนเงินที่ถูกต้อง
- ข้อมูลควรถูกบันทึกในระดับที่เล็กที่สุด (Data recorded at the smallest grain)
- คล้ายกับหลักการข้างต้น นี่ก็สำคัญอย่างมากต่อการสร้าง audit trail ที่ชัดเจน
- แม้ว่ารายงานทางการเงินและบัญชีแยกประเภททั่วไปจะเป็นข้อมูลสรุปรวม แต่ทั้งหมดก็คำนวณมาจากเหตุการณ์ที่ละเอียดกว่า
- เมื่อข้อมูลมีสิ่งที่อธิบายไม่ได้ เราจำเป็นต้องมีข้อมูลที่ละเอียดที่สุดเพื่อดีบักว่าปัญหาคืออะไร
- การเก็บข้อมูลในระดับความละเอียดต่ำสุดยังทำให้แก้ไขข้อมูลอนุพันธ์จากชุดข้อมูลนั้นได้ง่ายมาก
- หากมีชุดข้อมูลไม่เปลี่ยนแปลงเพียงชุดเดียวที่เป็นแหล่งความจริงหลักสำหรับทุกมุมมองของข้อมูลนั้น
- การแก้ไขมุมมองก็เพียงแค่แก้ไขข้อมูล แล้วรัน pipeline ที่สร้างมุมมองนั้นใหม่อีกครั้ง
- ในทำนองเดียวกัน เมื่อผู้ทำบัญชีเตรียมพร้อมจะปิดบัญชี
- พวกเขาจะกระทบยอดธุรกรรมทั้งหมดที่เกิดขึ้นกับยอดคงเหลือของบัญชี เพื่อตรวจสอบว่าบัญชีถูกต้องหรือไม่
- หากพบความไม่ตรงกัน ก็สามารถเจาะลงไปหาธุรกรรมที่เป็นต้นเหตุได้อย่างแม่นยำ
- ต้องมีคุณสมบัติ Idempotency
- ทุกเหตุการณ์ทางการเงินต้องถูกประมวลผลเพียงครั้งเดียว และความซ้ำซ้อนในบันทึกทางการเงินจะก่อให้เกิดความคลาดเคลื่อนอย่างชัดเจน
- ด้วยเหตุนี้ โค้ดทั้งหมดที่สร้างบันทึกทางการเงินจึงต้องเป็น idempotent
- idempotent หมายถึงคุณสมบัติที่เมื่อนำการดำเนินการเดิมไปใช้หลายครั้งแล้ว ผลลัพธ์ยังคงเหมือนเดิม
- กล่าวคือ แม้จะประมวลผลเหตุการณ์ทางการเงินหลายครั้ง ผลลัพธ์ก็ต้องเหมือนกับการประมวลผลครั้งแรก
แนวปฏิบัติที่ดี
- ควรใช้จำนวนเต็มในการแทนจำนวนเงินทางการเงิน: ทำให้การคำนวณเลขคณิตง่ายขึ้นมาก ควรหลีกเลี่ยง float
- ควรรองรับความละเอียดของจำนวนเงินทางการเงินเพื่อลดการสูญเสียความแม่นยำระหว่างการแปลงสกุลเงิน
- หากจัดการเฉพาะดอลลาร์ การแทนค่าเป็นหน่วยเซนต์อาจเพียงพอ
- สำหรับธุรกิจระดับโลก ควรใช้หน่วย micro หรือทศนิยมอย่าง
DECIMAL(19, 4) - การใช้ทศนิยมเป็นที่นิยมในระบบการเงินทั่วไป แต่ในระบบการเงินด้านโฆษณา หน่วย micro คือมาตรฐาน
- ควรใช้วิธีปัดเศษที่สม่ำเสมอ: วิธีปัดเศษที่ต่างกันอาจทำให้ยอดที่คาดหวังต่างไปอย่างมีนัยสำคัญ
- วิธีปัดค่าทุกค่าที่ 5 ขึ้นไปเป็นเลขนัยสำคัญถัดไป และ 4 หรือต่ำกว่าปัดลง
- หรือวิธีปัดขึ้นเสมอ เป็นต้น
- สิ่งสำคัญคือการรักษาความสม่ำเสมอทั่วทั้งระบบ (ถ้าต่างกันรายการละ 1 เซนต์ เมื่อมี 10 ล้านรายการก็จะต่างกัน $100k)
- ควรเลื่อนการแปลงสกุลเงินออกไปให้นานที่สุดเท่าที่ทำได้: การแปลงสกุลเงินล่วงหน้าอาจทำให้สูญเสียความแม่นยำ
- เลื่อนการแปลงสกุลเงินออกไปจนกว่าจะมีการรวมยอดในสกุลเงินท้องถิ่นเสร็จ
- ใช้การแทนเวลาแบบจำนวนเต็ม: อาจเป็นประเด็นถกเถียงเล็กน้อย แต่แนะนำอย่างยิ่ง
- ไลบรารีต่าง ๆ ที่ใช้ parse timestamp เป็น object มักจัดการแตกต่างกัน
- เพื่อหลีกเลี่ยงความปวดหัวเหล่านี้ การใช้จำนวนเต็มจะดีกว่า
- Unix timestamp หรือ integer datetime ที่อิงกับ UTC ก็ใช้งานได้สมบูรณ์
- ยิ่งมีการแปลงข้อมูลระหว่างระบบน้อยเท่าไรยิ่งดี
-
บันทึกจาก Wasteman
- ยังไม่ได้พูดถึง bug ที่เกี่ยวกับเวลาออมแสงเลย หากใช้จำนวนเต็มที่เพิ่มขึ้นเรื่อย ๆ จะหลีกเลี่ยงปัญหานี้ได้ทั้งหมด
- ถ้ายังยืนยันจะใช้ datetime อย่างน้อยก็ควรใช้ UTC เป็นเรื่องน่าแปลกที่บริษัทขนาดใหญ่มากจำนวนไม่น้อยยังใช้ timestamp ที่ไม่ใช่ UTC
2 ความคิดเห็น
อันนี้มีประโยชน์จริง ๆ นะครับ ถ้าแปลงชนิดข้อมูล (
decimal,float,double) หรือปัดเศษไปแบบไม่คิดและไม่คุยกันในงานจริงก่อน อาจเกิดปัญหาใหญ่ได้ครับความคิดเห็นจาก Hacker News
เน้นย้ำความสำคัญของการใช้แนวทางการปัดเศษที่สม่ำเสมอ
แนะนำวิธีแสดงเวลาเป็นจำนวนเต็ม
แนะนำให้ใช้ฐานข้อมูลเชิงสัมพันธ์สำหรับระบบบัญชี
เป้าหมายหลักของระบบบัญชีคือความถูกต้อง ความสามารถในการตรวจสอบย้อนหลัง และความทันเวลา
ความเห็นเกี่ยวกับความสมบูรณ์ของระบบบัญชี
สำหรับธุรกิจระดับโลก แนะนำให้ใช้ทศนิยมอย่างน้อย 8 ตำแหน่ง
กล่าวถึงความสำคัญของส่วนติดต่อผู้ใช้ (UI)
อธิบายความแตกต่างระหว่างการประมวลผลแบบแบตช์กับแบบสตรีมมิง
แชร์ประสบการณ์การสร้างระบบใบแจ้งหนี้ด้วย TypeScript
แนะนำให้ใช้คลาสจาก standard library
อธิบายความยากของการปัดเศษและการแชร์ข้อมูล
แชร์ประสบการณ์ทำงานกับ API ของธนาคาร 10 อันดับแรกในสหรัฐฯ
แนะนำ "Accounting Patterns" ของ Martin Fowler