Package-level declarations

Types

Link copied to clipboard
data class Causation(val correlationId: String, val causationId: String)

Where a command comes from (documentation/designs/08-data-protocols.md §4): correlationId is the id of the root command of the chain, and causationId the direct upstream, such as the event stream (ref@version) a saga reacted to. A command a user sends directly carries none: it is its own root.

Link copied to clipboard
class Command<C : Any> @InternalWowApi constructor(val id: String, val requestId: String, val ref: AggregateRef, val body: C, val expectedVersion: Int? = null, val scope: Scope = Scope.NONE, val headers: Map<String, String> = emptyMap(), val causation: Causation? = null, val operator: Operator? = null, val wait: WaitPlan = WaitPlan.DEFAULT, val caller: Caller? = null)

An admitted command: who it targets and what it asks, with every fact decided once at admission and read-only afterwards.

Link copied to clipboard
interface DecideScope

What a decide function (or an after hook) can ask of the kernel besides returning its decision.

Link copied to clipboard
sealed interface Decision

What a decide function concluded about a command: the events it produces, that it changes nothing, or that it rejects the command. A rejection is a business outcome the caller is told about; it writes nothing unless it carries events, which are appended with it (Rejected.events).

Link copied to clipboard
sealed interface Misuse

A Wow API that cannot serve a call as the code made it: the code's own mistake, a wrong argument (a read of a state class no aggregate has, a command to a context no peer describes) or a wrong state of use (a command object to a peer that takes named commands alone, a peer the configuration gives no address), never the caller's, nor a peer's or the wire's, which are transport failures. It is a subclass of IllegalArgumentException or IllegalStateException, never the class itself, which a decide function's own require and check throw and the kernel answers as the command's rejection: a decide function that runs into a Misuse fails its command, as an unexpected exception does, and a subscription handler's is unrecoverable, since it fails the same way every time. The kernel's decide scope, the read API and the command clients throw it.

Link copied to clipboard
class RejectedException @JvmOverloads constructor(val rejection: Rejection, val events: List<Any> = emptyList()) : RuntimeException

A decide function's refusal thrown instead of returned: the kernel answers it exactly as the Decision.Rejected of rejection, so nothing is appended, a compensation runs as for a returned rejection, and the caller gets the same answer. Kotlin throws one with reject(code, message) (or DecideScope.reject); Java writes throw new RejectedException(Rejection.of("out-of-stock", "Only 2 left.")), and a handler may rethrow one.

Link copied to clipboard
class Rejection @InternalWowApi constructor(val code: String, val message: String, val extensions: Map<String, Any?> = emptyMap())

Why a command was refused. code is stable and machine-readable (it names an error in the Manifest); message is for people. extensions are the rejection's structured data, by name, each a JSON value (a string, a number, a boolean, a list, a map, or a data class of them), such as the ids and titles of what blocks the command: the command's result carries them (me.ahoo.wow.api.result.ErrorInfo.extensions), and an HTTP answer writes each as an extension member of its problem.

Link copied to clipboard

The standard request facts: the keys of a Command's headers the framework itself fills, beside those the host lists, written into the committed event stream with them. No request header sets one (the trust boundary): the edge records each from what it knows.

Link copied to clipboard

Among which aggregates a reserved key is unique (DecideScope.reserve); always among the aggregates of one type.

Functions

Link copied to clipboard
inline fun checkUse(value: Boolean, message: () -> String)

Throws a Misuse.State of message unless value holds: check for a Wow API's own state.

Link copied to clipboard
inline fun reject(code: String, message: String, events: List<Any> = emptyList(), extensions: Map<String, Any?> = emptyMap()): Nothing

Refuses the command being decided with the rejection code, message and structured extensions: it throws a RejectedException, which the kernel answers as that rejection. message is shown to the caller. Nothing is appended unless the rejection carries events: ordinary events of the aggregate, each sourced by it, appended with the rejection in one stream, and the answer then names the version they reached (an audit of the attempt, a counter of refusals). A rejection of a creation, whose aggregate does not exist, appends nothing. The way a decide function that returns its event type rejects, with or without a DecideScope; require (wow.command.invalid) and check (wow.command.refused) reject a broken precondition alike. It is inline like error(), so the rejecting line is the caller's.

Link copied to clipboard
inline fun requireUse(value: Boolean, message: () -> String)

Throws a Misuse.Argument of message unless value holds: require for a Wow API's own arguments.