ServiceNode

class ServiceNode @InternalWowApi constructor(val service: String, val node: String, address: String?, models: Collection<AggregateModel<*>>, val manifest: Manifest, backends: BackendFactory, channel: ClusterChannel, remoteSignals: SignalSender = SignalSender.NONE, val options: ServiceNodeOptions = ServiceNodeOptions(), admission: AdmissionPlugins = AdmissionPlugins(), subscriptions: SubscriptionFactory = SubscriptionFactory.NONE, listener: (ServiceNode.Step) -> Unit = {}, codec: EventCodec = EventCodec(), val contracts: ContractDirectory = ContractDirectory(manifest), topics: SourceTopics? = null, parkedNotifier: ParkedNotifier = ParkedNotifier.NONE, clock: Clock = Clock.systemUTC(), observer: Observer = Observer.NONE, caller: CommandCaller? = null, failureClassifiers: List<FailureClassifier> = emptyList(), queries: ContextQueries = ContextQueries.NONE, replayGate: ReplayGate = ReplayGate.OPEN, deadlines: WaitDeadlines = WaitDeadlines.SHARDED)

One node of a service, assembled without a host: what a host (Spring Boot, a test, a sample) wires to its ports.

Per bounded context of the models (contexts): its log, kernel, executor, built-in subscriptions, lease group ownership, wait coordinator and forward endpoint, which ContextAssembly wires.

Per node: the waits registered here, the signals sender (a signal to this node goes straight to waits, one to another node through the host's remote sender), the router over every context's lease group, the gateway that admits commands, the registry of models, the forwardHandler that hands a forwarded command to its context's endpoint, and the parked entries of every subscription as an operator acts on them; each runtime retries the entries of the partitions it runs over the long horizon and tells the parkedNotifier when one parks or spends its retries.

Per topic read for the subscriptions of other contexts' published streams, and for the receipts of its sagas' commands: one TopicSubscriptions each, which TopicAssembly builds; a node with such subscriptions and no topics is not built.

Commands across contexts (–), when topics has a producer: a saga's command to an aggregate of another context is sent to that context's command topic (ContextNode.sender); the receipts of those rejected, and of those committed that the saga asked about, come back on the saga's context's receipts topic, read in the lease group of that topic for the contexts whose sagas have @OnRejected or @OnCommitted handlers (SubscriptionFactory.receipts). With SourceTopics.intake, each context executes the commands of its own command topic (ContextNode.intake, CommandIntake), driven by its PartitionOwnership: the owner of a log partition reads the same partition of the topic, and start fails unless the topic has as many partitions as the log.

The failure ledger across services: with ServiceNodeOptions.ledger, the node publishes the entries whose partitions it runs to the ops context (ledgerPublisher), from start until stop hands off; each intake runs the ledger's system commands the ops context sends back on the node's parked entries.

The topic router: the commands admitted for the aggregate types of ServiceNodeOptions.topicRouted are written to their context's command topic (TopicRouter) and executed by the owner of their log partition through the context's intake, which every such context runs; the node is not built when it does not serve one of those types, or has no producer in topics.

Topics: start first validates or creates the topics the node's contexts own (topicRequirements) as SourceTopics.ddl says, and fails when one is not as they need it.

Startup checks: the manifest (normally me.ahoo.wow.runtime.manifest.ManifestLoader's) names this service, and it and the models agree (ManifestCheck); otherwise the node is not built. Nor is it built without a producer in topics when a saga of the manifest derives, schedules or handles the rejections of commands of a context the node does not serve (ContractDirectory.commandsOutside), nor when a saga calls a context the node serves, or one its caller does not reach (ContractDirectory.contextsCalled). The contracts are those of this manifest and its peers, checked when they were built; each context's waits name subscriptions through them, so a chain may name one of another context.

Internal address: the address the other nodes reach this node at, given when the node is built, or learned with bind once the host's internal port is bound (a port the system chose, such as 0), before start.

Lifecycle (state): start joins every context's lease group and the node is then isReady; stop hands off, waits for the waits, closes the gateway, lets the executions end, closes the executors and the append queues, in that order (NodeLifecycle). listener hears each Step once it is done; a step that fails is logged and the next ones still run, and stop then throws the first failure.

The host does the rest around it. Starting: open the backends (BackendFactory) → serve the internal port (forwards to forwardHandler, signals to waits) → bind its address, when the node was built without one → start → serve the public port → report ready. Stopping: report not ready and shut the public port down gracefully → stop → stop the internal port, which serves the forwards and signals of the other nodes until then → close the backends. Its shutdown phase allows at least the waits' maximum timeout plus drainTimeout.

Constructors

Link copied to clipboard
constructor(service: String, node: String, address: String?, models: Collection<AggregateModel<*>>, manifest: Manifest, backends: BackendFactory, channel: ClusterChannel, remoteSignals: SignalSender = SignalSender.NONE, options: ServiceNodeOptions = ServiceNodeOptions(), admission: AdmissionPlugins = AdmissionPlugins(), subscriptions: SubscriptionFactory = SubscriptionFactory.NONE, listener: (ServiceNode.Step) -> Unit = {}, codec: EventCodec = EventCodec(), contracts: ContractDirectory = ContractDirectory(manifest), topics: SourceTopics? = null, parkedNotifier: ParkedNotifier = ParkedNotifier.NONE, clock: Clock = Clock.systemUTC(), observer: Observer = Observer.NONE, caller: CommandCaller? = null, failureClassifiers: List<FailureClassifier> = emptyList(), queries: ContextQueries = ContextQueries.NONE, replayGate: ReplayGate = ReplayGate.OPEN, deadlines: WaitDeadlines = WaitDeadlines.SHARDED)

Types

Link copied to clipboard
object Companion
Link copied to clipboard

Where a node is in its lifecycle.

Link copied to clipboard
enum class Step : Enum<ServiceNode.Step>

The steps of stop, in their order.

Properties

Link copied to clipboard

When and by whom the node was activated from standby; null while it is on standby or never was.

Link copied to clipboard

This node's internal address: the cluster channel and the signals reach it there. Known once hasAddress.

Link copied to clipboard

The node's clock: every wall time the node reads, its timers', leases' and waits' alike.

Link copied to clipboard

The parts of each bounded context, by its name.

Link copied to clipboard

The contracts of the manifest and its peers (ContractDirectory.load reads the peers on the class path).

Link copied to clipboard

The fatal errors the node's work met (NodeFatals): after the first, the node is fatal until its process ends, its liveness and readiness DOWN (me.ahoo.wow.runtime.health.NodeHealth).

Link copied to clipboard

What the internal port serves to the cluster channel: a forwarded command goes to its context's endpoint; one of a context this node does not host is refused as of an unknown type, so its sender runs it.

Link copied to clipboard

Admits the commands of every context.

Link copied to clipboard

Whether the node knows its address: it was built with one, or bind gave it.

Link copied to clipboard

The names of the bounded contexts the node hosts.

Link copied to clipboard

Whether the node serves commands: it has started and is not stopping.

Link copied to clipboard

Publishes the node's failure ledger to the ops context when ServiceNodeOptions.ledger says where: the entries whose partitions the node runs, through that context's command topic.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

The parked entries of every subscription of the node, the built-in ones, the intakes' and those of other contexts' topics included, as an operator lists and acts on them.

Link copied to clipboard

The partitions the node's subscriptions have paused on a systemic failure, the built-in ones, the intakes' and those of other contexts' topics included: what the health model reads.

Link copied to clipboard

The aggregate models this node serves, the built-in job aggregates among them.

Link copied to clipboard

Where commands run: Routing.PARTITIONED over every context's lease group, or Routing.LOCAL.

Link copied to clipboard
Link copied to clipboard

Delivers signals: to waits when they are this node's, through the remote sender otherwise.

Link copied to clipboard

Whether the snapshots of an aggregate type are being rebuilt or backfilled on its primary target: its context's SnapshotMaintenance.rebuilding; false for a type whose snapshots are not kept here.

Link copied to clipboard

Whether the node is on standby: started with standby and not activated yet.

Link copied to clipboard

Where the node is in its lifecycle.

Link copied to clipboard

The estimated bytes each context's cache holds when no capacity is configured: sized from the heap over the contexts the node serves; null when a capacity bounds it.

Link copied to clipboard

The aggregate states each context's cache keeps: the configured capacity, or null when it is bounded by stateCacheBytes.

Link copied to clipboard

The partitions the node's subscriptions throttle on backpressure now, the built-in ones, the intakes' and those of other contexts' topics included.

Link copied to clipboard

The node's one firing loop of every context's timers.

Link copied to clipboard

The topics the node's contexts own and use, which start validates or creates as SourceTopics.ddl says: the command topic of each context that takes commands from it, with exactly its log's partitions; the receipts topic of each context that reads its rejections; with a producer, the topic of each stream its contexts publish, and, under Ddl.VALIDATE, the command topic of each peer context whose contract lists commands, where the gateway sends them: that topic is the peer's, so it is checked, never created. None without topics.

Link copied to clipboard

The subscriptions of other contexts' published streams, by the topic they read: one runtime per topic on this node. Wow's own: health, pauses and tests read it; its type is internal to Wow.

Link copied to clipboard

The waits started on this node.

Functions

Link copied to clipboard
suspend fun activate(by: String, reason: String? = null): Activation

Activates a node started on standby, at a migration's cut-over: joins every context's lease group and every topic's, starting the subscriptions there (one that starts at the end of its stream starts where the stream ends now), the retries of the parked entries, the jobs and the snapshot maintenance, and then admits commands. It is the operator's act, recorded for the audit: by names who activated it and reason why, both logged with the time. A node not on standby is left as it is: the answer says when it was activated, if it ever was. An activation that fails stops the node and throws.

Link copied to clipboard
fun bind(address: String)

Gives the node built without an internal address the address its host's internal port was bound at, such as one whose port the system chose: before start, once.

Link copied to clipboard

The parts of context.

Link copied to clipboard
suspend fun start()

Registers the application's subscriptions and joins every context's lease group: heartbeats, reconciles, and acquires its share of the partitions, starting their subscriptions. A start that fails stops what it started and throws.

suspend fun start(standby: Boolean)

Starts the node as start does, on standby when standby says so (see start).

Link copied to clipboard
suspend fun stop()

Leaves the cluster and closes the node, in the order NodeLifecycle describes. Stopping twice does nothing.

Link copied to clipboard

What an operator reads of the node's subscriptions, the built-in ones included, each by the group it runs in, and of the checkpoints its groups hold under names they no longer have:

Link copied to clipboard

The append latency of the store store (wow.stores.<name>), as this node's logs on it measure it; null when none of them is on it.