Host and Module
A Host is the application you deploy; a Module is a business boundary inside it, reached only through its Gateway.
What it is
A Host is the application you deploy: one process that runs Operations. A Module is a business boundary inside a Host. It owns its data, its rules and its public Contracts, the messages it publishes and the questions it answers.
Inside a Module, code is grouped by Business Area, one aggregate or a close group of them, and each Business Area holds its Features: one command or query with everything needed to serve it. One Host holds one container for all of its Modules. What keeps the Modules apart is the compiler and the structure tests: everything in a Module stays internal except its Composition Root and a few named kinds of type, and a Module references another only through that Module's Contracts.
Modules of one Host never call each other over HTTP
Inside a Module, aggregates talk through Domain Events. Across Modules, a Module tells the others what happened with an Integration Event, or keeps a copy of their data from Snapshots. It asks another Module a question directly, through that Module's Contracts, only when the Operation cannot go on without the answer.
When it matters
- One business boundary: start with the monolith template, one Module's data and rules in one Host. It grows into the modular monolith when a second Module arrives, without changing its business code. See Templates and Choose a template.
- Several boundaries in one process: the modular monolith, each Module reached through its Gateway.
- One Module in a process of its own: the service template, which receives another Host's Integration Events and Snapshots over RabbitMQ.
How it works
Each Module declares everything it needs and offers in one Composition Root: a public sealed class that implements IModuleDefinition. Its Configure method declares the Module's data, its Features, what it publishes and subscribes to, and its endpoints, on a ModuleBuilder. The Host adds each Module once, with AddPouryaKitModules. The Composition Root section shows the Proving Sample's Catalog and the Host that composes it.
Anything outside a Module asks it to run a command or query through its Gateway, IGatewayProcessor<TModule>. Each call is one Operation in a scope of its own, run with the Operation Context the caller passes. An endpoint is the most common caller. This one, from the Proving Sample's Inventory Module, sends a query through Inventory's Gateway:
// No :guid route constraint: an id that does not parse is the caller's mistake (400), not a missing route (404).
internal sealed class GetStockItemEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder routes) =>
routes.MapGet(
"/stock-items/{productId}",
async (
Guid productId,
OperationContext context,
IGatewayProcessor<InventoryModule> inventory,
CancellationToken cancellationToken
) =>
(
await inventory.QueryAsync(
new GetStockItemQuery(productId),
context,
cancellationToken
)
).ToHttpResult()
);
}When another Module needs an answer before its Operation can go on, the owning Module publishes the question as an interface in its Contracts and answers it through its own Gateway. Asking another Module a question walks through how Catalog answers product questions.
What the Kit guarantees
| Guarantee | Stated in |
|---|---|
| What a Module may make public, and what it may reference | Structure tests |
| Which Module's Gateway runs a command or query | Which Module an Operation belongs to |
| One Operation per Gateway call, in its own scope | The Gateway |
| How Modules talk to each other | Asking another Module a question |
| What a mistake in a Host's composition does | The Host's checks |