0050: Event Sourcing for Audit Trail
Date: 2025-11-22
Status: Accepted
Service: book-keeper
Context
Financial systems face stringent regulatory requirements:
- Complete, immutable audit trail of all transactions
- Ability to reconstruct account balances at any point in time
- Tamper-proof record of who did what, when
- Compliance with standards like SOX, GDPR, financial regulations
Traditional CRUD systems struggle with these requirements:
- Updates destroy historical state
- Audit logs are separate from data, can be inconsistent
- Time-travel queries are difficult or impossible
- Proving data integrity is complex
Financial transactions in Book-Keeper must have the highest level of auditability and traceability.
Decision
The Book-Keeper write-side will use Event Sourcing as its core pattern:
-
Events as Source of Truth: All state changes are captured as immutable domain events (e.g.,
JournalEntryPosted,AccountDebited,AccountCredited) -
Event Store: Events are persisted to an append-only event store (see ADR-0048 for storage options)
-
Aggregate Reconstruction: Current state of any aggregate (e.g.,
Journal,Account) is derived by replaying its event stream -
Audit Trail: The event stream IS the audit trail - no separate logging required
-
Immutability: Events are never updated or deleted, only appended
Implementation Pattern
# Command → Events → Persistence
def handle_record_journal_entry(command: RecordJournalEntryCommand):
journal = Journal.create(...) # Creates JournalCreated event
journal.post() # Creates JournalEntryPosted event
event_store.save(journal.id, journal.uncommitted_events)
# Events are immutable, append-only
Write-Side vs Read-Side
- Write-side: Event Store (append-only, optimized for writes)
- Read-side: Projections (denormalized views, optimized for queries)
This is CQRS (Command Query Responsibility Segregation).
Consequences
Positive
- Perfect Audit Trail: Every state change is recorded with full context (timestamp, user, command data)
- Time Travel: Can reconstruct state at any point in history by replaying events up to that point
- Regulatory Compliance: Immutable event log satisfies audit requirements
- Bug Recovery: If a projection has incorrect data, can rebuild it from events
- Event Replay: Can apply new business rules to historical events
- Debugging: Can see exact sequence of events that led to current state
Negative
- Learning Curve: Event Sourcing is a paradigm shift from CRUD, requires training
- Complexity: More moving parts (commands, events, aggregates, projectors)
- Storage Growth: Event store grows indefinitely (though compaction strategies exist)
- Eventual Consistency: Read-side projections may lag behind write-side events
- Schema Evolution: Changing event schemas requires versioning andmigration strategies
Neutral
- Snapshots: For long-lived aggregates with many events, may need snapshots to optimize replay
- Event Bus Integration: Events can be published to event bus for downstream consumers
- Testing: Event-sourced systems are highly testable (given events, assert state)
Implementation Notes
Event Schema Versioning
Events must be versioned to handle schema changes:
{
"event_type": "JournalEntryPosted",
"event_version": "v1",
"data": {...}
}
Idempotency
Event store must enforce idempotency (same event not applied twice). See Decision #15 in 04-decisions.md.
Privacy/GDPR
For PII in events, consider:
- Encryption at rest
- Separate PII into different aggregates (can be deleted independently)
- Pseudonymization
Related ADRs
- ADR-0048: Swappable Storage Adapters - Event store implementations
- ADR-0049: Event-Driven Integration Pattern - Publishing events externally