Skip to content

The charter ​

Rules before code. Each one says what it guarantees, because a rule whose reason no one sees anymore ends up being dropped. Each module is a microservice from its first line of code, and the package holds it to that, even while it shares a process with the others.

#RuleWhat it guaranteesHow the package holds it
1Each module is designed as a microserviceIts routes, jobs, commands, listeners, configuration and data belong to it, so it can leave on its own when the day comes.The module's service provider loads its config, routes, commands, listeners and schedule; it has its own connection (data).
2A module knows of the others only their contracts, in the shared layerWhat a module agrees to show (its contracts, the shape of the data it exchanges, the names of its events) lives in the foundation. Nothing a module uses disappears when a neighbour moves to another process.The foundation uses no module and every module may use it. A module this process doesn't run is never autoloaded. Boundaries reports a module naming another's namespace.
3Ask through a contract, announce through an eventNo module writes to another's data.RPC on a contract, events on a stream, and read-only copies. Boundaries reports another module's connection or tables.
4A module chooses neither its connections nor its settings: its context provides themIts code always runs in its module's context, with its connections, its configuration and its services. Outside any module there is no database, so it has nothing to divert to reach a neighbour's.ModuleContext sets the context at every way into a module's code. Outside a module the connection is no_module.
5The same code, whatever the topologyWhether two modules run together or apart, neither the caller nor the callee changes.A call to a module this process runs is a direct method call; any other goes out on the module's transport. Events travel on the stream in both cases.
6Deployment is configurationWhich modules run in which process is a setting, not a decision written into the code.RUN_MODULES and distributable.declared.{module}.host; see Deploying modules.
7Boundaries are checked before deploymentWhat separate applications prevented, a check must detect: a forbidden import or a neighbour's table named in code fails the tests.modules:doctor and Boundaries, see Checking the application.

Nothing invented ​

None of these rules is new: they are proven microservices patterns, applied here to modules. What changes is that they are in place by default, with no ceremony in the business code.

PatternThe problem it solvesHow it applies to a module
Bounded contextwhere a domain endsone module = one domain, with its code, its config, its routes
Autonomous service (database per service, and beyond)a service reaching what belongs to anotherthe module's context: its connections, its configuration, its services; nothing that belongs to a neighbour is reachable from it
Shared kernelwhat two domains share, and nothing morethe foundation: contracts, exchanged data, event names
Transactional outboxan event lost when the data is saved but sending failsopt-in: the event is kept in the same transaction as the data until it is sent
Idempotent consumeran event received twiceon with a transport that redelivers: each handled event is marked; a replay does nothing
Event-carried state transferreading another's data without calling it every timeshadows: a local reflection of its data, kept up to date by its events
Event versioningevolving a message without breaking those who listen to iteach event carries a version; older ones are converted on read
Ports & adapterschanging transport without touching the business logicthe same contract as a local call or over RPC; stream and RPC are swappable