Point the Application at Its Database, Broker and Cache
The shop runs against a database on your machine, another in staging and a cluster in production, and the orders are to move to a database of their own. None of that touches a component: where things are is written in the Context, the same way for the database, the broker and the cache, and the user names and passwords are supplied separately as secrets.
§One address for everything
# context.toa.yaml
amqp: amqp://localhost
mongodb: mongodb://localhost
stash: redis://localhost These are the lines toa create wrote, with the addresses of what npm run dock starts. Every component of the application uses that broker, that database and
that cache.
| Key | Address of | Read |
|---|---|---|
amqp (amqp.context) | the message broker | Bindings |
amqp.sources.<source> | a broker of another system | Brokers of other systems |
mongodb | the database | Storages |
stash | Redis for the stash | Cache Something All Replicas Share |
http | components, for streamed calls | The HTTP binding |
atomicity.redis and the telemetry endpoints are addresses too, but plain ones: one value for
the whole application, without the per-component keys described below.
§Per environment
Suffix the key:
# context.toa.yaml
mongodb@local: mongodb://localhost:27017
mongodb@production: mongodb+srv://db.example.com
stash@local: redis://localhost:6379
stash@production: redis://redis.internal:6379
amqp:
context:
.@local: amqp://localhost:5672
.@production:
- amqp://rmq-0.internal:5672/store
- amqp://rmq-1.internal:5672/store An address is a URL as the technology understands it, so it may carry a path, such as a broker’s virtual host, and options in its query string.
§A different address for one component
Written as a map, a declaration gives an address for everything, for a namespace, or for one
component. . is the default:
# context.toa.yaml
mongodb:
.: mongodb://mongo.example.com # everything else
store: mongodb://store.mongo.example.com # every component of the namespace `store`
store.orders: mongodb://orders.mongo.example.com # `store.orders` alone A component takes the longest key that matches its id:
| Component | Takes |
|---|---|
store.orders | orders.mongo.example.com |
store.catalog, store.vouchers | store.mongo.example.com |
billing.wallets, billing.ledger | mongo.example.com |
accounts | mongo.example.com |
mongodb: mongodb://mongo.example.com is the same map with the single key ..
A component that matches no key, where there is no ., is refused, and the message names the
component.
A component without a namespace is keyed by its name: accounts for one, default for all
such components.
§Several addresses under one key
A key may hold a list, and every component that resolves to it is given all of them:
amqp:
context:
.:
- amqp://rmq-0.example.com
- amqp://rmq-1.example.com Numbered hosts can be written as a range in brackets:
amqp:
context: amqp://rmq-[0..31].example.com which is rmq-00 to rmq-31: every number is as wide as the end of the range. What several addresses mean depends on who reads them: the
broker binding uses them as nodes to connect to, while the stash uses only the first.
§Credentials
An address never contains credentials. One that does is refused:
Pointer URI 'mongodb://user:pass@mongo.example.com' must not contain credentials. §Locally
toa env writes one pair of variables per secret
into .env, and keeps what is already in the file when it runs again:
$ npx toa env --interactive # asks for each value
$ npx toa env --dev # fills in development defaults npm run env runs the second. Its defaults, developer and secret, are the user and the
password the generated docker-compose.yaml gives the broker and the database.
§In a deployment
A user name and a password are supplied as a secret in the environment the application is deployed to. There is one secret per key of the declaration, not per component: every component that resolved to a key shares it, and so does every address listed under it.
Ask for the list of what an environment needs:
$ npx toa export secrets production
toa-amqp-context.default:
username
password
toa-mongodb.default:
username
password Each holds the keys username and password:
$ npx toa conceal mongodb-store-orders username=orders password=secret
$ npx toa conceal amqp-context.default username=store password=secret toa conceal deploys the secret with the prefix toa- (toa-mongodb-store-orders). A secret
created by other means has to carry that prefix itself.
The name is made of the declaration’s identifier and the key, with dots turned into dashes:
| Declaration | Key | Secret |
|---|---|---|
mongodb | . | mongodb.default |
mongodb | store | mongodb-store |
mongodb | store.orders | mongodb-store-orders |
amqp.context | . | amqp-context.default |
amqp.context | store.orders | amqp-context-store-orders |
amqp.sources.payments | — | amqp-sources-payments |
A deployment into an environment that lacks any of them is refused before anything is built, naming what is missing:
Secrets are not deployed: toa-mongodb.default/username, toa-mongodb.default/password Addresses on protocols that carry no credentials have no secret: redis, http and https.
TLS client configuration is not available this way.
§Not the same as a secret of configuration
- The credentials of an address are named after the declaration and supplied as described here.
- A secret value of a component’s configuration, such as the key of an external API, is
declared
format: secretin the manifest, referenced as$NAMEin the Context, and deployed into the secretconfiguration. See Keep an API key out of the repository.
External HTTP services a component calls through context.fetch are of the second kind: their
address and key are configuration of the calling component
(Call an External Service).