Package-level declarations

Types

Link copied to clipboard
sealed interface Authorization

An authorizer's answer: Allowed, or Denied with a Denied.code and a Denied.message.

Link copied to clipboard
class CallContext @InternalWowApi constructor(val commandId: String, val tenantId: String, val scope: Scope, val causation: Causation, val operator: Operator? = null, val wait: WaitContext? = null, facts: Map<String, String> = emptyMap())

The root command a synchronous call acts for, as the calling service asserts it: the call's command id commandId (the id its saga derived, so a wait that follows the saga finds it), and the root command's tenant tenantId, scope, operator and the causation that makes it a derived command, with the wait context a wait that follows the saga goes on with, and the root request's facts that propagate (RequestFacts.PROPAGATED), such as the calling application: only those, whatever the map it is built with holds, so a call never carries the client's address or User-Agent.

Link copied to clipboard

A CallContext as the Wow-Call header: its CallWire, as :spi's CallWires encodes it. A fact that does not propagate is dropped as it is parsed (CallContext.facts).

Link copied to clipboard
class CommandAction @InternalWowApi constructor(val type: CommandType, val ref: AggregateRef, val scope: Scope, val creation: Creation)

What a caller asks to do, as action-level authorization sees it before the body is decoded: a command of type on ref, in scope, and whether the command may create the aggregate (creation).

Link copied to clipboard
fun interface CommandAuthorizer

Decides whether a caller may send a command, before its body is decoded (step 3). The instance-level check, whether the aggregate belongs to the caller's scope, is the kernel's, not an authorizer's.

Link copied to clipboard
sealed interface CommandBody

The body of a ServerCommandRequest: JSON Bytes as received, a JSON Tree already parsed, or the command object itself (Value).

Link copied to clipboard
class CommandGateway @InternalWowApi constructor(registry: ModelRegistry, coordinators: Map<String, WaitCoordinator>, authorizer: CommandAuthorizer? = null, transformers: List<CommandTransformer> = emptyList(), validators: List<CommandValidator> = emptyList(), val ids: IdGenerators = IdGenerators.DEFAULT, val tenants: TenantPolicy = TenantPolicy.CALLERS, mapper: JsonMapper = ClusterCodec.mapper, clock: Clock = Clock.systemUTC(), detachedContext: CoroutineContext = Dispatchers.Default, committed: CommittedRequests = CommittedRequests.NONE, observer: Observer = Observer.NONE, peers: PeerCommands? = null)

Admits commands: turns a ServerCommandRequest into an immutable Command and hands it to the WaitCoordinator of its context, which checks its wait plan, routes it and waits for it.

Link copied to clipboard
class CommandInput @InternalWowApi constructor(val type: CommandType, val body: Any, val aggregateId: String?, val headers: Map<String, String>, val requestId: String, val caller: Caller, val tenantId: String = caller.tenantId)

A request as a transformer sees it: decoded, before the command is built. Its identity may be incomplete: aggregateId is null when the request named none. The requestId, the caller and the tenantId the command runs in (the caller's, or the one the request targets) are only read: the command that runs keeps them.

Link copied to clipboard
class CommandTarget @InternalWowApi constructor(val type: CommandType, val aggregateId: String?, val body: Any, val headers: Map<String, String> = emptyMap())

The command a transformer sends, and to which aggregate: a body of type, decided by an aggregate the gateway serves, to the aggregate aggregateId of that type, with headers. The gateway builds the me.ahoo.wow.api.decide.Command from it by its usual rules: the tenant, the scope, the request id and the wait plan are the request's. A null aggregateId is allowed only for a command that creates its aggregate, whose id the gateway then generates.

Link copied to clipboard

Turns the commands of one input type into the command that runs: one command into one command, at admission, after the input is decoded and authorized and before the result is validated. A transformer may change the command's type, the aggregate it is sent to, its body and its headers; it cannot change the request id (retries stay idempotent), the tenant or the scope, or the wait plan, which CommandTarget has no field for. A command that must become several is a saga's work.

Link copied to clipboard
fun interface CommandValidator

Validates a decoded and transformed command body of type: the violations it finds, none when it is valid. Any violation refuses the command with FrameworkCodes.COMMAND_INVALID, listing them all.

Link copied to clipboard

The streams already committed for a request: what admission looks up before it gives a creation command without an id a new one.

Link copied to clipboard
class CreationIds(ids: IdGenerators = IdGenerators.DEFAULT, committed: CommittedRequests = CommittedRequests.NONE)

The ids admission gives the creation commands that name none: a command, a client's or one a saga derived, whose request already committed takes the id of the aggregate it created, without drawing a new one, so a retry or a replay keeps it; any other takes a new one from its aggregate type's generator in ids. Committed ids never change. A concurrent duplicate may still draw a number that is then not used: ids are stable, not gap-free.

Link copied to clipboard

The aggregate models a node serves, found by their AggregateType (step 1).

Link copied to clipboard
class PeerCommand(val name: String, val type: Class<*>?, val creation: Creation, val idProperty: String? = null)

A command type of a peer context's contract: its full name (ctx.agg.Command), its class, when on the class path (type), whether it creates its aggregate (creation), and the property of its body that holds its aggregate's id (idProperty; null for one whose request names it).

Link copied to clipboard
class PeerCommands(contracts: ContractDirectory, sender: CommandSender? = null, classLoader: ClassLoader = PeerCommands::class.java.classLoader)

The commands of peer contexts that the CommandGateway admits: the aggregates of a context another service serves, known from the contract its api jar carries (contracts). Such a command is written to the peer's command topic, wow.<context>.commands, through the node's command sender, the path a saga's command to another context takes, and the peer's intake executes it. Without one, the node has no command transport and refuses them.

Link copied to clipboard
class RouteAction @InternalWowApi constructor(val context: String, val name: String, val method: String, val path: String, val tenantId: String, val scope: Scope)

What a caller asks of a service route, as authorization sees it before the request is decoded: the route name of context, its method and the path it was called at, in tenantId and scope.

Link copied to clipboard
class ServerCommandRequest @InternalWowApi constructor(val context: String, val aggregate: String, val command: String, val body: CommandBody, val caller: Caller = Caller.ANONYMOUS, val aggregateId: String? = null, val target: TargetScope? = null, val requestId: String? = null, val expectedVersion: Int? = null, val headers: Map<String, String> = emptyMap(), val wait: WaitPlan? = null, val trace: Map<String, String> = emptyMap(), val call: CallContext? = null, val tenantId: String? = null)

The server side of a CommandRequest: what the gateway receives from any client, the HTTP edge, the Kafka intake or the in-process client, before admission. What the route fixed (context, aggregate, command and, on an instance route, aggregateId), the undecoded body, who sent it (caller), and the facts the request's headers gave: a cross-scope target (Wow-Scope), a requestId (Wow-Request-Id), an expectedVersion (If-Match), the request facts in headers, and the wait plan (Wow-Wait; the default plan when absent).

Link copied to clipboard

Decides whether a caller may call a service route, after authentication and before its request is decoded. The rule is the CommandAuthorizer's: a node's authorizers are all asked (allOf), the first Authorization.Denied is the answer, and there is no abstaining. With none registered, every authenticated caller may call the context's routes.