The Proving Sample
The application whose passing tests are the evidence that the Kit's guarantees hold, and the source of the templates' business code.
What it is
The Proving Sample is a modular monolith built on the Kit: four Modules, the Hosts that compose them and the tests that prove every guarantee against PostgreSQL, and against RabbitMQ and Redis where they apply. The Project templates generate their business code from it.
Read its code through the modular monolith
In the modular monolith, the business code, its Composition Roots and its endpoints are the sample's own files. dotnet new pouryakit-modular-monolith gives you its Modules to read. Its tests are the Kit's own and are not published; a generated solution has a smoke test instead.
When to use it
As the model for your own code: how a Module is laid out, what a Feature's files hold, how Modules talk, and how a test runs an Operation against a real database.
The Modules
| Module | Write model | What it shows |
|---|---|---|
| Catalog | EF Core, tenant-scoped | Products and categories; publishes ProductCreatedV1, ProductCreatedV2 and a Snapshot of each product; reads with Dapper; an agent that answers questions about products |
| Inventory | EF Core | Opens a stock item for each new product, keeps a copy of Catalog's products from their Snapshots, and reconciles stock on a cadence |
| Billing | EF Core | Opens a price list for each new product, and a welcome quote for each new account, whose expiry is a delayed command |
| Ledger | Stream | Accounts and their entries as appended events; publishes AccountOpenedV1, which Billing receives |
The Hosts
| Project | What it is |
|---|---|
ModularMonolith.Host | The four Modules in one Host, with the Kit's report Module |
Inventory.Host | Inventory alone, receiving Catalog's Integration Events and Snapshots over RabbitMQ: the service template's composition |
ModularMonolith.ReverseProxy | A YARP Reverse proxy of its own process in front of the Hosts |
ModularMonolith.AppHost | An Aspire AppHost that runs the Hosts and their resources |
ModularMonolith.ServiceDefaults | The service defaults recipes, which the templates generate as the application's own code: Host recipes |
A Feature, file by file
A Module is split into Business Areas, such as Catalog's Products, each with its entities, data access, events and Features together. A Feature is one command or query and everything that serves it, in a folder of its own. Renaming a product is Products/Features/RenameProduct:
The command
A record of what the caller asks for. It also names the cache entry it makes stale, so the Kit removes it once the command commits.
internal sealed record RenameProductCommand(Guid ProductId, string Name)
: Command,
IInvalidatesCache
{
public IReadOnlyCollection<string> InvalidatedCacheTags =>
[GetProductQuery.ProductTag(ProductId)];
}The validator
Run by the pipeline's one validation step, before the handler; its failures come back as Errors with their codes and targets: Validation.
internal sealed class RenameProductCommandValidator : AbstractValidator<RenameProductCommand>
{
public RenameProductCommandValidator()
{
RuleFor(command => command.ProductId).NotEmpty();
RuleFor(command => command.Name).NotEmpty().MaximumLength(200);
}
}The handler
Loads the aggregate through the repository and returns a Result. The Unit of Work saves the change and commits.
internal sealed class RenameProductCommandHandler(IRepository<Product, ProductId> products)
: CommandHandler<RenameProductCommand>
{
public override async Task<Result> Handle(
RenameProductCommand command,
CancellationToken cancellationToken
)
{
var product = await products.FindAsync(new ProductId(command.ProductId), cancellationToken);
return product is null ? ProductErrors.NotFound : product.Rename(command.Name);
}
}The endpoint
Beside them, RenameProductEndpoint maps the HTTP route and sends the command through Catalog's Gateway, with the Operation Context of the caller: Endpoints.
What its tests prove
A few of the behaviours they cover:
- An Operation commits once, or writes nothing.
- A product Catalog creates opens one stock item in Inventory, through an Integration Event.
- Each Module keeps to its own boundaries.
- A misconfigured Host fails when it is built or started.
Testing shows how to write the same kind of test against your own Host.