Scripta

Event sourcing and DDD for Java, without the ceremony.

Open source under Apache 2.0. Early development: not yet released.

The name

Verba volant, scripta manent.

That is the whole idea behind event sourcing. In Scripta, every event is written once and kept forever, and the current state of your domain is simply what those events add up to.

What it looks like

An aggregate is a plain Java class. Events are records behind a sealed interface, so the compiler checks that every event is handled.

BankAccount.java
public sealed interface AccountEvent {
    record AccountOpened(String owner) implements AccountEvent {}
    record MoneyDeposited(BigDecimal amount) implements AccountEvent {}
    record MoneyWithdrawn(BigDecimal amount) implements AccountEvent {}
}

public final class BankAccount extends Aggregate<AccountEvent> {
    private BigDecimal balance = BigDecimal.ZERO;

    public void deposit(BigDecimal amount) {
        if (amount.signum() <= 0) throw new IllegalArgumentException("Amount must be positive");
        raise(new MoneyDeposited(amount));
    }

    public void withdraw(BigDecimal amount) {
        if (balance.compareTo(amount) < 0) throw new IllegalStateException("Insufficient funds");
        raise(new MoneyWithdrawn(amount));
    }

    @Override
    protected void apply(AccountEvent event) {
        switch (event) {
            case AccountOpened e  -> balance = BigDecimal.ZERO;
            case MoneyDeposited e -> balance = balance.add(e.amount());
            case MoneyWithdrawn e -> balance = balance.subtract(e.amount());
        }
    }
}
BankAccountTest.java
@Test
void withdrawingRaisesMoneyWithdrawn() {
    given(new AccountOpened("Ada"), new MoneyDeposited(new BigDecimal("100")))
        .when(account -> account.withdraw(new BigDecimal("30")))
        .then(new MoneyWithdrawn(new BigDecimal("30")));
}

Scripta is pre-release, so the API shown here is a design target and may still change.

Principles

  • Zero runtime dependencies

    The core module depends on the JDK and nothing else. Integrations live in separate, optional modules.

  • Plain Java, no magic

    Aggregates and events are ordinary classes, records and sealed types. No reflection tricks, no bytecode generation.

  • Optimistic concurrency

    Every append checks the expected stream version, so conflicting writes fail loudly instead of silently overwriting.

  • Projections you can trust

    Read models track checkpoints and can be rebuilt from the event log whenever their shape changes.

  • Testing first

    Given/when/then fixtures let you test behaviour as events in, events out, without a database.

  • Pluggable storage

    The event store sits behind a small interface. PostgreSQL is the first implementation.

Quickstart

Scripta is in early development and is not yet published to Maven Central. The coordinates below are the ones it will use. Until a release is out, build the project from source with mvn install to use the snapshot locally.

pom.xml
<dependency>
    <groupId>dev.scriptaframework</groupId>
    <artifactId>scripta-core</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>

Requires Java 21 or newer.

Roadmap

Nothing here is finished yet. This is the order things are being built in.

  1. Core: aggregates, events, repositories, test fixtures In progress
  2. PostgreSQL event store Planned
  3. Jackson serialization Planned
  4. Spring Boot starter Planned