Sổ tay kỹ thuật fintech
(w.pitula.me)- Các hệ thống coi tiền là trạng thái cốt lõi phải được thiết kế dựa trên các nguyên tắc không tạo ra dữ liệu, không làm mất dữ liệu và không tin tưởng bất cứ điều gì
- Khi biểu diễn số tiền cần tránh float, thay vào đó kết hợp
BigDecimal, số nguyên ở đơn vị nhỏ nhất, số hữu tỉ... tùy theo trách nhiệm; việc tuần tự hóa số JSON cũng có thể tái tạo lại vấn đề IEEE-754 double - Sổ cái phải duy trì khả năng tái dựng số dư và báo cáo thông qua ghi sổ kép, dấu vết kiểm toán bất biến, tách biệt value time·booking time·settlement time, cùng các bản ghi điều chỉnh·hủy bỏ
- Dòng tiền thực tế phải ngăn chi trùng lặp và bỏ sót bằng đặt chỗ, tính idempotent, state machine có thể khởi động lại, xác minh API·webhook bên ngoài, outbox·CDC, và đối soát (reconciliation)
- Kiểm soát truy cập, phê duyệt four-eyes, theo dõi thay đổi trong SDLC, kiểm thử dựa trên thuộc tính và tiêm lỗi khiến cả người vận hành nội bộ lẫn thay đổi mã nguồn đều được xem là ranh giới tin cậy
Nguyên tắc cơ bản của hệ thống fintech
- Trong kỹ thuật phần mềm nơi tiền là mối quan tâm chính của hệ thống, khả năng truy vết, tính bất biến và khả năng kiểm chứng quan trọng hơn nhiều so với CRUD thông thường
- Đối tượng độc giả là người mới tham gia fintech, người đã làm việc trong fintech, và người ngoài fintech muốn hiểu hệ thống tiền tệ khác hệ thống thông thường như thế nào
- Mọi mẫu thiết kế đều là phương tiện để giữ vững ba nguyên tắc
- No invented data: Tiền không thể được tạo ra từ nơi không tồn tại, nên không được cho phép xử lý trùng lặp hay thay đổi số dư tùy tiện
- No lost data: Mọi điều xảy ra với tiền phải được truy vết và lưu bền vững
- No trust: Không tin tưởng nhà cung cấp bên ngoài, thành phần nội bộ hay thế giới thực, mà phải xác minh
Cách biểu diễn tiền
- Cách biểu diễn số tiền là quyết định nền tảng nhất của hệ thống tài chính; nếu chọn sai, toàn bộ các tầng phía trên sẽ kế thừa lỗi
- float/double hầu như luôn không phải lựa chọn tốt vì có thể gây mất chính xác khó dự đoán
- Dù vậy, chúng có ưu điểm là nhanh, tiết kiệm bộ nhớ và không cần thư viện hay cấu trúc dữ liệu bổ sung
- Các kiểu độ chính xác tùy ý như
BigDecimalcho phép kiểm soát tường minh độ chính xác tính toán và vị trí làm tròn- Phù hợp cho các phép tính trung gian gồm nhiều bước, như FX hay tính giá
- Lưu trữ bằng số nguyên ở đơn vị nhỏ nhất là cách dùng độ chính xác cố định như trong hệ thống ngân hàng trung ương đối với hầu hết tiền pháp định
- €12.34 được lưu thành
1234 - Phải tuân theo số chữ số của ISO 4217 và không được mặc định luôn là 2 chữ số
- Tiền mã hóa cũng dùng số nguyên ở đơn vị nhỏ nhất như satoshi, wei, nhưng độ chính xác khác nhau theo từng tài sản và do token định nghĩa, như
decimalscủa ERC-20 - Số tiền mã hóa có thể vượt quá số nguyên 64 bit, nên có thể cần số nguyên độ rộng tùy ý
- €12.34 được lưu thành
- Số hữu tỉ là lựa chọn mạnh nhất khi không được phép mất chính xác, nhưng chậm, khó chuyển sang định dạng khác mà không mất chính xác, và thường cần kiểu hay thư viện tùy chỉnh
- Cách lưu trữ và cách tính toán là hai quyết định riêng biệt; một hệ thống có thể vừa lưu bằng số nguyên vừa tính trung gian bằng
BigDecimal - Ở bước tuần tự hóa số tiền, xử lý ranh giới cũng rất quan trọng
- Số JSON thông thường trong hầu hết parser là IEEE-754 double, nên dù biểu diễn nội bộ có cẩn thận thì vấn đề float vẫn có thể tái xuất ở ranh giới
- Tiền nên được truyền dưới dạng chuỗi như
"12.34"hoặc số nguyên ở đơn vị nhỏ nhất
Làm tròn và xử lý tiền tệ
- Làm tròn là điều không thể tránh khỏi trong phép chia, chuyển đổi tiền tệ, phí, lãi suất, áp dụng tỷ lệ và thay đổi độ chính xác, nên không được để ngầm định
- Chiến lược làm tròn là một quyết định nghiệp vụ
- Có trường hợp cần làm tròn xuống theo hướng bảo thủ, cũng có thể dùng half-even để đạt hiệu ứng thống kê
- Việc ai nhận phần lẻ còn lại có thể ảnh hưởng đến pháp lý và thuế
- Cần giữ toàn bộ độ chính xác càng lâu càng tốt, và thường chỉ làm tròn ở ranh giới như trước khi lưu hoặc trước khi hiển thị cho người dùng
- Nếu chia một giá trị thành nhiều phần rồi làm tròn, tổng các phần có thể khác giá trị ban đầu
- Tùy tình huống, có thể cần một rounding account tường minh
- Tiền không thể chỉ được biểu diễn bằng số, mà luôn phải đi cùng đơn vị tiền tệ
- Gói số tiền và tiền tệ cùng nhau bằng
Moneynewtype, struct, class hoặc record sẽ giảm khả năng lỗi - Phải cấm cộng các đồng tiền khác nhau, và việc chuyển đổi phải được thực hiện tường minh bằng tỷ giá được kiểm soát nghiêm ngặt
- Không chấp nhận mã tiền tệ tùy ý; phải xác thực thành một tập tiền tệ được kiểm soát ở ranh giới hệ thống
- Mã tiền pháp định có thể dùng làm định danh, nhưng tiền mã hóa cần định danh phức tạp hơn như
(network, contract address) - Tiền mã hóa pegged, bridged, wrapped không tương đương với tài sản cơ sở
- Gói số tiền và tiền tệ cùng nhau bằng
Tỷ giá FX
- FX rate luôn có tính định hướng
- Tỷ giá EUR/USD không phải nghịch đảo đơn giản của USD/EUR
- Trên sàn giao dịch, lệnh mua và lệnh bán có giá khác nhau do bid/ask spread
- Thời điểm của tỷ giá cũng làm thay đổi kết quả
- Tỷ giá tại thời điểm hiện tại dùng để tính giá trị của khoản nắm giữ hiện tại hoặc các giao dịch được giả định là xảy ra ở hiện tại
- Tỷ giá theo value date dùng cho tính toán thay đổi giá trị hay thuế
- Trong chuyển đổi, có hai loại tỷ giá quan trọng
- Transactional rate là tỷ giá tại đó việc chuyển đổi thực sự diễn ra, được suy ra từ số tiền gốc và số tiền kết quả
- Reference rate được dùng cho định giá, như giá trị khoản nắm giữ hay chuẩn thuế, và để đánh giá tính tương đương, chứ không phải giá giao dịch thực tế
- Không tồn tại một tỷ giá chuẩn duy nhất
- Tỷ giá được hình thành từ thị trường và thay đổi theo nơi giao dịch hoặc cách tính
- Tỷ giá ngân hàng trung ương là thứ gần chuẩn nhất nhưng chỉ có thể dùng làm reference rate; các nguồn thay thế cũng có thể hợp lệ
- Cần lưu cùng số tiền và nguồn gốc của reference rate để có thể kiểm chứng về sau
Sổ cái và ghi sổ kép
- Việc di chuyển tiền phải được ghi lại theo cách có thể kiểm toán và tái dựng lại được cả sau nhiều năm
- Ghi sổ kép là cách phổ biến để lưu giao dịch tài chính dưới dạng danh sách entry có cấu trúc
(credit account, debit account, amount)- Biểu diễn cổ điển đặt riêng debit row và credit row cho mỗi lần dịch chuyển
- Vì mọi entry đều chuyển cùng một số tiền từ tài khoản này sang tài khoản khác, sổ cái luôn cân bằng
- Tiền luôn có nguồn và đích
- Ngay cả nhà cung cấp bên ngoài cũng nên có tài khoản chuyên dụng để có thể theo dõi dòng tiền đi vào và đi ra khỏi hệ thống
- Số dư không nên được lưu trực tiếp mà được suy ra từ các dịch chuyển tiền
- Tài khoản có các loại như asset, liability, equity
- accounting equation
assets = liabilities + equityđược duy trì - Trong thực tế còn cần tài khoản revenue và expense để ghi nhận doanh thu phí hoặc tổn thất write-off
- Công thức mở rộng là
assets = liabilities + equity + revenue - expenses
- accounting equation
- Một giao dịch thường tạo ra nhiều lần dịch chuyển
- Dịch chuyển số tiền ròng và dịch chuyển phí có thể phát sinh riêng biệt
- Theo thông lệ, posted entry là bất biến; việc điều chỉnh được xử lý bằng cách thêm entry mới để bù trừ entry gốc
Mô hình thời gian: value, booking, settlement
- Một giao dịch thường có từ hai dấu thời gian trở lên, đôi khi là ba
- Value time: thời điểm giao dịch thực sự xảy ra
- Booking time: thời điểm được ghi vào hệ thống
- Settlement time: thời điểm tiền thực sự được chuyển giao hoặc hiện thực hóa
- Settlement time không tồn tại với mọi giao dịch và thường được biểu diễn là T+X
- T+2 nghĩa là việc settlement diễn ra sau 2 ngày tính từ value date
- value time và booking time gần như luôn lệch nhau
- booking > value là backdated, đặc biệt quan trọng khi kỳ báo cáo khác nhau
- booking < value là forward-dated, xảy ra trong thanh toán đặt trước hoặc thanh toán ngày tương lai
- Ví dụ thanh toán thẻ: thanh toán xảy ra ở T1, được hệ thống ghi nhận ở T2, và nhà cung cấp thanh toán chuyển tiền vào tài khoản ở T3
- Báo cáo nghiệp vụ chủ yếu nhìn theo value time hoặc settlement time, còn booking time hữu ích cho khả năng truy vết
- Nếu gộp nhiều mốc thời gian vào một
created_at, sau này sẽ mất những thông tin không thể tái dựng
Dấu vết kiểm toán, event sourcing và tính bất biến
- Hệ thống tài chính là đối tượng bị kiểm toán theo quy định, và có thể phải chứng minh liệu có sự trộn lẫn giữa tiền của người dùng và tiền của công ty hay không, doanh thu có thể được giải thích hay không, thông tin đã cung cấp ra bên ngoài có khớp với thực tế hay không, cũng như trạng thái bảo vệ nguồn tiền
- audit trail là toàn bộ lịch sử không chỉ của trạng thái hiện tại mà còn của cách trạng thái đó được tạo ra
- Điều gì đã xảy ra
- Xảy ra khi nào
- Ai hoặc cái gì đã kích hoạt nó
- Vì sao nó xảy ra
- Không chỉ dòng tiền, mà cả can thiệp thủ công, thay đổi cấu hình như fee schedule, rate source, limit, và thay đổi quyền hạn cũng cần có dấu vết kiểm toán
- Với các quyết định như compliance check hoặc risk score, chỉ lưu kết quả có thể là chưa đủ
- Nếu nằm trong decision table hoặc rules engine như DMN, Drools, Decisions4s, thì có thể tạo cấu trúc tái hiện được quy tắc nào đã chạy với đầu vào nào và cho ra kết quả gì
- Event sourcing là một cách tiếp cận có hệ thống để tạo audit trail
- Không lưu riêng trạng thái hiện tại và log, mà chỉ lưu các sự kiện rồi suy ra trạng thái từ đó
- Sổ cái ledger theo nguyên tắc ghi sổ kép là một ví dụ của mẫu này ở chỗ không lưu balance mà tính từ các entry
- Event sourcing cũng có những ràng buộc thực tế lớn
- Không cần ở mọi nơi; nếu ledger đã bao phủ phần tiền, thì các miền xung quanh có thể chỉ cần mô hình thông thường và change log đáng tin cậy
- Vì hiệu năng, có thể cache hoặc snapshot balance và projection
- Nguồn sự kiện có thể khó truy vấn hiệu quả, khiến khối lượng công việc projection tăng lên
- Sự kiện tồn tại nhiều năm, nên code hiện tại phải đọc được cả các sự kiện rất cũ
- Dấu vết kiểm toán nếu có thể bị sửa thì không còn là bằng chứng, nên phải là append-only
- Các công cụ gồm có bảng append-only, gỡ
UPDATE·DELETEkhỏi quyền DB, chặn các thao tác mutating ở tầng ứng dụng, và tạo bằng chứng chống can thiệp bằng checksum hoặc hash chain
- Các công cụ gồm có bảng append-only, gỡ
- Trong hệ thống thực tế, đôi khi phải sửa event log hoặc audit trail vì lỗi
- Thông thường, dữ liệu phải được cố định sau khi đã báo cáo ra bên ngoài; còn trước khi báo cáo, nếu phát hiện vấn đề thì có thể sửa ngay tại chỗ trước khi nó đi ra ngoài hệ thống
Hủy và điều chỉnh
- Những sai sót như posting sai số tiền hoặc posting vào sai tài khoản vẫn xảy ra
- Tính bất biến yêu cầu cách sửa theo hướng tiến về phía trước
- Posting một compensating entry mới và liên kết hai chiều với bản ghi gốc
- Reversal sẽ bù trừ hoàn toàn về mặt kinh tế để coi như giao dịch gốc chưa từng tồn tại, nhưng cả giao dịch gốc lẫn reversal đều vẫn được giữ trong lịch sử
- Correction hoặc adjustment là ghi nhận phần chênh lệch giữa bản ghi thực tế và giá trị đúng, hoặc đảo ngược rồi posting lại với giá trị đúng
- Điều chỉnh có thể rơi vào kỳ báo cáo khác với bản gốc
- Cần có thông tin liên kết để báo cáo quy đúng phần điều chỉnh và phân biệt giữa hoạt động thực tế với phần dọn dẹp
- Thông thường không cho phép backdate vào kỳ báo cáo đã đóng, nên khi điều chỉnh có chỉ định value time trong quá khứ hay không sẽ phụ thuộc vào lịch báo cáo
Thực thi dòng tiền: điều kiện bất biến và đặt giữ tiền
- Invariant là đặc tính luôn phải đúng trong hệ thống
- Phương trình kế toán là một ví dụ, và các bên liên quan nghiệp vụ có thể định nghĩa nhiều điều kiện khác
- Các cách cưỡng chế invariant mang tính bổ trợ lẫn nhau
- Thiết kế để chỉ tạo ra các đối tượng hợp lệ ngay từ giai đoạn tạo lập
- Kiểm tra ở runtime bằng assertion, test và property-based testing
- Phân tích hậu kiểm dữ liệu đã lưu bằng reconciliation job hoặc nightly check
- Các giao dịch tương tác với thế giới bên ngoài phải tránh race condition
- Cần ngăn việc chỉ sau khi gọi ra ngoài mới phát hiện thiếu số dư, hoặc cùng một khoản tiền bị tiêu hai lần
- Funds reservation hoặc hold-and-release là mẫu đặt giữ tiền cho một giao dịch cụ thể trước khi tương tác bên ngoài
- Nếu thành công thì settle reservation và tiếp tục giao dịch
- Nếu thất bại thì release để trả về available balance
- Mẫu này phân biệt total balance và available balance
available = total - reserved- Việc kiểm tra số dư và tạo reservation mới được thực hiện dựa trên available balance
- Số tiền cuối cùng có thể khác với ước tính ban đầu
- Nếu phí hoặc tỷ giá thay đổi, có thể giữ trước số tiền ước tính, settle theo số tiền thực tế rồi release phần còn lại
- Reservation bắt buộc phải được settle hoặc release
- Reservation bị mồ côi sẽ khóa tiền của người dùng nhưng không làm mất tiền cũng không tạo ra tiền
- expiry hoặc timeout có thể là lưới an toàn nhưng không bắt buộc
- Kiểm tra số dư và ghi nhận reservation phải mang tính linearizable
- Với stale read, hai giao dịch đều có thể vượt qua bước kiểm tra và cùng dựa trên một nguồn tiền
Overdraft và tính idempotent
- Overdraft là tình trạng số dư tài khoản trở thành âm
- Overdraft có chủ đích là một sản phẩm tín dụng có hạn mức và lãi suất, thường được mô hình hóa bằng một tài khoản overdraft riêng
- Overdraft không chủ đích có thể vẫn xảy ra dù chính sách cấm
- settlement có thể đến với giá trị lớn hơn ước tính reservation, hoặc reversal có thể đến sau khi tiền đã rời đi
- funds reservation giúp thu hẹp cửa sổ rủi ro nhưng không thể loại bỏ hoàn toàn
- “Bị cấm” và “không thể biểu diễn” là hai chuyện khác nhau
- Nếu dùng unsigned integer hoặc
CHECK (balance >= 0)để khiến số dư âm không thể được biểu diễn, thì khi thực tế buộc phải chấp nhận số dư âm, hệ thống có thể crash, clamp về 0, hoặc xử lý sai - Clamp số dư âm về 0 sẽ tạo ra tiền
- Nếu dùng unsigned integer hoặc
- Khi phát hiện overdraft, cần coi đó là tín hiệu để điều tra, rồi thu hồi hoặc xử lý một cách tường minh bằng các cách như bù trừ với khoản nạp sau này, yêu cầu hoàn trả, hoặc write-off vào tài khoản chi phí/lỗ
- Trong hệ thống phân tán, không thể đảm bảo exactly-once delivery nên cần retry, và retry có thể tạo ra chuyển phát trùng lặp
- Idempotency là tính chất bảo đảm dù cùng một thông điệp được gửi hai lần thì việc xử lý chỉ tạo hiệu lực một lần
- Idempotency key tường minh thường đơn giản và an toàn hơn deduplication dựa trên payload
- Key nên được giới hạn trong phạm vi một operation cụ thể và phạm vi client
- Cần quyết định sẽ phát lại lỗi hay xử lý lại; với lỗi vĩnh viễn thì thường đơn giản hơn khi phát lại nguyên trạng
- Kiểm tra xem payload của lần gọi trùng lặp có giống bản gốc hay không là thực hành tốt, nhưng có chi phí về độ phức tạp triển khai và tính linh hoạt
- Ở quy mô lớn, cần deduplication cho hàng tỷ request và atomic barrier khi truy cập đồng thời
- Cửa sổ idempotency như 24 giờ giúp đơn giản hóa triển khai nhưng phải đánh đổi về tính đúng đắn
- Cũng cần test retry và xử lý retry đến không đúng thứ tự
Luồng có thể khởi động lại
- Luồng tiền kéo dài qua nhiều bước và phải giả định rằng nó có thể chết ở bất kỳ điểm nào giữa các bước
- Full resumability là thiết kế sao cho luồng đang làm dở luôn ở trạng thái có thể khôi phục
- Trạng thái tiến độ phải được lưu trong kho bền vững chứ không phải trong bộ nhớ
- Hãy mô hình hóa luồng dưới dạng state machine tường minh và commit việc hoàn tất mỗi bước trước khi bắt đầu bước tiếp theo
- Cần có một driver độc lập để đẩy tiếp các luồng bị gián đoạn
- scheduler, worker, poller phải có thể xử lý các luồng chưa hoàn tất ngay cả sau khi orchestrator bị crash
- Khi resume, có thể phải chạy lại những bước đã xảy ra một phần, nên mỗi bước phải có tính idempotent
- Không thể rollback các tác động ra bên ngoài
- Sau khi đã gọi ra thế giới bên ngoài thì không thể quay lại trạng thái như chưa gọi
- Phải roll forward cho đến khi hoàn tất, hoặc nếu bước sau thất bại vĩnh viễn thì posting compensating action theo saga pattern
- Có thể dùng durable-execution engine như Temporal, Camunda, Workflows4s, AWS Step Functions hoặc tự xây một persistent state machine
Tiêu thụ API bên ngoài
- Các API bên ngoài như nhà cung cấp thanh toán, custodian, blockchain node, nhà cung cấp KYC cần được xử lý theo hướng phòng thủ vì không thể kiểm soát code, chất lượng hay thời gian hoạt động của họ
- Không nên tin tưởng schema
- Có thể xảy ra thiếu field, thay đổi kiểu dữ liệu, hoặc
nullngoài dự kiến - Những phần quan trọng cần được kiểm tra ở ranh giới, và dữ liệu ngoài dự kiến nên bị lỗi rõ ràng
- Nếu kiểm tra cả những phần không cần thiết, có thể phát sinh sự cố không cần thiết vì bên thứ ba vi phạm hợp đồng
- Có thể xảy ra thiếu field, thay đổi kiểu dữ liệu, hoặc
- Ở API bên ngoài, các chuyện như truyền token trong URL, mất độ chính xác, HTTP code không đúng nghĩa, error body bên trong
200, pagination không nhất quán, hoặc định dạng ngày tùy biến hoàn toàn có thể xảy ra - Mọi lời gọi đều có thể thất bại nên cần timeout và retry
- Circuit breaker chủ yếu là phép lịch sự với các máy chủ đang quá tải, nhưng làm tăng độ phức tạp phía client
- Tuy vậy, nó có thể cần thiết để bảo vệ các tài nguyên hữu hạn như latency, thread và connection
- Cần ước tính trước để đối chiếu lưu lượng gọi dự kiến với rate limit và quota của provider
- Nếu lưu toàn bộ request và response dưới dạng có cấu trúc và có thể truy vấn, chúng sẽ trở thành tư liệu cho điều tra, audit trail, bằng chứng khi tranh chấp về hành vi của provider, và dữ liệu để xử lý lại sau khi có bug
- Ở các khu vực cốt lõi, có thể cân nhắc dự phòng provider
- Có thể theo cách như xác minh dữ liệu bằng hai blockchain node, có đối tác ngân hàng dự phòng, crypto custodian dự phòng, hoặc nhà cung cấp KYC dự phòng
- Chi phí phát triển, phí dịch vụ và độ phức tạp đều rất lớn
- Sandbox có thể khác production rất nhiều, vì vậy cần chuẩn bị kiểm thử trên production bằng canary release hoặc usage có kiểm soát với tác động thấp
Xử lý webhook
- Webhook là cách phổ biến để nhận tín hiệu từ hệ thống bên ngoài, nhưng xử lý an toàn không hề dễ
- Không được giả định về thứ tự
- Message có thể đến sai thứ tự hoặc chứa dữ liệu cũ
- Không được coi webhook vừa nhận là sự thật mới nhất rồi ghi đè trạng thái
- Không được giả định về tính hợp lệ
- Webhook có thể đến từ hệ thống con khác của issuer và chứa dữ liệu cũ hoặc bị chuyển đổi sai
- Cách tốt là chỉ dùng phần thân webhook làm trigger rồi truy vấn API để xác nhận authoritative state
- API cũng có thể eventually consistent, nên nếu truy vấn ngay có thể trả về trạng thái trước đó và cần retry
- Không được giả định về việc giao nhận
- Dù issuer hứa có chính sách redelivery mạnh, webhook rồi cũng sẽ có lúc bị thất lạc
- Cần một quy trình độc lập như reconciliation để bù cho tính toàn vẹn dữ liệu
- Cũng không được giả định là chỉ giao một lần
- Cùng một webhook có thể được gửi nhiều lần, và xử lý phải có tính idempotent
- Cần acknowledge nhanh và xử lý bất đồng bộ
- Lưu raw event vào durable store, trả về 2xx ngay lập tức, rồi thực hiện công việc thật một cách bất đồng bộ
- Raw payload phải được lưu nguyên trạng
- Điều này cần cho xử lý ổn định, audit trail và xử lý lại sau khi có bug
- Cần xác minh bên gọi
- Thông thường issuer sẽ gắn chữ ký vào payload, và bên nhận sẽ xác minh bằng HMAC với shared secret hoặc chữ ký bất đối xứng dựa trên khóa công khai
- Việc xác minh chữ ký phải thực hiện trên raw bytes đã nhận được, không phải trên payload đã được tuần tự hóa lại
- Webhook nên được xem là gợi ý rằng có chuyện gì đó đã xảy ra, chứ không phải là sự thật về những gì đã xảy ra
Thông báo đáng tin cậy: Outbox và CDC
- Khi cần thông báo đáng tin cậy các thay đổi của hệ thống qua kênh bên ngoài như Kafka event hay webhook call, tính transactionality trở thành vấn đề
- Có thể xảy ra tình huống publish thành công nhưng do sự cố mạng nên không nhận được phản hồi và hệ thống rollback trạng thái, hoặc state change đã được commit nhưng publish lại thất bại
- Câu trả lời kiểu textbook là 2-phase commit hoặc distributed transaction, nhưng chúng hiếm khi được dùng vì độ phức tạp và khó tiêu chuẩn hóa để tái sử dụng
- Có nhiều lựa chọn thực tế hơn
- Outbox pattern: ghi lại transactionally ý định publish vào một store chuyên dụng cùng với thay đổi trạng thái, rồi xử lý sau đó cho đến khi thành công
- Change Data Capture: đọc write-ahead log hoặc replication log của database để biến các thay đổi đã commit thành event stream
- Debezium và AWS DMS cung cấp CDC
- CDC xuất ra raw event dưới dạng hàng của table, nên nếu muốn tránh lộ schema nội bộ thì cần hậu xử lý
- Listen-to-yourself là publish event trước rồi tự dựng lại trạng thái của mình từ chính event đó
- Với event sourcing, event log đã có sẵn trong DB nên chỉ cần publish từ đó
- Dù chọn cơ chế nào, delivery vẫn là at-least-once
- Nếu relay hoặc connector crash sau khi publish nhưng trước khi ghi nhận, nó có thể gửi lại khi khởi động lại
- Consumer cần deduplicate bằng stable event id và phải hoạt động idempotent
Reconciliation
- Hệ thống phụ thuộc vào dữ liệu bên ngoài rất dễ gặp data drift, tức trạng thái giữa hai hệ thống bị lệch nhau
- Có thể bỏ lỡ webhook, hoặc transaction đã được ghi vào ledger nhưng chưa phản ánh trong hệ thống provider bên ngoài
- Reconciliation là quy trình đưa hai hệ thống về khớp nhau
- Trên thực tế có thể là hơn ba hệ thống, như ledger, payment processor và ngân hàng
- Tần suất có thể là theo giờ, hằng ngày, hằng tháng hoặc hằng năm tùy theo bối cảnh và ràng buộc
- Drift có thể chỉ là thiếu dữ liệu, hoặc là khác biệt phức tạp hơn như cùng một transaction nhưng số tiền khác nhau
- Timing cũng quan trọng
- Nếu settlement là T+3, một bản ghi có thể ở trạng thái unreconciled trong 3 ngày, nên quy trình phải phản ánh điều này để tránh alert không cần thiết
- Matching algorithm là điểm khó cốt lõi
- Thông thường, nếu lưu external provider id trong nội bộ thì việc matching sẽ đơn giản hơn
- Nếu không có, có thể cần heuristic dựa trên amount và time
- Cũng cần reconciliation một-nhiều
- Một settlement transfer có thể bao gồm nhiều transaction
- Không được đơn giản ghi đè discrepancy chỉ để reconciliation khớp nhau
- Cần có hỗ trợ hạng nhất như correction record hoặc xử lý lại dữ liệu webhook để hiểu nguyên nhân và sửa đúng cách
Kiểm soát và truy cập
- Hệ thống tiền tệ không chỉ cần kiểm soát dữ liệu mà còn phải kiểm soát ai có thể thực hiện hành động nào, đồng thời chứng minh được việc tuân thủ quy trình sau đó
- Segregation of duties là biện pháp kiểm soát để một người không thể sở hữu toàn bộ quy trình
- Four-eyes, maker-checker, dual control là cách yêu cầu người thứ hai phê duyệt trước khi một action cụ thể được áp dụng
- Các đối tượng áp dụng gồm những hành động có thể làm dịch chuyển hoặc ghi sai tiền như withdrawal thủ công hoặc quy mô lớn, correction ledger thủ công, chuyển treasury và cold wallet, hoặc thay đổi fee schedule hay limit
- Các biện pháp kiểm soát tương tự cũng áp dụng cho kỹ thuật
- Code merge, production deploy và thay đổi infrastructure đều là các action nhạy cảm trong hệ thống tiền tệ nên cần review và approval
- Bản thân approval cũng là một phần của trail
- Cần ghi lại ai đã yêu cầu, ai đã phê duyệt và liệu hai người đó có khác nhau hay không để chứng minh kiểm soát
- Cần có một đường break-glass rõ ràng và được audit chặt chẽ cho tình huống khẩn cấp
- Access control là một phần của trạng thái hệ thống và thay đổi theo thời gian
- Cả con người lẫn service đều phải được cấp quyền tối thiểu
- Ưu tiên RBAC hơn cấp quyền theo từng cá nhân sẽ giúp review dễ hơn
- Việc cấp và thu hồi capability cũng là sự kiện nhạy cảm, nên cần ghi lại cái gì, ai và vì sao đã thay đổi
- Cần scheduled access review để phát hiện permission drift đã cũ hoặc không còn chính xác
Theo dõi thay đổi trong SDLC
- Trong môi trường bị quản lý, cần có khả năng kiểm toán quá trình code đi tới production
- source control là lịch sử thay đổi
- commit history quy mọi thay đổi về tác giả, đồng thời liên kết lý do thông qua review và ticket được liên kết
- cần bảo vệ bằng signed commit, protected branch và cấm force-push lên shared history
- review và pipeline phải được bắt buộc thực thi
- required review, status check và cấm direct push lên main branch là rất quan trọng
- deployment phải có khả năng truy vết
- cần có khả năng tái dựng version nào đang chạy, ai đã release và khi nào, để có thể liên kết incident với thay đổi gây ra nguyên nhân
Chiến lược kiểm thử
- Trong các hệ thống tiền bạc, không gian của operation sequence rất lớn và các lỗi đáng chú ý thường xuất hiện từ tổ hợp, nên kiểm thử đặc biệt quan trọng
- Property-based testing kiểm tra các property phải luôn đúng với mọi đầu vào, thay vì một đầu ra cụ thể
- rất phù hợp với invariant và money math
- Khi tạo operation sequence, cần kiểm tra invariant sau mỗi bước chứ không chỉ ở cuối
- vì khó thực hiện thủ công ở quy mô lớn, nên cần testing harness có thể tự động chèn assertion
- Generative idempotency testing xác minh rằng mọi operation chạm vào thế giới bên ngoài đều không gây thêm tác động ở lần gọi thứ hai
- Crash and resume injection xác minh rằng long flow có thể phục hồi ngay cả khi bị chết giữa bất kỳ bước nào
- Round-trip testing kiểm tra sau encode/decode, serialize/deserialize, convert/convert back thì có quay về điểm ban đầu hay vẫn nằm trong tolerance đã biết hay không
- đây là cách nhanh để bắt lỗi mất độ chính xác ở biên của money và currency type cũng như bug serialization
- Golden testing so sánh kết quả tính toán như fee breakdown, statement, report với kết quả kỳ vọng đã lưu để làm lộ ra các diff ngoài ý muốn
- Backward-compatibility testing duy trì một corpus payload định dạng cũ trong thực tế và xác minh rằng code hiện tại vẫn deserialize và project đúng
- Production testing có thể cần thiết khi sandbox khác production quá nhiều
- ví dụ gồm canary release, controlled rollout với blast radius nhỏ và synthetic transaction liên tục chạy với số tiền thật nhỏ
- production test làm dịch chuyển tiền thật, nên phải đi qua ledger, reconciliation, audit trail y hệt, đồng thời được dọn dẹp bằng các luồng correction/reversal thông thường
Thuật ngữ miền và tài liệu tham khảo
- Khi nhập môn fintech, vocabulary và concept có thể khó hơn cả code, nên các thuật ngữ cốt lõi được tổng hợp riêng
- Nhóm kế toán và ledger bao gồm ledger, general ledger và 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
- Nhóm tiền tệ và FX bao gồm 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
- Nhóm giao dịch và settlement bao gồm value date, booking date, settlement date, T+X, clearing vs settlement, cut-off time, float, netting, backdating, reversal/correction
- Các thuật ngữ về thanh toán, thẻ, thị trường, crypto và compliance cũng được tổng hợp riêng
- 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
- Tài liệu tham khảo được chia thành kế toán và ledger, payments và cards, markets và trading, crypto, engineering, KYC và AML
- Accounting for Computer Scientists: bài viết cho kỹ sư giải thích kế toán kép bằng đồ thị và mô hình dữ liệu
- Modern Treasury, How to Scale a Ledger: loạt bài tiếp cận production ledger từ góc nhìn kỹ nghệ phần mềm
- Designing Data-Intensive Applications: bàn về idempotency, log, consistency và failure mode từ góc nhìn hệ thống
Ba ví dụ end-to-end
-
Rút crypto
- Đây là luồng người dùng rút 0.5 ETH đến một địa chỉ bên ngoài
- Yêu cầu bao gồm idempotency key để các lần gửi trùng lặp chỉ tạo ra một withdrawal duy nhất
- Khóa trước 0.5 ETH và network fee ước tính từ available balance
- Cổng compliance kiểm tra sanctions, AML và địa chỉ đích, và có thể sleep trong nhiều ngày do có gọi ra hệ thống bên ngoài và quy trình review thủ công
- Việc broadcast on-chain phải có tính idempotent, và sau khi crash thì phải kiểm tra lại chain thay vì broadcast lần thứ hai
- Sau đủ số confirmation, thực hiện posting vào ledger cho user account debit, external on-chain account credit, network fee expense và service fee revenue
- Job chạy ban đêm sẽ reconcile giữa ledger và trạng thái thực tế trên chain
-
Nạp tiền bằng thẻ
- Đây là luồng nạp tiền bằng thẻ thông qua PSP
- Người dùng gửi số tiền và thông tin thẻ, rồi mở deposit transaction tại PSP bằng idempotency key
- Authorization chỉ tạo hold, và vì tiền vẫn chưa thuộc về công ty nên không credit vào user balance
- Webhook
capturedxác minh chữ ký trên raw bytes, lưu raw payload, trả về 2xx nhanh rồi xử lý bất đồng bộ - Webhook chỉ là trigger, nên cần truy vấn authoritative state từ API của PSP
- Trạng thái captured nhưng chưa settled được posting thông qua clearing account, còn settlement có thể đến theo lô sau T+X
- Chargeback được xử lý bằng linked compensating entry thay vì sửa bản ghi gốc
-
Chuyển đổi trong ứng dụng kèm cashback
- Đây là luồng đổi 1,000 EUR sang USDC và cấp promotional cashback
- Quote EUR→USDC không phải là nghịch đảo của USDC→EUR mà là directional rate
- EUR và USDC không được cộng với nhau, USDC được định danh bằng
(network, contract address)và không giống với fiat được neo giá - Tính toán giữ toàn bộ độ chính xác và chỉ làm tròn một lần ở biên theo chiến lược được chỉ định rõ ràng
- Spread phải được booking rõ ràng vào revenue account, không được biến mất thành rounding residual
- Cashback không phải là phần tăng balance miễn phí mà là tiền thật được chuyển từ promotional/expense account của công ty sang user balance
- Việc publish kết quả phải đảm bảo reliable delivery bằng các cơ chế như outbox, CDC hoặc event log, và downstream consumer sẽ dedupe bằng stable event id
1 bình luận
Ý kiến trên Hacker News
Tôi đã đọc lướt qua, và thấy cuốn sổ tay này khá nông, thậm chí ở một số phần gần như là lời khuyên tệ
Chẳng hạn, thấy số tiền được lưu dưới dạng không phải số nguyên là tôi muốn hét lên rồi chạy mất. Ví dụ như trường hợp decimal của Rust được biểu diễn thành số thực dấu phẩy động trong JSON. Trừ khi có lý do cực kỳ thuyết phục, tiền luôn nên là số nguyên; còn view xuất ra thì có thể là bất cứ thứ gì, kể cả một định dạng mã hóa bit kỳ quặc nào đó
Tỷ giá ngoại hối cũng không phải là vấn đề có thể giải quyết bằng một thời điểm duy nhất. Tỷ giá tại thời điểm theo phía người mua, tỷ giá tại thời điểm theo phía người bán, việc thỏa thuận, dung sai thỏa thuận, timestamp xác nhận đã thỏa thuận — tất cả đều ảnh hưởng
Vì tính bất biến, ở mọi nơi xử lý tiền tôi đều muốn có event sourcing. Stream đã được dọn dẹp cuối cùng có thể trông như
A -> B -> E, nhưng stream thực tế có thể làA0 -> Edit(A0, A) -> B -> C -> D -> Rollback(B) -> ERốt cuộc Fintech không phải chỗ nào cũng giống nhau. Có nơi tiền bị đối xử như kiện hàng, có nơi tiền là trung tâm của mọi thứ
Về ngoại hối, nhận xét đó có vẻ đang củng cố cho câu “không có canonical rate” trong sổ tay. Hơn nữa bài viết đang nói về việc xử lý bản ghi sau khi đã xác nhận, còn phía này có lẽ đang nói về phương thức xác nhận. Đó là một sắc thái hợp lệ cho một mục tiêu riêng, nhưng không giống bằng chứng cho thấy bài viết thiếu hoặc sai
Phần về tính bất biến cũng có vẻ bài viết đang nói cùng ý. Tôi không rõ khác ở điểm nào
Nếu bạn đang tính giá quyền chọn Monte Carlo trên đường đi của lãi suất và quan tâm tới các chỉ số rủi ro như duration, convexity, vega, thì chẳng ai bận tâm quy tắc làm tròn là gì. double là đủ. Bạn định ép
exp(-rt)cashflowhay hàm phân phối tích lũy chuẩn về số nguyên như thế nào?Có những lĩnh vực dùng số nguyên là đúng. Nhưng đó không phải nguyên tắc phổ quát; chỉ cần chọn giải pháp kỹ thuật phù hợp
Nếu môi trường bạn dùng hỗ trợ thì cũng có thể dùng fixed-point, nhưng về mặt kỹ thuật nó vẫn là số nguyên
Với mục đích hiển thị, trả về giá trị decimal là an toàn
Bạn nói sẽ chạy khỏi hệ thống lưu số tiền bằng số nguyên à, tốt thôi. Vậy chắc chúng ta sẽ không làm việc trong cùng một hệ thống. Ngày nay tôi còn thường muốn chạy khỏi những hệ thống xử lý số tiền bằng số nguyên hơn. Nếu đó là một codebase lý tưởng chỉ do các lập trình viên tài chính giàu kinh nghiệm đụng vào thì có thể ổn, nhưng các hệ thống như vậy thường có nguy cơ trở nên quá khép kín hoặc mong manh
Nếu muốn khuyên những ai đang cân nhắc chiến lược minor-unit precision để biểu diễn số tiền, thì tốt nhất là đừng làm. Ít nhất là không nên dùng nó làm định dạng dữ liệu trao đổi/API
Nó trông có vẻ thông minh, như tính toán số nguyên nhanh, không gặp vấn đề làm tròn khi cộng và trừ, nhưng ngay khi làm việc với một đối tác có giả định ngầm về số chữ số thập phân cho một loại tiền tệ cụ thể khác với bạn, bạn có thể bị kẹt rất nặng. Điều này đặc biệt quan trọng với stablecoin, vì chúng thường có số chữ số thập phân ngầm định khác với tiền pháp định mà chúng đại diện
Với API dựa trên JSON, cũng đáng cân nhắc biểu diễn số tiền bằng kiểu chuỗi. JSON không chỉ định độ chính xác thập phân, nên bạn luôn phải kiểm tra rằng bản thân bạn và mọi người dùng/nhà cung cấp của bạn không bị parser/serializer nội bộ đi qua số dấu phẩy động rồi làm mất độ chính xác. Việc này có thể nhanh chóng trở nên lộn xộn, và chuỗi nhìn có vẻ kém gọn gàng hơn về mặt khái niệm, nhưng nó né hoàn toàn vấn đề này. Một số người sẽ gọi đây là anti-pattern [1], nhưng tôi không muốn chiến đấu cho sự thuần khiết ý thức hệ trên vai người dùng hay cổ đông
[1] https://blog.json-everything.net/posts/numbers-are-numbers-n...
Trong lĩnh vực giao dịch tần suất cao, nếu có thể chốt trước một số mũ nhất quán cho một {slice} nào đó thì có thể tiết kiệm dung lượng truyền. Ví dụ, trong một phạm vi như sản phẩm/kích thước tick/lớp tài sản/sàn giao dịch/feed/máy chủ, chỉ gửi phần định trị và client dùng số mũ đã hard-code
Nhưng ngay cả trong các lĩnh vực tương tự, nhiều khi việc gửi thêm một số mũ
uint32trong dữ liệu truyền là đáng giá. Như vậy sau này có thể thay đổi, và không bị mắc kẹt bởi thiết kế ban đầu kiểu “bây giờ chỉ cần cent là đủ”. Chẳng hạn, bạn có thể đột nhiên phải hỗ trợ giá bitcoin với toàn bộ độ chính xác. Người dùng sẽ biết ơn nếu không phải điều phối một thay đổi phá vỡ tương thích khi muốn điều chỉnh số mũ cố địnhTiêu chí “bất kỳ” là phi lý. Đó là một chuẩn không thể đạt được, có thể đòi hỏi ngân sách kỹ thuật vô hạn mà không có giá trị thực tế nào chứng minh được
Nhận diện sự thiếu vắng tiêu chuẩn, nói về cách các parser thực tế hoạt động, và thảo luận các khoảng trống cùng những use case chưa được đáp ứng thì tốt. Đề xuất cần một tiêu chuẩn hợp lý hơn cũng tốt. Nhưng yêu cầu mọi người hỗ trợ “mọi khả năng” mà chẳng ai thực sự cần, ý nghĩa cũng không rõ, và trên thực tế không thể đạt được, thì không phải là ý hay
float/doubledấu phẩy động chuẩn, số học fixed-point ở mức một phần nghìn của minor unit hoặc nhỏ hơn, số thập phân độ chính xác tùy ý, hay một cách hoàn toàn khác?Là một lập trình viên, khi nhìn các lập trình viên Fintech nói từ những trải nghiệm và góc nhìn khác nhau, tôi tự hỏi rốt cuộc lập trình giỏi là gì
Việc xlii nói không lưu số tiền bằng dấu phẩy động là vấn đề IEEE 754 quen thuộc. Theo dõi tài chính thì đúng là nên dùng log bất biến hoặc ghi chép dựa trên event, nhưng tôi không nghĩ cần biến toàn bộ các dịch vụ xung quanh thành event sourcing. Chỉ áp dụng cho logic cốt lõi như sổ cái, quyết toán, lệnh, khớp lệnh là đủ. Đọc bài của xlii, nó có vẻ như một kỹ thuật chỉ khả thi khi việc mô hình hóa thành công
Điều lxgr nói là chỉ ra vấn đề minor-unit. Nếu số JSON bị ngôn ngữ hoặc parser phân tích thành số dấu phẩy động thì có thể mất độ chính xác. Thông thường người ta gửi kèm giá trị và một trường số chữ số thập phân riêng. Tuy nhiên tôi nghe nói trong giao dịch tần suất cao, bản thân overhead đó quá đắt nên họ không làm vậy
Điều antonymoose nói khớp với nội dung nhiều sách đề cập. Vì vậy trong ngữ cảnh ngoại hối hoặc API, kiểu thiết kế này khá phổ biến. Nó cũng có cảm giác giống thiết kế giao thức
Tổng hợp lại thì ai cũng đúng trong domain của mình. Tôi nghĩ sẽ rất tốt nếu có một người như xlii làm lập trình viên senior, nhưng đồng thời tôi cũng cảm thấy có lẽ mình không thể thiết kế những hệ thống phức tạp như vậy. Theo nghĩa đó, lời của từng người đều có lý, và việc ý kiến phân hóa theo domain là điều thú vị. Có lẽ đây chính là chuyên môn
Nhìn những thứ như vậy, có thể phần nào suy đoán lập trình viên đến từ trải nghiệm nào. Đôi khi lập trình không giống việc tìm đáp án đúng, mà giống việc chọn một thế giới quan
Trên HN, việc xem các lập trình viên mô hình hóa domain của họ như thế nào luôn thú vị. Thỉnh thoảng tôi bấm vào hồ sơ, rồi thêm kiến thức domain của họ vào wiki cá nhân, nghĩ rằng biết đâu ngày nào đó sẽ dùng tới
Hay. Cuốn sách này đã chứa rất nhiều thông tin tốt mà cũng có thể tìm ở nơi khác, nhưng chỉ riêng việc gom chúng lại đã khá thực dụng rồi. Tôi rất khuyến nghị Designing Data-Intensive Applications của Kleppmann. Bản 1 đã rất hay, và gần đây bản 2 đã ra mắt
Tôi từng làm CTO trong FinTech và xây dựng toàn bộ software stack từ đầu; các bài học trong sách nhìn chung là đúng. Tôi nói “nhìn chung” vì như mọi khi, trong các dự án cụ thể phải cân nhắc rất nhiều yếu tố “tùy tình huống”. Ví dụ, tôi không dùng event sourcing để tránh bài toán tính toán toàn bộ trạng thái. Một audit trail append-only tiêu chuẩn cũng có thể là đủ
Không thể bảo đảm giao hàng đúng một lần, nhưng có thể cấu hình để thực tế xử lý một lần, và đó mới là thứ thực sự mong muốn
Lời khuyên lưu mọi request và response là hoàn toàn đúng. Không chỉ khi tiêu thụ API, mà cả khi thu thập bất kỳ thông tin nào từ thế giới bên ngoài cũng nên làm vậy; nếu có thể, trong phạm vi ranh giới của mình cũng nên ghi lại mọi bước chuyển đổi trung gian. Kết hợp bucket định địa chỉ theo nội dung và bảng quan hệ là tốt
Ngoài ra, phần nội dung không nói gì về dòng dõi dữ liệu. Nếu một nhà cung cấp cập nhật dữ liệu nào đó vào giữa trưa, và bạn nhất thiết phải biết việc đó thì sao? Bạn cần có thể giải thích thay đổi đó, đồng thời khi chạy lại các phép tính đã dùng giá trị cũ vẫn cho cùng kết quả. Đây không phải là vấn đề đặc biệt khó giải, nhưng cần suy nghĩ
Gọi là “lời khuyên tệ” thì vẫn còn khá lịch sự. Nói thật, phần lớn “sổ tay” này trông như do LLM viết
Ví dụ, trong phần về tính bất biến có câu như sau: “Nếu tách PII khỏi dữ liệu tài chính, bạn có thể tôn trọng quyền được xóa mà không mất lịch sử tài chính cần lưu giữ”
Trong các tổ chức tài chính, hai thứ này đi cùng nhau vì những lý do KYC/AML rất rõ ràng
Nếu bạn xóa ngay tên, địa chỉ khách hàng, v.v. theo yêu cầu trước khi thời hạn liên quan hết hiệu lực mà chỉ giữ lại dữ liệu tài chính, thì khi cơ quan hợp pháp đến yêu cầu dữ liệu để truy vết tội phạm, cả tổ chức sẽ có một ngày cực kỳ tồi tệ
Người muốn làm trong Fintech không nên dựa vào một “sổ tay” tùy ý do một người không rõ ở khu vực pháp lý nào viết
Người làm trong Fintech chỉ nên làm theo sổ tay/hướng dẫn nội bộ của nhà tuyển dụng, v.v. Những tài liệu đó hẳn đã được luật sư và nhân sự compliance của công ty cùng soạn thảo để đáp ứng luật pháp và yêu cầu báo cáo của các khu vực pháp lý nơi nhà tuyển dụng hoạt động
Theo tôi thấy, rốt cuộc bài viết khuyến nghị tách PII cần phải xóa khỏi dữ liệu mà bạn thực tế muốn lưu giữ vĩnh viễn vì nó nằm trong phương trình kế toán/các điều kiện bất biến. Như vậy sau khi thời hạn lưu giữ hồ sơ liên quan đã qua, bạn có thể xóa phần trước
Việc không nên dựa vào một “sổ tay” do một người không rõ ở khu vực pháp lý nào viết là đúng. Nhưng cũng không nên mù quáng phớt lờ các ý tưởng và thực hành trong đó, hoặc không nhìn ra ngoài tổ chức của mình. Lý tưởng nhất là đọc nó rồi đối chiếu với kiến thức của mình và quy định địa phương để điều chỉnh
Nếu thế giới chỉ có những tổ chức hoàn hảo, không lỗi, thì cách tiếp cận chỉ làm theo hướng dẫn nội bộ của nhà tuyển dụng có vẻ hợp lý. Nhưng nếu không có những cuộc trao đổi như thế này thì làm sao đạt đến mức đó?
Tôi cho rằng phần lớn nội dung này áp dụng không chỉ cho Fintech mà cho kỹ nghệ phần mềm nói chung
Ví dụ, các phần nói về retry, idempotency, thứ tự sự kiện, v.v. áp dụng cho mọi hệ thống cần một mức độ chính xác nhất định, dù tiền không trực tiếp liên quan. Tôi đã thấy quá nhiều hệ thống được xây dựng với giả định “cứ retry bất cứ lúc nào là được”, nhưng ngay từ đầu việc retry chỉ có thể thực hiện nếu hệ thống thất bại một cách sạch sẽ, và hệ thống con phải cung cấp mức idempotency như tôi nghĩ. Những phần này thường không được kiểm chứng thực tế
Tôi muốn đọc một bài ủng hộ cách tiếp cận cấp tiến hơn, như cơ sở dữ liệu theo từng tài khoản chẳng hạn. Kiểu có những đánh đổi riêng trong Fintech
Lời khuyên tôi muốn dành nhất cho kỹ sư hoặc founder Fintech là hãy xem rủi ro và compliance một cách nghiêm túc ngay từ ngày đầu tiên
Hệ thống tài chính dựa trên niềm tin. Nếu không thể giảm thiểu rủi ro một cách có thể chứng minh được, bạn sẽ mất niềm tin, và cuối cùng mất cả doanh nghiệp
Nói rằng phải tránh số dấu phẩy động là không đúng. Tôi đã làm trong Fintech 20 năm và phần lớn dùng double. Excel cũng dùng double, frontend cũng sẽ dùng double, và mọi cơ sở dữ liệu đều hỗ trợ double. Thư viện chuẩn biết cách parse double, còn JSON, dù về lý thuyết không hẳn, trên thực tế cũng dùng double. Nhiều hệ thống ERP cũng dùng double
Điểm cốt lõi khi xử lý tiền tệ bằng double là phải nhớ rằng nó có thể chứa tổng cộng 15 chữ số độ chính xác. Miễn là số của bạn không dùng nhiều chữ số hơn thế, như
123456789.01hay123.456789, bạn có thể có độ chính xác thập phân hoàn hảo trong tính toán tài chính. Chỉ cần luôn làm tròn kết quả vào trong phạm vi 15 chữ số độ chính xác sau mỗi phép tính và trước mỗi phép so sánh. Excel làm như vậyƯu điểm lớn nhất của double là được hỗ trợ rộng rãi, và có thể trộn các độ chính xác khác nhau trong cùng một hệ thống. Khi xử lý tài chính quốc tế hoặc các sản phẩm tài chính nâng cao, điều đó sẽ xảy ra. Một số nghiệp vụ kế toán cần độ chính xác đến phần nghìn, một số khác phải làm tròn theo bội số của 0,25. Cuối cùng bạn sẽ không dùng số học cơ bản mà dùng một thư viện toán học kế toán chuyên biệt, và thư viện đó hoàn toàn có thể dùng số dấu phẩy động làm backend
Kiểm tra số dư Plaid không đảm bảo một khoản ghi nợ ACH sắp gửi sẽ thành công
Số dư có là một triệu đô la cũng không quan trọng. Trước khi ACH được xử lý, toàn bộ tiền có thể (a) bị rút ra bằng wire transfer, (b) được clearing bởi các ACH của ngày hôm qua, tức hóa đơn, ghi nợ tự động, v.v. cùng với séc, hoặc (c) được dùng qua thẻ ghi nợ/ATM
Có lẽ tốt nhất tôi không nên nói vì sao tôi biết một số Fintech không xử lý việc này
Chỉ riêng phần idempotency key đã đáng đọc. Phần lớn lập trình viên học bài học đó theo cách vất vả
Nhiều trong số đó ra đời trước khi hiểu biết về idempotency được phổ biến rộng rãi, nên thường phải cưỡng ép tạo idempotency key bằng cách nối nhiều trường có vẻ là duy nhất toàn cục. Vấn đề là nó không bao giờ hoàn toàn duy nhất. Thỉnh thoảng có thể nhìn thấy phía sau tấm rèm, ví dụ khi ngân hàng không cho chuyển cùng một số tiền đến cùng một tài khoản nhận trong cùng một ngày
Tôi đã dành rất nhiều thời gian để giải thích idempotency nên hoạt động như thế nào và vì sao nó quan trọng. Hầu hết các đội hiểu nhu cầu, nhưng rất ít đội nghĩ đến nó ngay từ đầu
“Fintech” là một phạm vi rất rộng, và phần lớn những gì được gọi là Fintech thực ra là truyền thông. Đó là truyền thông giữa các công ty, giữa các trader, giữa các hệ thống, giữa các ledger. Không có cách lập trình “đúng” áp dụng cho toàn ngành. Rốt cuộc, cách đúng là cách mà đối tác tôi đang giao tiếp có thể hiểu
Nếu đối tác theo dõi tiền tệ đến đơn vị cent, mà bạn theo dõi với độ chính xác cao hơn, sẽ phát sinh sai lệch làm tròn. Ngược lại, nếu tôi dùng đơn vị cent còn đối tác xử lý đến 0,1 cent thì cũng vậy. Mọi lời khuyên khác trong tài liệu này cũng nên được nhìn theo cách đó