Migration Guide
Migrate from Event Sourcing to ATOMiK
Event sourcing gives you a complete history. ATOMiK gives you instant current state. This guide shows how to evaluate replacing hot event replay paths with accumulator-model state reconstruction, reduce snapshot pressure, and simplify fit-model undo -- and when to keep your event log.
The fundamental shift
Event sourcing stores every change and replays them to reconstruct state. ATOMiK accumulates changes into a single value and reconstructs state in one operation: current_state = initial_state ⊕ accumulator. In fit workloads, you trade history for a faster hot-state path: accumulator reads instead of replay, fixed-width accumulator state instead of linear hot-state growth, and algebraic undo via self-inverse deltas.
Event Sourcing to ATOMiK Mapping
Some event sourcing concepts have accumulator-model counterparts in ATOMiK.
| Event Sourcing | ATOMiK | Notes |
|---|---|---|
| Event Log | Accumulator | Event sourcing appends every event to an ordered log (linear growth). In the fit model, ATOMiK XOR-accumulates deltas into a fixed-width accumulator value. |
| Event Replay | ctx.read() | ES replays all events since the last snapshot to reconstruct state: O(n). ATOMiK reconstructs with one accumulator read in the fit model: initial_state XOR accumulator. |
| Snapshot | ctx.swap() | ES periodically snapshots to bound replay cost. ATOMiK swap() atomically captures current state and resets the accumulator -- no serialization needed. |
| Compensating Event | Re-apply same delta | ES undo requires publishing a compensating event with reversal logic. ATOMiK undo is algebraic: XOR is self-inverse, so re-applying the same delta reverses it. |
| Projection / Read Model | ctx.read() | ES projects events into read-optimized views (CQRS). ATOMiK read() returns current state directly -- no separate read model needed. |
| Event Schema Evolution | Fixed-size delta | ES events require versioning and upcasting as schemas evolve. ATOMiK deltas are fixed-size integers -- no schema to evolve. |
Pattern 1: Reduce Hot-Path Event Replay
The most impactful migration candidate is a hot read path where replay latency or snapshot pressure is already measurable.
Before: Event Sourcing
class Account:
def rebuild_from_events(self, event_store):
# Load latest snapshot (if any)
snap = event_store.latest_snapshot(self.id)
if snap:
self.state = snap.state
start = snap.version
else:
self.state = 0
start = 0
# Replay all events since snapshot
events = event_store.load(
self.id, since=start
)
for e in events: # O(n)!
self.apply(e)
# Maybe snapshot for next time
if len(events) > 1000:
event_store.save_snapshot(
self.id, self.state
)After: ATOMiK
from atomik_core import AtomikContext
class Account:
def __init__(self):
self.ctx = AtomikContext()
self.ctx.load(0)
def apply_change(self, delta):
self.ctx.accum(delta)
def current_state(self):
# Accumulator-model read; no event replay here.
return self.ctx.read()
def checkpoint(self):
# Atomic snapshot + reset
return self.ctx.swap()
# Event store can remain for audit/history.
# Hot read path avoids replay scheduling.Pattern 2: Simplify Fit-Model Compensation
In event sourcing, undo requires designing and publishing a compensating event with reversal semantics. In ATOMiK, undo is algebraic when the state change is represented as a fit XOR delta.
Before: Compensating Events
# To undo a transfer, publish a reversal
def cancel_transfer(original_event):
# Must design reversal logic
compensation = TransferReversed(
from_acct=original_event.to_acct,
to_acct=original_event.from_acct,
amount=original_event.amount,
reason="cancellation",
ref=original_event.id
)
event_store.publish(compensation)
# Log grows. Complexity grows.
# Every event type needs a reversal.After: ATOMiK Self-Inverse
# To undo: apply the same delta again
def cancel_transfer(ctx, original_delta):
ctx.accum(original_delta)
# For fit XOR deltas, that's the accumulator undo.
#
# XOR is self-inverse:
# state XOR delta XOR delta = state
#
# No reversal logic to design.
# No compensating event schema.
# No additional storage.
# Validate domain semantics before replacing events.
# Algebraic property:
# a XOR a = 0 (proven in Lean4)Trade-off Comparison
Event sourcing and ATOMiK optimize for different things. This table shows where each approach wins.
| Dimension | Event Sourcing | ATOMiK |
|---|---|---|
| State reconstruction | O(n) -- replay from last snapshot | O(1) in the accumulator model -- initial XOR accumulator |
| Storage growth | Linear -- every event stored forever | Fixed-width accumulator for hot state; audit history must stay elsewhere |
| Undo / Compensation | Manual compensating events | Algebraic for fit XOR deltas -- self-inverse |
| Full audit trail | Yes -- every event preserved with ordering | No -- deltas are accumulated, not stored individually |
| Temporal queries | Yes -- replay to any point in time | No -- only current state is available |
| CQRS pattern | Native -- separate command and query models | Hot read model can be simplified when the state fits the accumulator model |
| Ordering requirements | Strict -- events must be totally ordered | Accumulator deltas commute; business ordering remains workload-specific |
| Infrastructure complexity | High -- event store, projectors, snapshot store | Small accumulator API; integration and audit needs remain workload-specific |
When Event Sourcing Is Still Better
ATOMiK is not a universal replacement. These are legitimate reasons to keep your event log.
Regulatory audit trails
Financial services, healthcare, and compliance domains often require a complete, ordered, tamper-evident record of every state change. ATOMiK accumulates deltas into a single value -- individual events are not preserved. If you must answer "what happened at 14:32:07 on March 3rd?", keep your event log.
Temporal queries / time travel
Event sourcing lets you reconstruct state at any historical point by replaying events up to that timestamp. ATOMiK provides only the current state. If your domain requires "show me the account balance as of last Tuesday", event sourcing is the right tool.
Complex domain event choreography
If your system relies on event-driven sagas, process managers, or cross-aggregate reactions where the semantics of individual events matter (OrderPlaced triggers InventoryReserved triggers PaymentCharged), ATOMiK's opaque deltas cannot replace meaningful domain events.
Existing projections that work well
If your CQRS read models are performant and your event store handles representative load without issues, migration cost may exceed benefit. ATOMiK is most valuable when replay latency, snapshot overhead, or storage growth are actual pain points.
The Hybrid Approach
You do not have to choose exclusively. Many teams keep event sourcing for audit-critical aggregates while using ATOMiK for hot-path state where performance matters most.
# Hybrid: event sourcing for audit trail,
# ATOMiK for real-time state queries
class HybridAggregate:
def __init__(self):
self.event_store = EventStore() # audit trail
self.ctx = AtomikContext() # hot-path state
self.ctx.load(0)
def apply(self, event, delta):
# Write path: both systems
self.event_store.append(event) # for compliance
self.ctx.accum(delta) # for speed
def current_state(self):
# Hot read path via ATOMiK accumulator
return self.ctx.read() # no replay on this path
def audit_at(self, timestamp):
# Time travel: via event store
return self.event_store.replay_to(timestamp)Key takeaway
Event sourcing optimizes for understanding the past. ATOMiK optimizes for knowing the present. If you need both, use both -- ATOMiK on the hot read path, event sourcing for audit and temporal queries. Validate the write-path overhead and replay reduction against the actual workload before making a production claim.