Event Sourcing Under MiFID II: When the Complexity Pays for Itself
Regulators fined Wall Street firms $1.8 billion for missing records. Not fraud — records. Event sourcing is complex and expensive. It's also the best architecture to answer 'prove what happened' from the data itself.
In 2022, the SEC and CFTC fined Wall Street firms a combined $1.8 billion for record-keeping failures. Not insider trading. Not market manipulation. Missing records. Employees had used encrypted messaging apps without audit trails, and when the SEC asked the firms to prove what had been communicated, they couldn’t. The firms were not penalised for what was said. They were penalised for the inability to demonstrate what was said.
MiFID II asks the same question of every regulated trading firm in the EU: can you reconstruct any trade within 72 hours? Can you produce timestamps to the precision RTS 25 demands — microsecond granularity for algorithmic trading, millisecond for most other activity? Can you retain the records for five years — seven if the regulator requests it? The question isn’t “do you have an audit log?” The question is “can you prove what happened at 14:32:15 UTC three years ago, and can you prove nobody changed the record since?”
I’ve written before about why we chose event sourcing for a regulated trading platform — the high-level argument that the audit trail should be a property of the architecture, not a feature bolted on. This post goes deeper into the specific patterns that made it work, and why no simpler alternative could adequately satisfy the regulatory requirement.
Logging is not proving
Martin Fowler describes event sourcing as capturing all changes to application state as a sequence of events. What that definition doesn’t emphasise is the regulatory consequence: the sequence becomes evidence.
Most systems log what happened. They write to a database, then separately write an audit record describing the change. The business data can be updated or deleted. The audit log is a second-class citizen — maintained in parallel, hoped to be consistent, rarely verified. Under regulatory inspection, this falls apart. If the business state was modified directly — even accidentally, even through a migration script — the audit log and the data tell different stories. The regulator doesn’t know which to trust. Neither do you.
Event sourcing reverses the hierarchy. The sequence of events is the authoritative record. The current state is derived from replaying those events. There is no “business data” that can be modified independently — the log IS the data. Greg Young, who codified the pattern, noted in his 2014 Code on the Beach talk that in heavily regulated environments, regulators respond well to an append-only, immutable log with cryptographic verification. The reason is structural: the architecture eliminates the class of problems where business state and audit trail diverge.
The pattern that earned its complexity
The strongest pattern we built — and the one we wouldn’t have discovered from a tutorial — was two-phase auditing.
Every command from our back-office to our core trading platform is audited in two phases. The intent — what the user requested — is recorded synchronously, before the command executes. If the audit write fails, the command is rejected. HTTP 503. No exceptions. The outcome — what actually happened — is recorded asynchronously after execution. If the outcome audit fails, the authoritative event log inside the core trading platform still has the record, and the outcome can be reconstructed later.
The asymmetry is deliberate. MiFID II Article 16 requires recording communications intended to result in transactions before execution occurs — you must know who requested what before the system acts on it. The intent phase is the compliance gate. The outcome phase is the operational record.
This creates an edge case worth naming: an intent record exists, but the command failed at the infrastructure level before executing — no outcome, no event log entry. We can handle this through a reconciliation process that periodically scans for orphaned intents and marks them as failed. We can handle it immediately on a timeout or error event. The regulator sees a complete picture: every intent, every outcome, and every intent that never reached outcome — with the reason recorded.
This is Gregor Hohpe’s Architect Elevator in practice — the two-phase pattern connects the regulatory requirement at the top with the non-blocking write at the bottom. Both floors get what they need because the architecture separates the concern rather than conflating it.
Binary compliance and tamper evidence
Two supporting patterns complete the picture.
We run a binary compliance posture: the system is either 100% compliant or it stops accepting commands. If the audit database is unreachable, if user identity can’t be resolved — the system returns 503. There is no degraded mode where “most” of the audit trail is captured. Neal Ford and Mark Richards frame architectural decisions through fitness functions — measurable criteria that determine whether a design is earning its keep. Our compliance fitness function is simple: zero audit gaps, or zero commands accepted. We chose back-office downtime over non-compliance. In regulated finance, that’s the right trade.
Every audit event includes a cryptographic hash of its content and a reference to the hash of the previous event. Modifying any record breaks every subsequent hash. This is the difference between “we logged it” and “we can prove it hasn’t been tampered with since.” One caveat: hash chaining detects tampering but doesn’t prevent it on its own — an insider with write access could rebuild the chain. One good solution is to anchor hashes externally at regular intervals.
Apply it surgically
Event sourcing adds real complexity: immutable schemas require versioning strategies, projections introduce eventual consistency, and running event-sourced and CRUD domains in the same system means you can’t join across them easily.
The answer — and this maps directly to Sam Newman’s thinking on service decomposition — is to follow the compliance boundary, not the service boundary. The domains the regulator will ask about — order execution, trade lifecycle, risk decisions, position changes — get event sourcing. Everything else — user profiles, reference data, etc. — can be CRUD. The complexity pays for itself in the regulatory domains and costs you in the domains where nobody will ever ask for reconstruction.
One tension the tutorials skip: GDPR’s right to erasure conflicts directly with an immutable event log. We handle this through crypto-shredding — encrypting personal data in events with a per-subject key and destroying the key on erasure request. The data remains in the store but becomes cryptographically unreadable. The event stream’s integrity is preserved. Whether this fully satisfies GDPR is legally debated, but it’s an established pattern in regulated systems that must balance immutability with erasure rights.
The alternatives and when they’re enough
Change Data Capture captures what changed at the row level — but not why. Temporal tables support time-travel queries but record state snapshots, not business events. Append-only tables provide immutability without the full event sourcing machinery. Each has a place. None answers the hardest regulatory question — “prove the complete lifecycle of this trade placed by a dealer on behalf of client X, from intent through execution, with cryptographic integrity” — as cleanly as a genuine event-sourced system.
Event sourcing is complex. It’s expensive. It constrains your design in ways that are difficult to reverse. And when a regulator asks “prove what happened” — not “show me the log,” but “prove it, with integrity guarantees, going back years” — it’s probably the best architecture to glean an answer from the data itself. The complexity isn’t the price of elegance. It’s the price of evidence.