Event Sourcing Template
Overview
Event Sourcing is Talon's High Availability model that provides the best performance for latency-sensitive applications. With Event Sourcing, Talon replicates inbound messages rather than state changes. Each instance processes the same sequence of messages deterministically to maintain identical state and produce identical outputs.
How Event Sourcing Works
With Event Sourcing:
Inbound messages are replicated - Messages are replicated to backup instances in parallel with processing
State is application-managed - Your state is stored in POJOs, opaque to the Talon runtime
Deterministic processing ensures consistency - All instances execute identical code on identical inputs
No state modeling required - State can be any Java objects (ADM modeling optional)
Lower replication overhead - Messages replicated in serialized form without re-serialization
Key Features
Application-Controlled State: Complete control over state structure and management
Opaque State: Runtime doesn't inspect or manage your state
Low Latency: Messages replicated in parallel with handler execution
Efficient Replication: Received messages already serialized, no re-serialization cost
Full Flexibility: Use any Java objects for state, no ADM constraints
Deterministic Recovery: State reconstructed through message replay
When to Use Event Sourcing
Event Sourcing is ideal when:
Ultra-low latency is critical (sub-millisecond requirements)
You need full control over state management
State logic is complex or involves custom algorithms
Your state doesn't fit ADM modeling constraints
You want to use standard Java POJOs
Replication overhead must be minimized
Comparison with State Replication
State Management
Application manages (opaque POJOs)
Runtime manages (transparent)
State Modeling
No modeling required
ADM XML required
Replication
Inbound messages replicated
State deltas replicated
Latency
Lower (parallel replication)
Slightly higher (state tracking)
Complexity
More complex (determinism requirements)
Simpler application code
Recovery
State reconstructed by message replay
State reconstructed from log
Discipline Required
High (must avoid divergence)
Low (runtime enforces consistency)
See Consensus Models for detailed comparison.
Building an Event Sourcing Microservice
Creating an Event Sourcing microservice involves five main steps:
Model your application messages using ADM
Annotate your main class for Event Sourcing
Manage microservice state in POJOs
Write deterministic message handlers
Configure storage (clustering and persistence)
Step 1: Model Application Messages
Define your microservice's messages using the ADM modeling language in your model.xml file. Unlike State Replication, you don't need to model your state in ADM - only your messages.
Example Message Model:
See The Modeling Language for complete modeling guide.
Step 2: Annotate Main Class for Event Sourcing
Use the @AppHAPolicy annotation to declare that your microservice uses Event Sourcing:
Step 3: Manage Application State
With Event Sourcing, your state is completely private to your application. You can use any Java objects - POJOs, collections, or even third-party data structures:
Important Points:
State is completely private to your application
No ADM modeling required for state
Runtime never inspects or manages your state
State must be reconstructed deterministically on recovery through message replay
Step 4: Write Deterministic Message Handlers
Message handlers must be deterministic - they must produce identical state and outputs given identical inputs. Handlers receive only the inbound message (not state, since state is private).
Critical: Preventing Divergence
Event Sourcing requires special discipline to avoid state divergence between primary and backup:
Never use non-deterministic operations in handlers:
System.currentTimeMillis()- Useengine.getEngineTime()insteadSystem.nanoTime()- Not replicated, will cause divergenceRandom numbers - Will differ on backup
External I/O or database calls - Results not replicated
Environment variables - May differ between instances
Clock-dependent logic - Clocks may be skewed
Safe Practices:
Use
engine.getEngineTime()- Provides deterministic timestamp from messageProcess only message data - All decisions based on message content and existing state
Use message injection - Inject results from external operations as new messages
Use environment replication - Tunnel environment data into replication stream (see below)
Step 5: Configure Storage
Configure storage in your DDL to enable clustering and persistence.
Register Message Factories
Only message factories need to be registered (not state factories, since state is opaque):
DDL Configuration:
Programmatic Registration Alternative:
Enable Clustering
Clustering allows multiple instances to discover each other and form an HA cluster:
Key clustering concepts:
Instances with same
storeNameform a clusterOne instance elected as primary via leadership election
Primary establishes messaging and invokes handlers
Backups receive replicated messages and process them identically
Enable Persistence
Persistence logs the message stream to disk for cold start recovery:
Cold Start Recovery: With Event Sourcing, recovery from a cold start (no backup running) requires replaying the entire inbound message stream from disk. For long-running applications with high message volumes, transaction log compaction and checkpointing strategies are critical.
See Storage Configuration for complete configuration reference.
Advanced Techniques
Using Engine Time
The AepEngine.getEngineTime() method provides deterministic timestamps for Event Sourcing applications:
How It Works:
When called from a handler in Event Sourcing mode, returns timestamp from message event
Timestamp is captured just before handler dispatch
Same timestamp replicated to backup, ensuring deterministic processing
Outside handlers or in State Replication mode, returns
System.currentTimeMillis()
Message Injection
Message injection allows you to inject messages into the processing stream for fault-tolerant execution. This is useful for:
Deferred processing - Schedule work to be done later
Async operations - Inject results from other threads
External system integration - Inject data from external sources
Example: Scheduling Deferred Work
Example: Integrating External Operations
See Scheduling Messages for complete message injection documentation.
Environment Replication
Environment Replication allows you to tunnel local environment data into the replication stream without creating separate transactions. This is more efficient than message injection for frequently-accessed environment data.
Example: Hostname Provider
How It Works:
On primary: Provider records hostname lookup in replication buffer
On backup: Provider replays recorded hostname from buffer
All within same transaction - no additional message injection overhead
Deterministic Programming Rules
Event Sourcing requires strict adherence to deterministic programming rules:
What You MUST Do
Base all decisions on message data and existing state only
Use
engine.getEngineTime()for all time-dependent logicKeep handlers synchronous and single-threaded
Use message injection for async operations
Use environment replication for environment data
Ensure identical handler execution on all instances
What You MUST NOT Do
Never call
System.currentTimeMillis()orSystem.nanoTime()Never use
Randomor any non-deterministic algorithmsNever read from files, databases, or external systems directly
Never use thread-local storage or instance variables modified outside handlers
Never depend on environment variables or system properties in handlers
Never use object identity (e.g.,
System.identityHashCode()) for logic
See Programming Fundamentals for complete deterministic programming rules.
Related Documentation
Core Concepts
Consensus Models - Understanding Event Sourcing vs State Replication
Transactions - How transactions work with Event Sourcing
Application Lifecycle - Initialization and recovery
Development
Modeling Messages & State - ADM modeling for messages
Programming Fundamentals - Deterministic coding rules
Handling Messages - Writing message handlers
Scheduling Messages - Message injection
Configuration
Storage Configuration - Complete storage configuration reference
Configuring Threading - Optimize replication performance
Operations
Transaction Log Tool - Browse and analyze transaction logs
Querying Transaction Logs - XPQL query language
Next Steps
Start modeling: Define your messages in
model.xmlfollowing ADM syntaxCreate application class: Annotate with
@AppHAPolicy(EventSourcing)and design state POJOsWrite handlers: Implement deterministic message handlers
Configure storage: Enable clustering and persistence in DDL
Test failover: Verify state consistency through message replay
Test determinism: Ensure identical processing on multiple instances
Monitor in production: Track transaction log size and replay time
Last updated

