VI Platform Services

Connectors

The execution model speaks of operations being run, state being stored, and calls being carried. It does not say in what language, in which database, or over which transport. Those are supplied by connectors: replaceable parts that attach the model to concrete technology.

There are three kinds, one for each verb.

§Bridges

How logic runs

A bridge runs an operation’s implementation. It knows how to load code written in some language, call it with input, state, and context, and return its result.

The bridge supplied for general use runs JavaScript and TypeScript. The conventions of writing an operation belong to it: an operation is a file named after its endpoint, the name of the exported function states the operation’s type, and the name of its second parameter — entry, entries, changeset, stream — states its scope.

Because the model addresses logic only through a bridge, an operation’s language is a property of that operation. Components written for different bridges take part in the same application, call one another, and receive one another’s events, since what passes between them is defined by the model and not by the language.

§Storages

How state is kept

A storage connector retrieves and commits entity state. It maps entities to whatever the underlying database provides and implements queries over them.

A component names its storage in its entity declaration, and there is a default. The operations of that component are unaffected by the choice; they receive and return plain state either way.

Storages differ in what they can guarantee, and some features of the runtime depend on those guarantees:

Capability of the storageWhat depends on it
Atomic write of an entity with a record beside itTransactional outbox, once
Atomic write of several entitiesTransitions over a set
Memory of the order of committed writesChanges in collection streams

A component that asks for a feature its storage cannot support does not start, and says which feature and which storage. The runtime prefers this to a guarantee that silently does not hold.

A component with no entity uses no storage at all.

§Bindings

How calls travel

A binding carries requests, replies, and events between processes.

The primary binding is a message broker. Its properties are where several of the runtime’s guarantees come from: a call waits for an absent component because the broker holds it, and an event survives the restart of its receiver for the same reason.

Within one composition, calls do not leave the process. They are passed in memory with the same interface and the same validation. This is the mechanism behind location transparency: a component makes the same call either way, and the runtime chooses the path.

A second binding carries streamed calls directly between processes, since a stream in progress cannot be held by a broker.

Bindings are also how an application connects to systems that are not Toa applications. A receiver for a foreign event names the binding and the source it consumes from.

§Why the parts are separate

Each kind of connector isolates one technology decision from the others and from business logic.

Business logic survives infrastructure change. Moving a component to another storage, or an application to another transport, changes declarations and connectors. Operations stay as they are, because they never referred to either.

Guarantees are stated against capabilities. The runtime’s promises are defined by the model and delivered by connectors. Where a connector cannot deliver one, the gap is explicit.

The runtime is extensible at the bottom as well as the top. Extensions add capabilities beside the model, and connectors replace what is beneath it. An application that needs another database, language, or transport adds a connector.

§An opinionated default

Replaceable parts do not mean that an application begins by choosing among them. In keeping with the runtime’s stance on choices, there is one supplied connector of each kind for general use, and they are the defaults. Most applications never name a connector.

The separation exists so that the choice can be revisited when a real requirement appears, and so that it can be made for one component without affecting the rest.

Search
Search the documentation