Kernel
The write kernel: turns one admitted command into at most one committed event stream (documentation/designs/02-write-path.md §1).
Deduplicate before deciding: a request id already committed in the tenant is never decided again; a command of another type reusing it is rejected. Commands are told apart by their model name only, not by a hash of their body: a same-type command reusing a request id is taken for a retry, by the caller's contract. A duplicate is answered only within the scope that sent it (see duplicate). Each loaded aggregate carries the KernelOptions.recentRequests it committed last, derived from the log: a request they hold is answered from its stream, and on a miss most commands skip looking the request id up; see RequestCheck for when they still do. The append's request key stays the backstop of "committed at most once". KernelOptions.recentRequests 0 looks every request id up before deciding.
Load the committed state, decide (the command's function, then its after hooks), evolve a fresh state with the events, append once.
A version conflict decides again on the newer state, up to KernelOptions.maxAttempts; an unknown append outcome is settled by reading the version slot.
A rejection or an unchanged outcome decided on a cached state stands only once the log holds nothing after the state's version: one read of the next version slot, else the state is reloaded and the command decided again (not counted as an attempt). Another node of the service may have appended, and only an append meets the version-unique key.
An attempt whose events are surely not appended (rejected, conflicting, failing) runs the compensations its decide functions registered (DecideScope.onAppendFailure); a committed one runs their after-append actions. Once an append commits, the rest of the attempt does not observe cancellation: a caller giving up must not tear what is already a fact.
KernelOptions.verifyDecidePurity fails a command whose decide function or after hook changed the state (Preparation).
states keeps states between commands; see StateCache for when a state is kept or dropped, and StateSourcing for where a state comes from on a miss.
reservations backs DecideScope.reserve and DecideScope.release; a pending reservation lives for KernelOptions.reservationTtl unless the append confirms it first. Each attempt reserves by a hold of its own, so an attempt surely not appended abandons only its own hold. The keys an attempt reserved, held already or not, and those it released, go into the appended entry's transport, so the reservation subscription confirms and releases them even when the after-append actions never run. A rejection that commits events gives its keys up as one that writes nothing does: its entry carries none of them.
Every stream carries the aggregate's scope after it: the creating command's scope, carried forward by every later command and changed only by the lifecycle commands transferring the owner or the space (see Preparation.metadataAfter). The kernel keeps the deleted mark beside the state too, evolved by the lifecycle events: a deleted aggregate refuses every command but RestoreAggregate (FrameworkCodes.AGGREGATE_DELETED).
The aggregate's scope also guards it, by the default rule (me.ahoo.wow.api.access.ScopeAccess) on the command's scope (admissible): an existing aggregate whose owner or space is another than the one the command's scope names is out of the caller's reach, and is answered as one that does not exist (FrameworkCodes.AGGREGATE_NOT_FOUND), or FrameworkCodes.AGGREGATE_FORBIDDEN for a command that may create it. A part absent from the command's scope is not checked: a tenant-level caller's, or an administrator's acting across scopes, carries no owner. Creating an aggregate gives it the creating command's scope, and is checked again on the aggregate it creates (creatable). An aggregate that belongs to its tenant (AggregateModel.ownership) neither records nor checks an owner: only the space of a command counts. An aggregate whose id is its owner's (AggregateModel.idSource) records its id as its owner, whoever creates it, so a caller of another owner neither creates nor changes it. A transfer of the owner or the space is guarded like any other command before deciding: its scope is the caller's, so the current owner (or a member of the current space) transfers, and the new owner or space is in its body. It is not checked after: it gives the aggregate away on purpose.
A derived command (isDerived: a saga's, an @OnRejected compensation, a timer's, one another context sent through its command topic) is not guarded: the server's own code made it, there is no caller to check, and what it may do to whom is the domain's to decide, such as A's transfer crediting B's account. The root command was authorized when it was admitted. The scope it inherits from the root only gives an aggregate it creates its ownership and is recorded for the audit; a retry of one is answered whatever the aggregate's scope.
A decide function's schedules (DecideScope.schedule, DecideScope.cancelSchedule) are the system events wow.ScheduleSet and wow.ScheduleCancelled after its events in the stream it commits, the last change of each key: they commit with its events, or not at all, and a decision that only schedules commits them alone (on an aggregate that exists). The command a schedule sends is stored by its type name and JSON (AttemptScopes). Every aggregate also decides the system commands wow.schedule.Set and wow.schedule.Cancel a saga derives (KernelLifecycle); the kernel keeps the rules of the schedules an aggregate holds beside its state (CachedState.schedules).
A command a schedule sent (Command.schedule) is not decided before the time its slot was due by this kernel's clock: a node whose clock is behind the firing node's waits until then, so a schedule never fires early. It waits at most KernelOptions.maxEarlyWait, holding the aggregate's queue meanwhile: a command due later than that fails at once with ScheduleNotDueException, and its firing runner sends it again later.
A decide function takes its ids from ids (AttemptScopes).
A decide function or after hook marked @Blocking runs on blocking (Decisions).
observer sees each execution, decision and append; what an append carries for delivery holds the append's trace (Transport.trace), so the subscriptions that handle it continue the command's trace.
Constructors
Functions
The declared access attributes of state, a state of model, in their stored form (EventCodec.accessOf).
The committed state after stream, evolved from base, the state of its aggregate at the version right before it (a copy the caller hands over, which may be mutated; version 0 for the initial state), without reading the log: the caller already holds stream as the log holds it, such as a subscription's entry. The state cache is not touched.
Executes admitted on model's aggregate, its scope narrowed to what the model's ownership keeps (me.ahoo.wow.api.aggregate.Ownership.scopeOf).
Executes queued, commands of one aggregate in arrival order, deciding each on the state the ones before it left and appending their streams with one EventLog.appendAll: the aggregate's commands are committed together instead of one append each. Each command's outcome is what execute would have given it.
The committed state of ref at version, evolved from base, its state at an earlier version that the caller hands over to be evolved (it may be mutated: a copy, never a live instance), with the log's streams after it up to version (a subscription behind the snapshot evolves the state it read for the previous commit, instead of replaying the log from version 1). The state cache is not touched.
The committed state of ref as of at, read-only: the state after the last stream committed at or before at by its commit time. Commit times are epoch milliseconds, so at counts to its millisecond. The log is read in version order and the first stream committed after at ends it: commit times are taken to rise with the versions, which only a clock stepping back between two owners of the aggregate breaks, and only by that step.
The committed state of ref at version, read-only (the state a subscription hands over when the commit's shared state is gone, on a replay or after an ownership change): snapshot evolved with the log's streams after it up to version when it fits, as on a cache miss, else the log replayed up to version. A snapshot past version, of another aggregate or not fitting is passed over. The state cache is not touched.
The commits of ref from version fromVersion up to toVersion, at most limit of them, in version order, read-only: each stream with its events, read as the model's current schema, and the state and metadata after it. The state before fromVersion is the one stateAt gives for its version, from snapshot and the tail when it fits, so the log is not replayed from version 1 then; reaching it folds at most maxReplay streams. The state cache is not touched.