> For the complete documentation index, see [llms.txt](https://docs.rumi.systems/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rumi.systems/rumi-core/guides/developing-applications/microservice-template/state-replication-template.md).

# State Replication Template

## Overview

State Replication is the simpler of Rumi's two High Availability models. With State Replication, Rumi automatically replicates changes to your microservice's state and the outbound messages emitted by your message handlers. In the event of failover to a backup or cold start recovery from a transaction log, your microservice's state is available at the same point where processing left off, and the engine will retransmit any outbound messages that were left in doubt as a result of the failure.

### How State Replication Works

With State Replication:

* **State is modeled using ADM** - Define your state using the Application Data Model XML
* **State changes are automatically replicated** - The Rumi runtime tracks and replicates all state modifications
* **Transparent state management** - State is managed by the runtime as a tree of POJOs
* **Atomic transactions** - State changes and outbound messages form atomic units of work
* **Automatic failover** - Backup instances maintain synchronized state for seamless takeover

### Key Features

* **Automatic Replication**: Runtime handles state synchronization across cluster members
* **Transparent State Tree**: State organized as an object tree, automatically tracked by runtime
* **ADM-Based Modeling**: State defined using same XML modeling language as messages
* **Built-in Consistency**: Guaranteed state consistency across all instances
* **Transaction Integration**: State changes and message sends are atomic
* **Disk Persistence**: Transaction log enables cold start recovery

### When to Use State Replication

State Replication is ideal when:

* You want automatic state synchronization with minimal code
* State can be modeled using ADM primitives and collections
* You don't need complex custom state logic or inheritance
* Simplicity and ease of development are priorities
* You need guaranteed state consistency across instances

### Comparison with Event Sourcing

| Aspect               | State Replication                         | Event Sourcing                          |
| -------------------- | ----------------------------------------- | --------------------------------------- |
| **State Management** | Runtime manages (transparent)             | Application manages (opaque POJOs)      |
| **State Modeling**   | ADM XML required                          | No modeling required                    |
| **Replication**      | State deltas replicated                   | Inbound messages replicated             |
| **Latency**          | Slightly higher (state tracking overhead) | Lower (no state tracking)               |
| **Complexity**       | Simpler application code                  | More complex (determinism requirements) |
| **Recovery**         | State reconstructed from log              | State reconstructed by message replay   |

See [Consensus Models](/rumi-core/concepts/consensus-models.md) for detailed comparison.

***

## Building a State Replication Microservice

Creating a State Replication microservice involves five main steps:

1. Model your microservice state using ADM
2. Annotate your main class for State Replication
3. Provide a state factory
4. Write message handlers that operate on state
5. Configure storage (clustering and persistence)

### Step 1: Model Application State

Define your microservice's state using the ADM modeling language in your `model.xml` file. The state model defines entities that form a tree structure with a single root object.

**Example State Model:**

```xml
<model xmlns="http://www.neeveresearch.com/schema/x-ddl"
       namespace="com.example.orderprocessor.state"
       defaultFactoryId="2">

  <entities>
    <!-- Root entity of the state tree -->
    <entity name="OrderBook" id="1">
      <!-- Simple fields -->
      <field name="totalOrders" type="Long" default="0"/>
      <field name="totalValue" type="BigDecimal" default="0.0"/>

      <!-- Collection of entities -->
      <field name="orders" type="Order" collection="Map"
             key="orderId" id="1"/>

      <!-- Nested entity field -->
      <field name="statistics" type="Statistics" id="2"/>
    </entity>

    <!-- Child entity -->
    <entity name="Order" id="2">
      <field name="orderId" type="String"/>
      <field name="symbol" type="String"/>
      <field name="quantity" type="Integer"/>
      <field name="price" type="BigDecimal"/>
      <field name="status" type="String"/>
    </entity>

    <!-- Embedded entity for nested data -->
    <entity name="Statistics" id="3" embedded="true">
      <field name="ordersProcessed" type="Long" default="0"/>
      <field name="rejectCount" type="Long" default="0"/>
    </entity>
  </entities>
</model>
```

**Important Modeling Considerations**:

* The state tree must have a single root entity
* Entities can contain simple fields, entity fields, and collections
* See [State Tree Limitations](#state-tree-limitations) for restrictions
* See [The Modeling Language](/rumi-core/guides/developing-applications/modeling-messages-and-state/the-modeling-language.md) for complete modeling guide

### Step 2: Annotate Main Class for State Replication

Use the `@AppHAPolicy` annotation to declare that your microservice uses State Replication:

```java
package com.example.orderprocessor;

import com.neeve.aep.AepEngine;
import com.neeve.aep.annotations.AppHAPolicy;

@AppHAPolicy(value = AepEngine.HAPolicy.StateReplication)
public class OrderProcessorApp {
    // ... application code ...
}
```

### Step 3: Provide a State Factory

The AEP engine needs to be able to create your application's initial state. Provide a state factory method annotated with `@AppStateFactoryAccessor`:

```java
import com.neeve.aep.IAepApplicationStateFactory;
import com.neeve.aep.annotations.AppStateFactoryAccessor;
import com.neeve.sma.MessageView;

@AppStateFactoryAccessor
final public IAepApplicationStateFactory getStateFactory() {
    return new IAepApplicationStateFactory() {
        @Override
        final public OrderBook createState(MessageView view) {
            // Return a new, empty state root
            return OrderBook.create();
        }
    };
}
```

{% hint style="warning" %}
**Important**: The state factory should return an empty, uninitialized object. The platform will:

1. Invoke the factory during initialization with a `null` argument to determine the state root type
2. Subsequently invoke it on receipt of a MessageView when state hasn't been created
3. Manage the state tree after creation - don't store references to the state yourself
   {% endhint %}

### Step 4: Write Message Handlers

Message handlers receive both the inbound message and the state root object. The handler updates state and sends outbound messages as an atomic transaction:

```java
import com.neeve.aep.AepMessageSender;
import com.neeve.aep.annotations.EventHandler;
import com.neeve.aep.annotations.AppInjectionPoint;

public class OrderProcessorApp {
    private AepMessageSender messageSender;

    // Inject message sender for outbound messages
    @AppInjectionPoint
    final public void setMessageSender(AepMessageSender messageSender) {
        this.messageSender = messageSender;
    }

    @EventHandler
    final public void onNewOrder(NewOrderRequest request, OrderBook orderBook) {
        // Create and populate order entity
        Order order = Order.create();
        order.setOrderId(request.getOrderId());
        order.setSymbol(request.getSymbol());
        order.setQuantity(request.getQuantity());
        order.setPrice(request.getPrice());
        order.setStatus("ACCEPTED");

        // Update state - changes are automatically tracked and replicated
        orderBook.getOrders().put(order.getOrderId(), order);
        orderBook.setTotalOrders(orderBook.getTotalOrders() + 1);
        orderBook.setTotalValue(
            orderBook.getTotalValue().add(
                order.getPrice().multiply(
                    BigDecimal.valueOf(order.getQuantity())
                )
            )
        );

        // Update embedded statistics
        orderBook.getStatistics().setOrdersProcessed(
            orderBook.getStatistics().getOrdersProcessed() + 1
        );

        // Send outbound message - atomically replicated with state changes
        OrderConfirmation confirmation = OrderConfirmation.create();
        confirmation.setOrderId(order.getOrderId());
        confirmation.setStatus("ACCEPTED");
        messageSender.sendMessage("confirmations", confirmation);
    }

    @EventHandler
    final public void onCancelOrder(CancelOrderRequest request, OrderBook orderBook) {
        Order order = orderBook.getOrders().get(request.getOrderId());
        if (order != null && "ACCEPTED".equals(order.getStatus())) {
            // Update state
            order.setStatus("CANCELLED");
            orderBook.getStatistics().setRejectCount(
                orderBook.getStatistics().getRejectCount() + 1
            );

            // Send confirmation
            OrderConfirmation confirmation = OrderConfirmation.create();
            confirmation.setOrderId(order.getOrderId());
            confirmation.setStatus("CANCELLED");
            messageSender.sendMessage("confirmations", confirmation);
        }
    }
}
```

**Key Points**:

* State changes and message sends form an atomic transaction
* All modifications to state are automatically tracked and replicated
* No need for manual state serialization or replication code
* State root is passed to every handler method

### Step 5: Configure Storage

Configure storage in your DDL to enable clustering and persistence.

#### Register Object Factories

Both message factories and state factories must be registered with the runtime:

**DDL Configuration:**

```xml
<service name="order-processor" mainClass="com.example.orderprocessor.OrderProcessorApp">
  <messaging>
    <factories>
      <factory name="com.example.orderprocessor.messages.MessageFactory"/>
    </factories>
    <buses>
      <bus name="orders-bus">
        <channels>
          <channel name="orders" join="true"/>
          <channel name="confirmations" join="false"/>
        </channels>
      </bus>
    </buses>
  </messaging>

  <storage enabled="true">
    <factories>
      <factory name="com.example.orderprocessor.state.StateFactory"/>
    </factories>
    <clustering enabled="true">
      <storeName>order-processor-store</storeName>
      <localPort>10000</localPort>
      <discoveryDescriptor>nvds://localhost:4090</discoveryDescriptor>
    </clustering>
    <persistence enabled="true">
      <flushOnCommit>false</flushOnCommit>
      <storeRoot>rdat</storeRoot>
    </persistence>
  </storage>
</service>
```

**Programmatic Registration Alternative:**

```java
@AppInjectionPoint
public void initialize(AepEngine engine) {
    // Register message factory
    engine.registerFactory(new com.example.orderprocessor.messages.MessageFactory());

    // Register state factory for replication
    engine.registerFactory(new com.example.orderprocessor.state.StateFactory());
}
```

#### Enable Clustering

Clustering allows multiple instances to discover each other and form an HA cluster:

```xml
<clustering enabled="true">
  <storeName>order-processor-store</storeName>
  <localPort>10000</localPort>
  <localIfAddr>192.168.1.100</localIfAddr>
  <linkParams>SO_REUSEADDR=true,TCP_NODELAY=true</linkParams>
  <discoveryDescriptor>nvds://localhost:4090</discoveryDescriptor>
  <initWaitTime>5000</initWaitTime>
  <failOnMultiplePrimaries>true</failOnMultiplePrimaries>
  <memberElectionPriority>100</memberElectionPriority>
</clustering>
```

**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 maintain synchronized state for failover

#### Enable Persistence

Persistence logs the replication stream to disk for cold start recovery:

```xml
<persistence enabled="true">
  <flushOnCommit>false</flushOnCommit>
  <autoFlushSize>8192</autoFlushSize>
  <storeRoot>rdat</storeRoot>
  <compaction>
    <compactOnStart>false</compactOnStart>
    <compactionThreshold>1024</compactionThreshold>
  </compaction>
</persistence>
```

{% hint style="info" %}
**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.
{% endhint %}

See [Storage Configuration](/rumi-core/reference/configuration.md#storage-configuration) for complete configuration reference.

***

## State Tree Limitations

State Replication has several important restrictions that developers must understand. These are documented in detail in [Programming Fundamentals - State Tree Limitations](/rumi-core/guides/developing-applications/authoring-user-code/message-processing/processing-messages/handling-messages/programming-fundamentals.md#state-tree-limitations):

### Key Restrictions

1. **Single Parent Restriction**: An entity can only appear in one location in the state tree (throws `IllegalStateException` if violated)
2. **No Multiple Fields of Same Type**: Cannot have multiple non-embedded entity fields of the same type in a parent
3. **No State Tree Cycles**: Cycles (including self-references) are not supported
4. **No Primitive Collections**: Collections must use boxed types (e.g., `Integer` not `int`)
5. **No Inheritance**: Inheritance not supported; use entity inlining for polymorphism

### Workarounds

**For Single Parent Restriction**: Store entities in a Map and reference by ID:

```java
// Instead of storing Customer in multiple places:
// order1.setCustomer(customer);  // Not allowed
// order2.setCustomer(customer);  // Would throw exception

// Use a Map and reference by ID:
orderBook.getCustomers().put(customer.getId(), customer);
order1.setCustomerId(customer.getId());
order2.setCustomerId(customer.getId());
```

See [Programming Fundamentals](/rumi-core/guides/developing-applications/authoring-user-code/message-processing/processing-messages/handling-messages/programming-fundamentals.md#state-tree-limitations) for complete details and additional workarounds.

***

## Related Documentation

### Core Concepts

* [Consensus Models](/rumi-core/concepts/consensus-models.md) - Understanding State Replication vs Event Sourcing
* [Transactions](/rumi-core/concepts/transactions.md) - How transactions work with state replication
* [Application Lifecycle](/rumi-core/concepts/microservice-operation/lifecycle.md) - State creation and initialization

### Development

* [Modeling Messages & State](/rumi-core/guides/developing-applications/modeling-messages-and-state.md) - Complete ADM modeling guide
* [Programming Fundamentals](/rumi-core/guides/developing-applications/authoring-user-code/message-processing/processing-messages/handling-messages/programming-fundamentals.md) - State tree limitations and restrictions
* [Handling Messages](/rumi-core/guides/developing-applications/authoring-user-code/message-processing/processing-messages/handling-messages.md) - Writing message handlers

### Configuration

* [Storage Configuration](/rumi-core/reference/configuration.md#storage-configuration) - Complete storage configuration reference
* [Configuring Threading](/rumi-core/guides/developing-applications/configuring-the-runtime/threading.md) - Optimize replication performance

### Operations

* [Transaction Log Tool](/rumi-cli/commands/tools/tlt.md) - Browse and analyze transaction logs
* [Querying Transaction Logs](/rumi-core/guides/operating-applications/analysis-and-troubleshooting/querying-transaction-logs.md) - XPQL query language

***

## Next Steps

1. **Start modeling**: Define your state in `model.xml` following ADM syntax
2. **Create application class**: Annotate with `@AppHAPolicy` and provide state factory
3. **Write handlers**: Implement message handlers that operate on state
4. **Configure storage**: Enable clustering and persistence in DDL
5. **Test failover**: Verify state consistency and message retransmission
6. **Tune performance**: Adjust threading and batching based on load testing
7. **Monitor in production**: Track transaction log size and replication latency
