事件溯源(Event Sourcing):如何建模不同事件间的关联关系?
Great question—this is a common, critical point of confusion when starting with Event Sourcing, especially in financial domains where auditability, traceability, and consistency are non-negotiable. Let’s break this down step by step.
1. Deposit Scenario: Should You Link account.deposited to deposit.created?
Short answer: Yes, absolutely.
Including a reference to the deposit.created event’s UUID (or better yet, a dedicated depositId that identifies the Deposit entity) in the account.deposited event is essential for three key reasons:
- Auditability: Financial systems require a clear paper trail. If someone later asks, "Why was $100 added to account X?", you need to trace that change back to the original deposit initiation event.
- Consistency Validation: When replaying events to rebuild state, you can verify that every
account.depositedhas a correspondingdeposit.createdevent (and vice versa), catching any orphaned or invalid events that could corrupt account balances. - Avoid Duplication: If events are ever reprocessed (e.g., due to a system glitch), the
depositIdlets you skip duplicateaccount.depositedevents tied to the same deposit.
Example Event Structures:
deposit.created:{ "eventId": "deposit-evt-123", "type": "deposit.created", "depositId": "deposit-456", "accountId": "acc-789", "amount": 100.00, "timestamp": "2024-05-20T14:30:00Z" }account.deposited:{ "eventId": "acc-evt-001", "type": "account.deposited", "accountId": "acc-789", "amount": 100.00, "depositId": "deposit-456", "causationId": "deposit-evt-123", // Directly links to the triggering event "timestamp": "2024-05-20T14:30:01Z" }
Note: Using causationId to reference the direct parent event (here, deposit.created) is a standard Event Sourcing pattern for tracking event lineage.
2. Transfer Scenario: Event Correlation Best Practices
Transfers are more complex because they involve two accounts (debit and credit) and a central Transfer entity. The goal is to tie all related events to a single transfer context so you can trace the entire flow from initiation to completion (or failure).
Recommended Event Flow & Correlation:
When executing a bank.transfer command, you’ll typically generate a sequence of events like this, all linked by a shared transferId:
transfer.initiated: Marks the start of the transfer. IncludestransferId,fromAccountId,toAccountId,amount, and any metadata (e.g., user reference).account.withdrawn: Records the debit from the source account. IncludesaccountId,amount,transferId, andcausationIdpointing totransfer.initiated.account.deposited: Records the credit to the target account. IncludesaccountId,amount,transferId, andcausationIdpointing totransfer.initiated.transfer.completed: Confirms the transfer is fully processed. IncludestransferIdand references to theaccount.withdrawnandaccount.depositedevent IDs for full traceability.
Handling Failure Scenarios:
If the transfer fails (e.g., insufficient funds in the source account), you’d generate:
transfer.failed: IncludestransferId,reason, andcausationIdpointing totransfer.initiated.account.refunded(if funds were temporarily withdrawn but need to be reversed): Links back to thetransferIdand the originalaccount.withdrawnevent.
Key Pattern: Correlation IDs
For end-to-end traceability across multiple events, use two standard fields:
correlationId: A single ID that ties all events in a business process together (e.g., all events from a single transfer command). This is useful for debugging or auditing entire workflows.causationId: The ID of the event that directly triggered the current event (e.g.,account.withdrawn’s causation ID istransfer.initiated’s event ID). This tracks the direct lineage of events.
Final Takeaway
In financial Event Sourcing systems, event correlation isn’t just a nice-to-have—it’s a requirement. Linking events via IDs ensures you can audit every transaction, validate state consistency during replay, and debug issues quickly. For both deposits and transfers, always tie related events together using dedicated entity IDs (like depositId or transferId) and lineage fields (causation/correlation IDs).
内容的提问来源于stack exchange,提问作者Calvin Lau

