PouryaKit.Modularity
Modules for a Host.
Modules for a Host. A Module is a business boundary that declares what it needs and offers in one public class, its Composition Root, and keeps everything else internal. Anything outside it, an endpoint, a job, another Module or a test, runs one of its Operations through its Gateway and passes an Operation Context that says which Actor it runs for.
Install
dotnet add package PouryaKit.Modularity --prereleaseWhen to use it
- You are writing a Module's Composition Root, or composing the Host from its Modules.
- Code outside a Module needs to run one of its commands or queries.
- One Module needs an answer from another before its Operation can go on.
- A Host fails when it is built or started, and you want to know which check stopped it.
It builds on PouryaKit.Cqrs. A Host with a single Module may leave it out, as the monolith template does until a second Module arrives.
Quick start
Add each Module to the Host:
builder.AddPouryaKitModules(modules => modules.Add<InventoryModule>());Declare the Module in its Composition Root, as the Proving Sample's Billing does:
public sealed class BillingModule : IModuleDefinition
{
public void Configure(ModuleBuilder module)
{
module.UsePouryaKitPostgres<BillingDbContext>(connectionName: "app", schema: "billing");
module.AddPouryaKitCqrs();
module.Subscribe<ProductCreatedV1, OpenPriceListIntegrationEventHandler>(
"billing.product-created"
);
module.Subscribe<AccountOpenedV1, OpenWelcomeQuoteIntegrationEventHandler>(
"billing.account-opened"
);
module.AddPouryaKitDelayedCommand<ExpireQuoteCommand>("billing.expire-quote");
}
}Run an Operation through its Gateway, with the context of the running request. This endpoint also needs PouryaKit.Web, which begins the request's Operation and supplies ToHttpResult():
routes.MapGet(
"/stock-items/{productId}",
async (
Guid productId,
OperationContext context,
IGatewayProcessor<InventoryModule> inventory,
CancellationToken cancellationToken
) =>
(
await inventory.QueryAsync(
new GetStockItemQuery(productId),
context,
cancellationToken
)
).ToHttpResult()
);What you get
- One public class per Module, with everything else
internal, and architecture tests that hold the boundary. IGatewayProcessor<TModule>, the only way in: each call is one Operation in a scope of its own.- Every Operation belongs to one Module, which owns what it declares.
- A Host that fails when it is built or started, naming what its composition got wrong, before it serves.
There is no anonymous or default Actor. Every Gateway call needs an Operation Context: create a new one for each Operation that starts work, with Actor.Person or a named Actor.System, and pass Continue() of the running context for work an Operation causes.