I First Steps

Add Configuration

The shop refuses orders above a limit, and the limit is not the same everywhere: large in production, small where the tests run. A value like that is configuration: the component declares it, reads it from the operation context, and the Context sets it.

The component is store.orders, which Create a Component writes; its create forwards to the inherited transit, and Expose It over HTTP serves it at POST /store/orders/.

§What is configuration

Configuration is a parameter of an algorithm that may have different values in different environments.

A value that cannot differ between environments is a constant, and stays in the code, however much it looks like a setting. Which channels an order confirmation goes over depends on the templates that exist: the email one is written, the SMS one is not. An environment that named sms in the configuration would have nothing to send it.

A value is configuration when the answer to both questions is yes:

  1. Could it differ from one environment to another?
  2. Would every value the schema allows work with the code as it is?

The limit is one. create refuses an order above it, be it 100 or 5000, and the local environment sets it lower, so that a test reaches it with a small order.

§Declare the value

# components/store.orders/manifest.toa.yaml
configuration:
  schema:
    type: object
    properties:
      limit:
        type: integer
        minimum: 0
    required: [limit]
  defaults:
    limit: 5000

schema is a JSON Schema of the component’s configuration; defaults is what the component runs with unless the Context says otherwise.

The schema is there to keep a wrong value from becoming a runtime exception, so make it as tight as the code. A list of currencies is not an array of any strings: its items are an enum of the currencies the code can price, tax and charge.

§Read it

The rule needs code, so create stops forwarding and becomes an operation of its own:

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

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

  Object.assign(entry, input)

  return entry
}
# components/store.orders/manifest.toa.yaml
operations:
  create:
    query: false
    errors: [LIMIT_EXCEEDED]
    input:
      type: object
      properties:
        customer: { type: string }
        items: { type: array }
        total: { type: integer, minimum: 0 }
      required: [customer, items, total]

context, the last parameter of every operation, is the operation context. context.configuration is the configuration object, already checked against the schema, so the operation does not validate it. Never read an environment variable or a file for a setting.

Try it:

$ npm run restart
$ curl -i http://localhost:8000/store/orders/ 
    -H 'content-type: application/json' 
    -d '{"customer":"c1","items":[],"total":7000}'
HTTP/1.1 422 Unprocessable Entity
content-type: application/json

{"code":"LIMIT_EXCEEDED"}

A component that declares configuration gets it from a service of sys, the process that runs beside the application.

§Set the value in the Context

# context.toa.yaml
configuration:
  identity.tokens:
    # …
  store.orders:
    limit: 100000

context.toa.yaml is the Context: what is true of the application as a whole. toa create wrote it with the addresses of the local infrastructure and one setting, the key the gateway signs users in with. Values are given per component, by <namespace>.<name>. A value here stands over the manifest’s default; a default the Context does not mention stays.

The Context is read when .env is written, which npm run restart does first:

$ npm run restart

The same request now answers 201 Created.

A key that names no component, or a component that declares no configuration, stops toa env:

Component 'foo.bar' does not request configuration or does not exist.

§One value per environment

Append @<environment> to a key to give it a value for one environment only:

# context.toa.yaml
configuration:
  store.orders:
    limit: 100000
    limit@local: 10000

local is the environment of your machine, the one toa env writes when no other is named. The limit is 10000 there and 100000 in every other environment. An environment is not declared anywhere; it is named on the command line:

$ npx toa env                              # local
$ npx toa env staging --as .env.staging    # another one, into another file
$ npx toa deploy production

The suffix works on any key of the Context, at any depth, which is how the addresses toa create wrote get their production values:

# context.toa.yaml
mongodb: mongodb://localhost
mongodb@production: mongodb://mongo.internal.example.com

exposition:
  authorities:
    local: localhost:8000
    local@production: api.store.example

The tests run in local, so they can rely on its limit:

# features/orders.feature
  Scenario: An order above the limit
    When the request is sent:
      """
      POST /store/orders/ HTTP/1.1
      content-type: application/yaml
      accept: application/yaml

      customer: c1
      items: []
      total: 10001
      """
    Then the response is received:
      """
      422 Unprocessable Entity

      code: LIMIT_EXCEEDED
      """
$ npm run env
$ npm run features

The steps of this scenario are the ones toa create wrote; Write a Test explains them.

Everything about environments is in Give Each Environment Its Values.

§Secrets, and changing a value while running

A value that must not be stored with the sources, an API key for instance, is declared with format: secret; the Context then holds only a $NAME reference, as it does for the key under identity.tokens, and the value is supplied to each environment separately. Keep Settings and Secrets out of the Code covers secrets, nested values and everything else the manifest and the Context take. A value can also be changed in a running application, without a deployment: Change a Setting without Redeploying.

Search
Search the documentation