Configuration
Business logic depends on values that are not logic: a credit limit, a provider’s address, an API key, a feature that is on in one place and off in another. Such values change on a different schedule from code, differ between environments, and are sometimes secret.
In Toa a component declares the configuration it needs, and the platform supplies it. The component does not read environment variables or files.
§A contract for configuration
A component declares its configuration as a schema, with defaults:
# orders/manifest.toa.yaml
configuration:
schema:
type: object
properties:
limit: { type: integer, minimum: 0 }
currency: { type: string }
defaults:
limit: 5000
currency: EUR Operations read it from the context:
export async function transition (input: Input, entry: State, context: Context) {
if (input.total > context.configuration.limit)
return new Error('LIMIT_EXCEEDED')
entry.total = input.total
} The schema makes configuration a contract in the same sense as an operation’s input. A value that does not fit is refused before it reaches the component, so an operation never has to handle a malformed setting.
§Layers of values
A component’s configuration is resolved from layers, each overriding the one before it.
- Defaults in the manifest: what the component’s author considers sensible.
- The Context: what this application wants instead.
- The environment: what a particular deployment wants.
# context.toa.yaml
configuration:
orders:
limit: 10000
limit@staging: 100 The component is unaware of the layering. It sees one validated object.
§Secrets
A value that must not be stored with the application’s sources is declared as a secret:
configuration:
schema:
properties:
api-key: { type: string, format: secret } The Context then refers to the secret by name instead of containing it, and the value itself is given to the deployment separately. Inside an operation, a secret is not a plain string. It has to be explicitly unwrapped to be used, and it prints as redacted if it is logged or serialized by accident.
The declaration carries the intent. A reader of the manifest knows which values are sensitive, and the platform treats them accordingly at every step.
§Changing values while running
Configuration can be changed without deploying. A new set of values is created for a component, validated against its schema, and delivered to the running processes, which use it from then on.
Each change is recorded as a new immutable object, together with the identity that made it. Nothing is edited in place, so the history of what a component was configured with, and by whom, is kept.
A component can also be reset. It then returns to its deployed defaults and continues to follow them through later deployments.
§Schema changes
When a component’s configuration schema changes, values recorded for the old schema may no longer fit. Each set of values is therefore tied to the schema it was validated against. After a deployment that changes the schema, a component starts from its deployed defaults and does not inherit values written for a different shape.
This removes a class of failure in which a value set long ago silently breaks a new release.
§What configuration is not
Configuration is for values that tune behavior and are the same for every replica of a component. Two things resemble it and belong elsewhere.
Infrastructure addresses — the database, the broker, the cache — are deployment concerns declared in the Context for the extensions and connectors that use them. A component never sees them.
Business data that users change — prices, catalogs, permissions — is entity state. It has its own operations, events, and history, and it is not configuration even when it changes rarely.