Skip to content

fireflyframework/fireflyframework-eventsourcing

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

109 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Firefly Framework - Event Sourcing

Version Java Spring Boot License

A reactive event sourcing library for Spring Boot that stores domain events as the source of truth, enabling full audit trails, temporal queries, and event-driven architectures. Built on R2DBC and Project Reactor.

How It Works

Command Flow (Write)

Command --> Aggregate --> [validate] --> Event(s) --> EventStore.appendEvents()
                                                         |
                                              +----------+-----------+
                                              |                      |
                                       events table            event_outbox
                                     (BIGSERIAL seq)          (if publisher
                                                                configured)

Read Flow (Query)

EventStore.loadEventStream() --> StoredEventEnvelope[] --> aggregate.loadFromHistory()
                                                               |
                                                        Aggregate (current state)

Version Semantics

State AggregateRoot.version expectedVersion for appendEvents
New (no events yet) -1 -1
After 1st event 0 0 (for next append)
After Nth event N-1 N-1 (for next append)

The aggregate version starts at -1 and increments with each event applied via applyChange(). When calling appendEvents, pass the aggregate's current version as expectedVersion for optimistic concurrency control.

Features

Core

  • AggregateRoot base class with reflection-based event handler dispatch
  • Reactive EventStore interface backed by R2DBC (PostgreSQL, H2, MySQL)
  • Optimistic concurrency control via aggregate versioning
  • @DomainEvent annotation for declarative event type registration (bridges to @JsonTypeName)
  • AbstractDomainEvent with builder pattern and metadata helpers (correlationId, causationId, userId, source)
  • StoredEventEnvelope wraps domain events with storage metadata (global sequence, created timestamp)
  • EventStream with query helpers (getEventsFromVersion, getEventsInRange, isEmpty, size)

Persistence

  • Flyway-managed schema with 8 migrations (V1-V8)
  • BIGSERIAL global sequence assigned by the database -- INSERT excludes global_sequence
  • TEXT columns for event_data and metadata (database-agnostic, not JSONB)
  • Snapshot store with UPSERT semantics -- PK is (aggregate_id, aggregate_type), one snapshot per aggregate
  • Transactional Outbox pattern for reliable event publishing with exponential backoff retry

Operations

  • @EventSourcingTransactional with configurable propagation, isolation, retry, and timeout
  • Auto-configuration chain (9 configuration classes with conditional bean creation)
  • Health indicators: EventStore, Outbox, Snapshot, Projection
  • Micrometer metrics via EventStoreMetrics (timers, counters, gauges)
  • Structured logging with 16 MDC keys and reactive context propagation
  • Circuit breakers (eventStore, outbox, projection) via Resilience4j (off by default)
  • Multi-tenancy via TenantContext (off by default)
  • Event upcasting for schema evolution via EventUpcaster interface

Requirements

  • Java 21+
  • Spring Boot 3.x
  • Maven 3.9+
  • PostgreSQL (recommended) or any R2DBC-compatible database

Installation

<dependency>
    <groupId>org.fireflyframework</groupId>
    <artifactId>fireflyframework-eventsourcing</artifactId>
    <version>26.02.07</version>
</dependency>

Quick Start

1. Define Domain Events

@DomainEvent("order.placed")
@SuperBuilder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class OrderPlacedEvent extends AbstractDomainEvent {
    private String productId;
    private int quantity;
    private BigDecimal totalPrice;
}

2. Create an Aggregate

public class Order extends AggregateRoot {

    private String productId;
    private int quantity;
    private BigDecimal totalPrice;

    // Constructor for loading from event store
    public Order(UUID id) {
        super(id, "Order");
    }

    // Constructor for creating a new order (command)
    public Order(UUID id, String productId, int quantity, BigDecimal totalPrice) {
        super(id, "Order");
        applyChange(OrderPlacedEvent.builder()
                .aggregateId(id)
                .productId(productId)
                .quantity(quantity)
                .totalPrice(totalPrice)
                .build());
    }

    // Event handler -- updates state, no validation
    private void on(OrderPlacedEvent event) {
        this.productId = event.getProductId();
        this.quantity = event.getQuantity();
        this.totalPrice = event.getTotalPrice();
    }
}

3. Persist and Load

@Service
@RequiredArgsConstructor
public class OrderService {
    private final EventStore eventStore;

    public Mono<Order> placeOrder(String productId, int qty, BigDecimal price) {
        UUID orderId = UUID.randomUUID();
        Order order = new Order(orderId, productId, qty, price);

        return eventStore.appendEvents(
                orderId, "Order", order.getUncommittedEvents(), -1L  // -1 = new aggregate
            )
            .doOnSuccess(stream -> order.markEventsAsCommitted())
            .thenReturn(order);
    }

    public Mono<Order> getOrder(UUID orderId) {
        return eventStore.loadEventStream(orderId, "Order")
            .map(stream -> {
                Order order = new Order(orderId);
                order.loadFromHistory(stream.getEvents());
                return order;
            });
    }
}

Architecture

+--------------------------------------------------+
|                Application Layer                 |
|  Services, Controllers, Command Handlers         |
+--------------------------------------------------+
          |                          |
          v                          v
+----------------------+    +------------------------+
|   Domain Layer       |    | Infrastructure Layer   |
|                      |    |                        |
| AggregateRoot        |    | R2dbcEventStore        |
| Event interface      |    | R2dbcSnapshotStore     |
| AbstractDomainEvent  |    | EventTypeRegistry      |
| @DomainEvent         |    | EventOutboxService     |
| StoredEventEnvelope  |    | EventSourcingPublisher |
| EventStream          |    +------------------------+
+----------------------+             |
                                     v
                        +------------------------+
                        | PostgreSQL (R2DBC)     |
                        |                        |
                        | events                 |
                        | snapshots              |
                        | event_outbox           |
                        | projection_positions   |
                        +------------------------+

Database Schema

The library ships with 8 Flyway migrations. Key tables:

Table Purpose PK
events Append-only event log event_id (UUID)
snapshots Aggregate state cache (aggregate_id, aggregate_type)
event_outbox Transactional outbox for publishing outbox_id (UUID)
projection_positions Projection checkpoint tracking projection_name

The events table uses BIGSERIAL for global_sequence -- the database auto-assigns sequence numbers. The INSERT statement does not include global_sequence. Columns event_data and metadata are TEXT, not JSONB.

See Database Schema Reference for full details.

Configuration

spring:
  r2dbc:
    url: r2dbc:postgresql://localhost:5432/mydb
    username: user
    password: pass

firefly:
  eventsourcing:
    enabled: true
    event-scan-packages: "com.example.myapp"
    store:
      type: r2dbc
      batch-size: 100
    snapshot:
      enabled: true
      threshold: 50
    publisher:
      enabled: true

See Configuration Reference for all properties and defaults.

Documentation

Document Description
Event Sourcing Explained Conceptual introduction and comparison with CRUD
Quick Start Step-by-step setup guide
Architecture System layers, auto-configuration, event flows
API Reference Complete interface and class documentation
Configuration All properties with defaults
Database Schema Flyway migrations, tables, indexes, triggers
Testing Unit, integration, and projection testing
Account Ledger Tutorial Complete working example
Optional Enhancements Circuit breakers, metrics, multi-tenancy, upcasting

License

Copyright 2024-2026 Firefly Software Foundation. Licensed under the Apache License 2.0.

About

Event Sourcing library with reactive event store, snapshot management, projections, outbox pattern, and multi-tenancy support over R2DBC.

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors