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

Aspect
Event Sourcing
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:

  1. Model your application messages using ADM

  2. Annotate your main class for Event Sourcing

  3. Manage microservice state in POJOs

  4. Write deterministic message handlers

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

Safe Practices:

  • Use engine.getEngineTime() - Provides deterministic timestamp from message

  • Process 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 storeName form a cluster

  • One 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:

Clustering Requires Persistence: When clustering is enabled, persistence must also be enabled. The transaction log is used to initialize new cluster members that connect to the primary.

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

  1. Base all decisions on message data and existing state only

  2. Use engine.getEngineTime() for all time-dependent logic

  3. Keep handlers synchronous and single-threaded

  4. Use message injection for async operations

  5. Use environment replication for environment data

  6. Ensure identical handler execution on all instances

What You MUST NOT Do

  1. Never call System.currentTimeMillis() or System.nanoTime()

  2. Never use Random or any non-deterministic algorithms

  3. Never read from files, databases, or external systems directly

  4. Never use thread-local storage or instance variables modified outside handlers

  5. Never depend on environment variables or system properties in handlers

  6. Never use object identity (e.g., System.identityHashCode()) for logic

See Programming Fundamentals for complete deterministic programming rules.


Core Concepts

Development

Configuration

Operations


Next Steps

  1. Start modeling: Define your messages in model.xml following ADM syntax

  2. Create application class: Annotate with @AppHAPolicy(EventSourcing) and design state POJOs

  3. Write handlers: Implement deterministic message handlers

  4. Configure storage: Enable clustering and persistence in DDL

  5. Test failover: Verify state consistency through message replay

  6. Test determinism: Ensure identical processing on multiple instances

  7. Monitor in production: Track transaction log size and replay time

Last updated