12 mins read

Event Sourcing: When to Use It and How to Design It

Event sourcing stores changes as an ordered history of business events instead of keeping only the latest state of each record. An order system might record “OrderPlaced,” “ItemAdded,” “PaymentAuthorized,” and “OrderShipped,” then derive the current order from that history. The approach gives teams a durable view of what happened, but it adds design and operational complexity. It fits best when history and reconstruction matter enough to justify that cost.

This guide explains the pattern, its difference from ordinary event messaging, the tradeoffs to evaluate, and a practical adoption path.

How event sourcing works

In a conventional application, a database row often stores the current values. When an order changes, the system updates its status and may discard the previous value. With event sourcing, the application appends a new event that describes a business change to an event stream for that entity. The current state can then be rebuilt by applying the events in order.

For example, an account’s balance might be derived from “FundsDeposited” and “FundsWithdrawn” events. The event history preserves the sequence; a projection can provide a fast balance view for the user interface. Martin Fowler’s event sourcing overview describes the core idea of capturing each state change as an event. Microsoft’s architecture pattern guide also stresses that teams should consider the pattern’s cost and complexity before adopting it.

Events, event streams, and projections

An event should describe something that happened in a domain, usually in the past tense. “PaymentAuthorized” communicates business meaning; “StatusChangedTo3” reveals an implementation detail but not why the change occurred. Good event names help developers, analysts, and auditors understand the history.

An event stream is the ordered history for an entity, such as a specific order or account. A command handler loads enough history to check a new action against business rules, then appends the next event. A projection reads events and maintains a query-friendly view. Different projections can answer different questions without changing the source history.

Projections may update asynchronously. That means a user could see a brief delay between an accepted command and an updated read view. The product should define how it handles that delay: show a pending state, return the result from the command, or use a read-your-writes path for a critical screen.

Event sourcing is not the same as a message broker

A message broker distributes messages between services. An event store preserves an entity’s authoritative history and supports reading that stream. A broker can deliver integration events to consumers, but it may not provide the stream queries, optimistic concurrency checks, and replay semantics your domain needs. Teams should avoid assuming that using a broker automatically means they have event sourcing.

Likewise, event sourcing does not require every event to trigger asynchronous workflows. A system can record an event synchronously and update some local state in the same request. Decoupled consumers are useful when they fit the domain, but they introduce eventual consistency and more failure modes.

Benefits worth considering

  • Business history: teams can answer what happened and in what order, instead of inferring from a current record.
  • Reconstruction: rebuilding a projection can correct a bug in a derived view or create a new view from historical data.
  • Audit support: an append-only record can make changes easier to trace, though it still needs access controls and retention rules.
  • Integration: selected events can notify other services about domain changes.
  • Analysis: the event history can support questions about process paths that a current-state table cannot answer alone.

These benefits are not automatic. Teams still need event ownership, durable storage, monitoring, privacy controls, and a clear policy for correcting bad events.

Costs and risks

Schema evolution

Events may live for years, while application code changes. Consumers must handle older event versions. Teams can use explicit versioning, tolerant readers, upcasters, or carefully planned migrations. Rewriting the original history should remain a last resort because it weakens the value of an immutable audit trail.

Concurrency and ordering

Two requests may try to update the same entity at once. Use optimistic concurrency or another explicit rule to ensure the second command checks the latest stream state. Define ordering within an entity’s stream; do not assume timestamps alone resolve every race.

Replay and external side effects

Rebuilding a projection by replaying history must not resend payments, emails, or other irreversible actions. Separate projection handlers from side-effecting consumers and make external operations idempotent. Test replay against production-like volumes before relying on it as a recovery method.

Privacy and deletion

Immutable history can conflict with privacy, retention, or correction requirements. Avoid storing unnecessary personal details in events. Use references to separately managed data when possible, and define how to remove or redact sensitive data while preserving system integrity.

Where the pattern tends to fit

Consider event sourcing for domains with meaningful state history, such as financial ledgers, inventory movements, approval trails, or complex workflows that need reconstruction. It may also fit a bounded part of a larger application while other domains use ordinary tables.

It is usually a poor default for simple create-read-update-delete data, low-risk settings, or teams without the operational capacity to manage event streams and projections. If the business only needs a basic audit log, a targeted audit trail may meet the need with less complexity. For an application modernization effort, evaluate the domain and migration and cutover requirements before changing the storage model.

A safe implementation sequence

  1. Choose a domain where business history has clear value.
  2. Define event names around business intent and keep payloads minimal.
  3. Specify stream ownership, concurrency behavior, and event ordering.
  4. Build one projection and verify replay, correction, and rebuild behavior.
  5. Make consumers idempotent and isolate external side effects.
  6. Test schema upgrades, backups, access controls, retention, and disaster recovery.
  7. Expand only after the team can operate the pattern in production.

Track projection lag, failed appends, consumer retries, event volume, and replay duration. These measures help operations see whether the system remains healthy as history grows.

Conclusion

Event sourcing changes a system’s source of truth from current state to a sequence of domain changes. That can unlock useful auditability and reconstruction, yet it also asks teams to manage event evolution, projections, replay safety, and consistency. Start with a narrow domain and use the pattern only when the business value clearly outweighs the new operating model.

Design event contracts that can survive change

Before storing an event, decide which fields express business intent and which are merely implementation detail. Include identifiers needed to associate an event with its entity and workflow, plus a version or schema identifier if consumers need it. Keep payloads small enough to avoid copying entire customer records into an immutable stream. If downstream systems need a broad data object, they can query an approved source or receive a purpose-built integration event.

Schema evolution requires a policy. An additive optional field is often easier for old consumers to tolerate than a renamed or reinterpreted field. When meaning changes, create a new event type or version and define how old history is read. Build replay tests across representative historical versions before upgrading production handlers. Document deprecation windows and identify which consumers still depend on older formats.

Separate history from side effects

Projection rebuilds should be repeatable. Sending an invoice, charging a card, or emailing a customer should not happen again just because a view needs to be regenerated. Keep projection handlers deterministic where possible. For side-effecting consumers, store a durable processing record or use an idempotency key so a retry does not repeat the action. Add a reconciliation process for a consumer that is down while events continue to arrive.

Plan for correction and retention

People will enter incorrect data, and integrations may produce invalid events. Define how the domain corrects history. Often the right response is a compensating event that records the correction while retaining what happened earlier. This approach preserves audit context, but it does not eliminate the need to handle privacy obligations or retention schedules. Minimize personal data in events and use controlled references when sensitive details may need separate management.

Operational dashboards should show append conflicts, consumer lag, projection freshness, failed handlers, storage growth, and replay time. Set an owner for each event stream and its projections; without ownership, a permanent event log can become an expensive data lake no one understands.

Migration questions to answer before adoption

Event sourcing changes the source of truth, so a migration needs a clear boundary. Decide which aggregate or business entity owns a stream, how existing records become an initial state, and whether you need a historical backfill or only events from the cutover date forward. Reconcile counts and key values between the old store and the new projection before switching users to the new write path.

During a transition, dual writes can diverge if one write succeeds and the other fails. Prefer a migration plan with an explicit source of truth and reconciliation process. If you publish events from a relational transaction, use an outbox or another reliable handoff so the database commit and message publication cannot drift silently. Test retries and delayed consumers.

Plan snapshots or checkpoints when replaying a long stream becomes expensive. A snapshot is a derived convenience, not a replacement for the authoritative history. Verify that a snapshot can be rebuilt and that its version matches the event schema and projection code that produced it.

Use a small operational runbook for append conflicts, corrupt events, stuck projections, and replay requests. Require review before replaying a side-effecting consumer. Teams should also test backup restoration and stream export; a pattern is only dependable if the organization can recover it under pressure.

Keep queries and projections understandable

List the questions each projection must answer before building one. A customer support screen may need the current status and a short history; an analyst may need daily totals. These read models can have different storage and update schedules. Document whether a projection is eventually consistent and show its “as of” time when a delay could confuse a user.

Rebuilds should be tested as operational jobs. Estimate how long a full replay would take, how much temporary storage it uses, and whether the live consumer can continue while a projection catches up. Provide a checkpoint and a way to resume after a failure. Compare rebuilt totals with known control values before directing user traffic to a new projection.

Use event naming and documentation that domain experts can review. Ask them whether the event describes a true business fact or merely a database operation. Their input helps prevent low-level technical events from becoming permanent records that fail to answer business questions. Keep the event catalog searchable, with owners and schema history.

Frequently Asked Questions

Is event sourcing the same as event-driven architecture?

No. Event sourcing stores domain changes as the authoritative history. Event-driven architecture describes systems that communicate or trigger work through events. A system can use one pattern without fully adopting the other.

Does event sourcing require CQRS?

No. CQRS separates command and query models, while event sourcing changes how state is stored. Teams often combine them, but doing so adds more models and consistency concerns.

Can a message broker act as an event store?

A broker may distribute events, but an event store also needs the domain’s required stream history and concurrency semantics. Evaluate the actual guarantees rather than treating the terms as interchangeable.

Should every business application use event sourcing?

No. It suits domains where the history itself has lasting value. For simple data management, conventional persistence with a focused audit log is often easier to build and operate.

Leave a Reply

Your email address will not be published. Required fields are marked *