II Basics

Events

An event announces that a component’s state changed. Operations do not send events. The runtime derives them from committed changes, and components declare which changes are worth announcing and who reacts to them.

§A consequence of a commit

Every committed change gives the runtime the same facts:

originThe state before the change, or null if the entity did not exist.
stateThe state after the change.
inputThe input of the operation that made the change.
trailersWhat the operation said about the change besides the state itself.

An event is a declaration over these facts. Because it is evaluated after the commit, an event cannot announce something that did not happen, and a change cannot happen without its events.

§Condition

An event’s condition decides whether a given change is an occurrence of it:

// orders/events/approved.ts
export function condition (event: Event<Entity>) {
  return event.origin?.status !== 'approved' && event.state.status === 'approved'
}

The condition describes a fact about state, not a call to a particular operation. Whichever operation moves an order to approved — a dedicated approve, a generic transit, one added next year — produces orders.approved.

§Payload

By default an event carries the resulting state. A payload function shapes what subscribers receive instead:

// orders/events/approved.ts
export function payload (event: Event<Entity>) {
  return {
    order: event.state.id,
    customer: event.state.customer,
    total: event.state.total
  }
}

The payload is the event’s contract with other components. Declaring it separately from the entity lets the component change its internal state without changing what it announces.

§Trailers

Sometimes the meaning of a change is not recoverable from the two states. A total that went from 1500 to 1000 may have been discounted or partially refunded. An operation records such facts as trailers:

export async function transition (input: Input, entry: State) {
  entry.total -= input.amount
  entry.TRAILERS = { reason: 'refund', amount: input.amount }
}

TRAILERS is a property the state has only while a transition runs. Trailers are not stored with the entity. They accompany the change to its events, where conditions and payloads can use them:

// orders/events/refunded.ts
export function condition (event: Event<Entity>) {
  return event.trailers?.reason === 'refund'
}

§Inherited events

A component with an entity inherits created, updated, and deleted from the generic prototype. A subscriber interested in every change to orders does not need the orders component to declare anything.

§Receivers

A receiver binds an event to an operation of the receiving component:

# billing/manifest.toa.yaml
receivers:
  orders.approved: charge

The event is identified by its label: the publishing component’s address followed by the event name. The receiver may adapt the payload into the request its operation expects, and may decline events it has no use for:

// billing/receivers/orders.approved.ts
export function condition (payload: { order: string, total: number }) {
  return payload.total > 0
}

export function request (payload: { order: string, total: number }) {
  return { input: { order: payload.order, total: payload.total } }
}

The operation a receiver invokes is an ordinary operation. It is validated, retrieved, and committed like any other call, and it can be called directly as well. What differs is that nobody waits for its reply.

Among the replicas of the receiving component, one handles each event. Different components subscribed to the same event each receive it.

§Direction of dependency

The publisher does not list its subscribers and does not know they exist. The dependency points from the receiver to the event’s contract. A new reaction to order approval is added by writing a receiver in a new component; orders does not change.

An event that nothing subscribes to costs nothing: the runtime publishes an event only where something consumes it.

§Events from outside

An application can react to events it does not produce, such as messages published by another system on a broker. A receiver for such a foreign event names where it comes from, since the application has no component whose contract would say:

receivers:
  payment_settled:
    binding: amqp
    source: gateway
    operation: settle

In the other direction, the Context can state that an event is consumed outside the application, so that it is published even though no component of the application receives it.

§What delivery promises

Events are delivered at least once and in no guaranteed order. The reasons, and what they ask of a receiver’s operation, are the subject of Delivery and Transactional Outbox.

Search
Search the documentation