II Business Logic

The Operation Context

An order above a configured limit is refused; approving one debits a wallet kept by another component; a voucher needs an id and an expiry time. Everything an operation needs from outside itself comes through its last argument, the context:

// components/store.orders/operations/approve.ts
import type { Context, State } from '../types/index.d.ts'

export async function transition (input: null, entry: State, context: Context) {
  if (entry.total > context.configuration.limit)
    return new Error('LIMIT_EXCEEDED')

  const reply = await context.remote.billing.wallets.debit({
    query: { id: entry.customer },
    input: { amount: entry.total, ref: entry.id }
  })

  if (reply instanceof Error)
    return reply

  entry.status = 'approved'

  return entry
}

approve is the transition Create a Component writes for store.orders. limit is the setting Add Configuration declares, and billing.wallets.debit the operation Refuse a Request writes: it takes an amount and a ref, what the money is for.

$ npm run restart
$ npx toa call store.orders.approve "{ query: { id: '5b1ad5d8a4c54e1dbb2a4a0b6e1f0c11' } }"

It is the last argument whatever the operation’s type:

export async function transition (input: Input, entry: State, context: Context) { /* … */ }
export async function computation (input: Input, context: Context) { /* … */ }

Events and guards are given a context too.

§Calls

Where
context.local.<operation>(request)Calls an operation of the same component.Call Operations and Select State
context.remote.<namespace>.<name>.<operation>(request)Calls an operation of another component; context.remote.<name>.<operation>(request) where it has no namespace.Call Operations and Select State

§Ids, time and randomness

context.id()A new id, of the same form as an object’s.
context.now()Milliseconds since the epoch, as Date.now().
context.random()A number in [0, 1), as Math.random().

Use these instead of Date.now(), Math.random() and id generators. An operation written as a multi-step workflow is given the same answers when it is resumed; a value taken elsewhere would differ between the two runs.

§Where it runs

Where
context.nameThe name of the application, as in the Context.The Context
context.envThe environment it was deployed as.Environments
context.regionThe rank of the region; 0 unless there are several.Regions
context.instanceThe name of this process.Keep Something in a Process

Prefer configuration to branching on context.env.

§What extensions add

Some members are there for every component, others once the manifest declares the key.

Manifest keyWhat it is forWhere
context.configurationconfiguration:Values set per environment.Add Configuration, Configuration
context.fetch()—HTTP requests to other systems.Call an External Service
context.delay(), context.delay.cancel()cadence:Calling an operation later.Do Something Later
context.schedule(), context.schedule.cancel()cadence:Calling an operation on a calendar of an item’s own.Run Work on a Schedule
context.statestate:Something kept in the process.Keep Something in a Process
context.stashstash:A shared cache.Stash
context.atom—Locks and decisions shared by replicas.Shared Decisions
context.storagesstorages:Files.Storages
context.logs—Log entries.Logs
context.span—Spans of a trace.Traces
context.metricstelemetry:Metrics of your own.Metrics
context.aspects.<name>()the extension’s keyWhat an extension of your own provides.Own Extensions

How a component declares an extension is in Extensions.

§Rules

Read from the context when you need a value; do not copy values out of it. The context object lives as long as the component, and what it holds can change: configuration may be updated while the component runs.

Reach the outside world through the context. Reading a file that ships with the component and using a pure library are fine. Opening connections, reading environment variables and keeping module-level state are not; see the rules for operation code.

Context in the model explains why there is one door.

Search
Search the documentation