PouryaKit
Capabilities

Capabilities

PouryaKit lets Feature code hold only business behaviour.

PouryaKit lets Feature code hold only business behaviour. The Feature supplies the aggregate, the command or query, its validator and handler, its data access and its endpoint. The Kit dispatches the Operation, validates it, owns its single commit, runs its reactions and delivers its Integration Events and Snapshots to other Modules.

The Proving Sample, named throughout these files, is the sample application whose tests prove the Kit's guarantees. Its source is not published, but the project templates generate its business code (templates.md), so a name from it here is the same name in a generated solution. The Kit's packages and this skill are MIT-licensed; the Kit's source repository is private.

These hold in every Capability:

  • A handler returns a Result. An expected business failure is an Error in that Result; exceptions are for bugs and infrastructure.
  • The Unit of Work writes the Outbox, saves and commits, and sends what it wrote only after the commit. Feature code returns its Result and leaves all of it to the Unit of Work.
  • Anything outside a Module reaches it through its Gateway, and every Gateway call is one Operation in its own scope, run with the OperationContext the caller passes. There is no anonymous or default Actor.
  • A reaction is one of three kinds, kept distinct: a Domain Event Handler inside the Module and its Unit of Work, a Notification Handler after the commit for anything that reaches outside the Module's data, and an Integration Event Handler in the receiving Module.
  • Modules talk through Domain Events inside a Module, Integration Events and Snapshots across Modules, and a synchronous question through the owning Module's Contracts only when the answer is needed before the Operation can go on.
  • HTTP access is authenticated by default, and a public endpoint says so.
  • A trusted continuation (IsTrustedContinuation) is work an authorized Operation causes, delivered by the Kit or continued with Continue() inside that running Operation. It runs on its recorded authority: a declared permission is not checked again for it, while every authorizer the application registered still runs, and so does the tenant requirement of a tenant-scoped Module (multi-tenancy.md). A context application code builds, or continues outside the Operation that ran it, is never trusted, and CausationId is informational (security.md).
  • A query, an authorizer, a validator and a Snapshot mapper only read: every write the Kit guards is refused there (commands-queries-validation.md).
  • A Module references no other Module's implementation, makes public only its Composition Root, what its Host implements and its Sagas with their timeouts, and never names a transport type, clears Domain Events or writes an aggregate's table with SQL; in a tenant-scoped Module, every SQL read of a tenant-scoped table holds each such table to the tenant at the top level of its WHERE. No application type implements a contract the Kit fills for its pipeline. The Kit's structure tests reject each of these in the Proving Sample, and any save or commit in a Module's code; those tests are not published, so a project writes its own (testing.md).
  • Names follow the Kit's glossary: Module, Feature, Operation, Operation Context, Actor, Domain Event, Notification Handler, Integration Event, Snapshot, and for messages the wire name, the subscription and, on a broker, the service name, Wolverine's name for a Host there.
  • A declaration refuses its own mistakes while the Host is composed. A composition check stops the Host when its container is built, and a startup check when it starts (modules.md).

Start here

Capabilities

Read the reference file of each Capability the code touches before writing it.

CapabilityPackagesRead whenReference
Domain primitivesPouryaKit.DomainWriting an aggregate, entity, typed ID, Domain Event, business rule or Error, or code that needs the timedomain-primitives.md
Commands, queries and validationPouryaKit.Cqrs, PouryaKit.ValidationWriting a command, query, handler, validator or authorizer, sending a nested command, making a command idempotent for a client's request id, declaring a query's paging with allow-listed sort keys and filters, or diagnosing a tracked-state change in code that only readscommands-queries-validation.md
ModulesPouryaKit.Modularity, with Cqrs's Module seam and composition checksWriting a Composition Root without naming its Module or assembly, declaring for a Module from the Host's composition, composing the Host, diagnosing a Host whose build fails on a composition check or whose start fails on a startup check, calling a Gateway, creating or reading an Operation Context, asking another Module a question, or building an application of one Modulemodules.md
Persistence and the Unit of WorkPouryaKit.Persistence, .EFCore, .PostgresMapping an aggregate, using the repository or a specification, removing an aggregate, opting into audit or soft delete, handling a conflict or a unique value, reading a sorted and filtered Page through a repository, numbering with a gap-free number sequence, writing a Domain Event Handler, adding a migration, the idempotency key table's included, or setting how long idempotency keys are keptpersistence.md
Dapper readsPouryaKit.Persistence.DapperWriting a query's read in SQL, reading a sorted and filtered Page in SQL, using typed IDs with Dapper, or writing a Module-internal table with plain SQL in the Unit of Workdapper-reads.md
MessagingPouryaKit.Messaging, .Wolverine, .Wolverine.EFCoreWriting a Notification Handler, telling other Modules what happened with an Integration Event, writing an Integration Event Handler, subscribing a second Module to an Integration Event, adding a new version of an Integration Event or retiring the old one, publishing the Snapshot and Tombstone of an aggregate, writing a Snapshot mapper or choosing its aggregate key, keeping a copy of another Module's data, scheduling a command to run later, declaring recurring work under a named system Actor and its cadence, declaring Notification Handlers, publications, Snapshots and subscriptions in a Composition Root, running deliveries in the Host, renaming or removing a Domain Event, an Integration Event, a Snapshot, a handler or a subscription, or giving one a stable name, adding the Receipt or Snapshot version table, setting the retries or the inbox stale time, or finding and replaying dead lettersmessaging.md
RabbitMQ between HostsPouryaKit.Messaging.Wolverine.RabbitMqDelivering Integration Events and Snapshots to or from another Host over RabbitMQ, declaring what a Host receives from another Host, choosing a Host's service name, knowing what a receiving Host does with a body its contract cannot read, finding dead letters at a Host's broker queue, deploying a Host that receives, or testing two Hosts on one brokerrabbitmq.md
SecurityPouryaKit.Security, .JwtBearerDeclaring the permission a command or query requires, supplying the permissions an Actor holds, deciding what a continuation or a system Actor may run, testing a permission, hashing and verifying a stored password with upgrade detection, or authenticating callers with JWT bearer tokens whose verified claims name the Actor and tenantsecurity.md
Multi-tenancyPouryaKit.Domain, PouryaKit.Cqrs, PouryaKit.Persistence.EFCore, PouryaKit.Messaging.WolverineDeclaring a Module or an application tenant-scoped, marking an aggregate as one tenant's and setting its tenant from the Operation Context, reading only the Operation's tenant's rows or setting that filter aside on purpose, diagnosing a save refused with Tenant.Mismatch or Tenant.NotLoaded, or a delivery dead-lettered with Tenant.Required, keying a copy or a Snapshot's aggregate so tenants never share it, keeping a delivered tenant in a Module that is not tenant-scoped and reading by it, writing a plain SQL read of a tenant-scoped table, or testing with two tenantsmulti-tenancy.md
WebPouryaKit.WebWriting an endpoint, composing the Host's HTTP pipeline, choosing a response's shape, binding page, sort and filter parameters from the query string, making an endpoint public or relying on authenticated access by default, requiring an Idempotency-Key on a repeatable write, reading the caller's claims into the Operation Context, calling another Host with the Operation Context and the standard retry, choosing a client's replay policy, trusting peer Hosts to continue an Operation, beginning the Operation on a route group outside a Module's or carrying the Operation Context's header pairs on another transport, configuring CORS policies, response compression or the rate limit per Actor, or testing over HTTPweb.md
API versions and OpenAPIPouryaKit.Web.OpenApiDeclaring a Module's API versions, choosing the version of a Module that joins a Host, mapping an endpoint to some of them, deprecating a version and configuring its sunset, serving one OpenAPI document per version and the Scalar reference, or checking an operation's security requirement in a documentopenapi.md
gRPCPouryaKit.GrpcWriting a Module's gRPC service from a Protobuf contract in its Contracts, mapping the services in the Host, answering a failed Result as a gRPC status with its Errors in the trailer, calling another Host with a gRPC client that carries the Operation Context and a deadline from options, knowing why a client never calls its own Host, or testing two Hosts over gRPCgrpc.md
TestingPouryaKit.TestingWriting a test against the hosted application, choosing the Actor a test runs as, fixing the time in a test, waiting for and asserting the deliveries an Operation caused, reading and replaying its dead letters, abandoning a Host in a test, or checking the structure rules a Module follows or writing them as architecture tests of your owntesting.md
ObservabilityPouryaKit.ObservabilityObserving the Host's Operations as activities and duration metrics, choosing what a Host's exporter listens to, following one trace from an Operation through the Outbox into the deliveries it causes, in its Host or in another over RabbitMQ, or asserting telemetry in a test with listenersobservability.md
Project templatesPouryaKit.TemplatesStarting a new application with dotnet new as a monolith, a modular monolith or a service of one Module, choosing between them, growing the monolith into a modular monolith, splitting a Module out as a service over RabbitMQ, running, migrating and testing the generated solution, the monolith's plain registration, IDispatcher endpoints and composition and startup checks, upgrading its Kit version, or pre-generating Wolverine's code for productiontemplates.md
CachingPouryaKit.CachingCaching a query's successes per Actor and tenant, declaring the cache tags a command invalidates, sharing cached answers between Hosts through Redis, or testing what a cached query answerscaching.md
File storagePouryaKit.Storage, .S3, .AzureBlobStoring, downloading or deleting a file under a key, giving a time-limited link to it, recording a stored file's key in a command and removing what a failed command left, choosing and configuring the S3, MinIO or Azure Blob provider, or testing against RustFS or Azuritestorage.md
EmailPouryaKit.Email, .MailKitSending email from a Notification Handler, configuring the Host's SMTP server and sender address, knowing what a rolled-back Operation or a failed delivery does to a mail, or testing mail against a Papercut containeremail.md
LocalizationPouryaKit.LocalizationWriting a Module's messages in English and Persian, reading the request culture from the client's Accept-Language header, or testing a localized message over HTTPlocalization.md
Reverse proxyPouryaKit.ReverseProxyPutting a YARP Host of its own process in front of the Hosts, configuring its routes and clusters with service discovery and its liveness probe, keeping one trace through it, knowing why it carries no gRPC and probes no readiness path, or testing a Host behind itreverse-proxy.md
Aspire resourcesPouryaKit.Aspire, AppHost-onlyWriting an Aspire AppHost that runs a Kit Host's PostgreSQL, Redis, RabbitMQ and Ollama on pinned images with named volumes, giving each Host the resources its composition uses under its connection names, passing a Host's bearer settings, running the Reverse proxy in front of the Hosts with a route to a service, serving a Host's gRPC through Aspire's endpoint proxy on an endpoint of its own, knowing what the sample AppHost leaves out and why, or testing the AppHost headlessaspire.md
RealtimePouryaKit.RealtimePushing a realtime event to browsers from a Notification Handler, mapping a Module's SSE endpoint and the rule that admits an Actor to a Channel, knowing what a rollback, a second instance, a lost listener connection or an oversized event does to a push, or testing server-sent events over HTTPrealtime.md
ReportingPouryaKit.ReportingLaying out a Module's report and rendering it to PDF, CSV or a spreadsheet, branding it with a logo and an accent colour, storing the rendered file, knowing which layout the renderer refuses, or testing a rendered file with a standard readerreporting.md
Report ModulePouryaKit.Reporting.ModuleComposing the Kit's report Module in a Host, implementing the ports that authorize a report request and lay out a report kind, requesting a report job over HTTP and reading its state and download link, knowing what a stale claim, a late settlement, another Actor or tenant and the retention do to a job, or testing report jobs against a storage containerreporting-module.md
AIPouryaKit.AI, .PostgresComposing the Host's chat client for OpenAI, Azure OpenAI or Ollama from validated options, giving a run a Module's command or query as a Gateway tool or an MCP server's tools over stdio or HTTP, knowing what authority a tool call runs with and what the allow-list and the iteration cap do, running an agent plain, streamed or for a typed answer, keeping its session and memory in the application's store, written by a command's Unit of Work as the Proving Sample's agent does, bounding its history with a counting or summarizing reducer, composing sequential, concurrent or handoff workflows, checking each call to the model with a guardrail, running agent work on a cadence as a system Actor, embedding text and finding it again by meaning over pgvector within the tenant, keeping the Host's one resilience pipeline on the provider's client, or testing a model's tool calls against a stub serverai.md
StreamsPouryaKit.Persistence.Marten, PouryaKit.Messaging.Wolverine.MartenWriting a Stream, an aggregate whose state is its appended events, loading and starting Streams in a handler, registering a Stream Module and the Host's Marten store beside or without EF Core Modules, knowing what commits together and what a concurrent append, a send that fails after the commit or another tenant does, or testing a Stream Modulestream.md
SagasPouryaKit.Messaging.Wolverine.SagasWriting a Saga that Integration Events start and advance and its timeouts end, declaring it with its system Actor in a Composition Root, emitting from a step, knowing what a duplicate delivery, another tenant's event, a crash or a step's outside effect does to it, hosting one and pre-generating its steps for production, or testing a Sagasagas.md
Core helpersPouryaKit.CoreComposing two predicates into one, finding the concrete types that implement a contract in given assemblies (or every candidate class a registration must validate), or blocking on asynchronous code without the current synchronization contextcore.md

Guides

GuideRead whenReference
Host recipesComposing a Host's service defaults: exporting the Kit's telemetry with OpenTelemetry, liveness and readiness endpoints and why readiness outside Development is the platform's, service discovery, one standard resilience pipeline for every outgoing client and a client that replaces it, logging through Serilog, or testing a Host that uses themhost-recipes.md

On this page