Skip to main content

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:

  1. Events as Source of Truth: All state changes are captured as immutable domain events (e.g., JournalEntryPosted, AccountDebited, AccountCredited)

  2. Event Store: Events are persisted to an append-only event store (see ADR-0048 for storage options)

  3. Aggregate Reconstruction: Current state of any aggregate (e.g., Journal, Account) is derived by replaying its event stream

  4. Audit Trail: The event stream IS the audit trail - no separate logging required

  5. 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