Skip to content

Connector

Connectors are uni-directional conduits for reliably transmitting data of a particular type. A connector joins exactly one outlet to exactly one inlet.

connector OrderFlow is
  from outlet OrderEventSource.OrderEvents
  to inlet OrderEnricher.RawOrders
with {
  briefly as "Connects the order source to enrichment"
}

Data Transmission Type

A connector transmits one data type. The transmission type is often an alternation of messages, such as the commands and queries an entity might receive.

The outlet's type and the inlet's type must be compatible. A port typed Anything is compatible with every other type — which is what lets the standard module's universal terminators accept any stream.

Port Cardinality

Exactly one connector may attach to any given port.

Fan-in and fan-out are modeled by declaring multiple ports — the arity is what derives a merge or split shape — never by attaching several connectors to a single port.

// Correct: a split declares two outlets, each with its own connector
processor Router as split is {
  inlet incoming is type Order
  outlet domestic is type Order
  outlet international is type Order
}
connector ToDomestic      is from outlet Router.domestic      to inlet Home.in
connector ToInternational is from outlet Router.international to inlet Abroad.in

Attaching more than one connector to a port is an Error.

Scope

A connector may be declared in a Context or, when its two ends are in different contexts, in the enclosing Domain.

Placement validation

Both ends are resolved, and their owning contexts and domains compared:

  • Error — the ends resolve to different domains. A stream edge across a domain boundary is a failure of domain analysis, not something to wire around.
  • Error — a domain-scoped connector whose ends share one context. It is over-scoped; move it into that context.
  • Error — a context-scoped connector whose ends cross contexts. It is under-scoped; promote it to domain scope.
  • CompletenessWarning — a domain-scoped cross-context connector without the persistent option. Durability at a context boundary can be model correctness, not merely a deployment concern.

These checks are conservative: they only fire when both ends resolve.

Options

Connector options are advice to the translators converting a connector into something else. A connector often plays a large part in a reactive system's resilience, so several are available.

Option Arguments Meaning
persistent 0 Messages are persisted to stable, durable storage, so they survive failure or shutdown
ordered 0 Delivery preserves order
unordered 0 Delivery order is not significant, enabling partitioning and parallelism
at-least-once 0 Each item is delivered one or more times; handlers must be idempotent
at-most-once 0 Each item is delivered zero or one times; loss is possible
exactly-once 0 Each item is delivered precisely once
partitioned 1 Data is partitioned by a key so a consumer group can process it in parallel
circuit-breaker 0–2 Trip the connection when the downstream is failing

persistent

Messages flowing through the connector are persisted to stable, durable storage, so they cannot be lost even if the system fails or shuts down. This arranges a kind of bulkhead that retains published data despite failures on either end.

Removing the burden of persistence is likely to make a connector considerably more performant, since storage latency is no longer involved — which is why it is opt-in rather than the default.

Delivery semantics

at-least-once requires the implementation to make handling idempotent, so that running an item twice has the same effect as running it once. at-most-once relaxes that to best-effort, which may increase throughput and lower overhead where data loss is not catastrophic — some IoT systems permit it because the next transmission is imminent.

Producers and Consumers

Attached to the ends of connectors are producers and consumers. These are processors that may originate, terminate, or flow data through them, joining two connectors together.

graph LR;
Source --> C1{{Connector 1}} --> Flow --> C2{{Connector 2}} --> Sink

Because each port takes exactly one connector, a processor that must serve several downstream consumers does so by declaring several outlets — becoming a split — rather than by sharing one outlet among several connectors.

Occurs In

Contains

Nothing