§ reference

Entity

A persistent aggregate that does NOT emit events. Use Entity when the persistence layer needs optimistic-locked rows whose changes don't carry domain meaning worth recording — caches, lookup tables, idempotency markers, audit-only side-effects.

Type signature

package io.ekbatan.core.domain;

public abstract class Entity<
                ENTITY extends Entity<ENTITY, ID, STATE>,
                ID     extends Comparable<?>,
                STATE  extends Enum<STATE>>
        implements Persistable<ID> {

    public final ID id;
    public final STATE state;
    public final Long version;

    protected <B extends Builder<ID, B, ENTITY, STATE>> Entity(Builder<ID, B, ENTITY, STATE> builder);

    public ID getId();
    public Long getVersion();
    public final boolean isModel();         // always false on Entity
    public abstract Entity.Builder<ID, ?, ENTITY, STATE> copy();
    public <E extends Persistable<ID>> E nextVersion();

    public abstract static class Builder<...> { /* see below */ }
}

The framework’s other persistent shape is Model, which does emit events. Action code stages additions and updates of either kind on the implicit ActionPlan; ActionExecutor writes the domain row and (only for Model) extracts events into the eventlog atomically.

When to use Entity vs Model

You’re persisting…Use
Domain aggregates whose mutations describe business events worth recording (Wallet, Order, Subscription, Account)Model
Lookup tables that rarely change and don’t drive event consumers (Currency, Country, RateCard, FeatureFlag)Entity
Idempotency markers / dedup tables (RequestSeen, ProcessedMessage)Entity
Caches of derived data that don’t need to be in the event stream (BalanceSnapshot, LeaderboardEntry)Entity
Audit-only side-effects (RawApiCallLog)Entity

The rule of thumb: if a downstream consumer of the eventlog would care about this change, it’s a Model. Otherwise it’s an Entity.

Differences from Model

PropertyModelEntity
Emits events✓ (via withEvent(...))
events fieldList<ModelEvent<MODEL>>(none)
createdDate / updatedDate fields
Equality includes timestamps
Equality includes events✗ (excluded by design)n/a
isModel() returnstruefalse
Builder has withEvent(...)
Persisted alongside outbox rows✗ (no outbox rows are written for Entity changes)

Both have id, state, version, optimistic-locking semantics, and the same nextVersion() / copy() / copyBase() / withInitialVersion() / increaseVersion() builder surface.

Construction and mutation

Subclasses are immutable with a generated *Builder via @AutoBuilder from ekbatan-annotation-processor. Mutations produce a new instance via copy()...build(); the builder threads the version forward so nextVersion() returns the properly-versioned successor for the repository’s optimistic-locked update:

@AutoBuilder
public final class Currency extends Entity<Currency, Id<Currency>, GenericState> {
    public final String code;
    public final String displayName;

    Currency(CurrencyBuilder builder) {
        super(builder);
        this.code        = Validate.notBlank(builder.code,        "code cannot be blank");
        this.displayName = Validate.notBlank(builder.displayName, "displayName cannot be blank");
    }

    @Override
    public CurrencyBuilder copy() {
        return CurrencyBuilder.currency()
                .copyBase(this)
                .code(code)
                .displayName(displayName);
    }
}

Equality

Two Entity instances are equal iff their id, state, and version match — there’s no event list and no timestamps to consider, so the equality surface is narrower than Model’s. Two readers of the same row at the same version produce equal entities.

Builder API

The base Entity.Builder provides:

MethodSets
id(ID)The primary identifier
state(STATE)The lifecycle state
withInitialVersion()Sets version = 1 (for first inserts)
version(Long)Sets the version explicitly (must be ≥ 1)
increaseVersion()version++ (used by nextVersion)
copyBase(E)Copies id, state, and version from an existing entity
build()Returns the configured entity; subclasses provide the concrete type

See also