The domain model gives you the nouns and the facts — aggregates, value objects, the events they emit. The application layer gives you the verbs: the operations a caller actually invokes to act on that domain. You have modeled what an Order is; now you model what a caller can do — place one, pay it, list a customer's orders, react when one is placed.
Vishwakarma splits those verbs into four constructs, plus one rule that guards them:
| Construct | What it does | Touches | Example in Chowk |
|---|---|---|---|
command | A write — mutates exactly one aggregate | one aggregate | PlaceOrder, MarkOrderPaid |
query | A read — never mutates | reads one aggregate | GetOrder, ListOrdersByCustomer |
use_case | A saga — orchestrates several writes in one transaction | many aggregates | Checkout |
subscription | An event reaction — runs when a fact is announced | dispatches a command | StartFulfillmentOnOrderPlaced |
policy | An authorization rule — gates who may run an operation | the caller | OperatorOnly |
Writes and reads are separate constructs
The first thing to notice is that a write and a read are different kinds of thing. A command changes state; a query observes it. They are never the same declaration with two modes — they are authored apart, live in commands/ and queries/, and are optimized independently. This is command–query separation made structural: the write surface and the read surface of an aggregate evolve on their own.
That is why the Chowk Order has a commands/order.vishwakarma (place, add a line, mark paid) and a separate queries/order.vishwakarma (get one, list a customer's). The same aggregate, two surfaces, two files.
One aggregate per write — and what to do when you need more
The load-bearing rule of the write side: a command mutates exactly one aggregate. PlaceOrder opens an Order; AddOrderLine creates an OrderLine; ReserveStock adjusts a StockLevel. None of them reaches across aggregate boundaries.
When a single business action genuinely must change several aggregates together — atomically, all-or-nothing — that is not a bigger command. It is a use_case: a small state machine that calls one command per step inside one transaction. Chowk's Checkout places an order and adds its first line as a single unit. The one-aggregate rule stays intact; the saga composes commands that each still obey it.
How the pieces connect
A command can announce a fact by emitting a domain event; a subscription reacts to that fact by dispatching another command — and the two sides never call each other directly. That is the event-driven spine of the whole application:
flowchart LR A[command<br/>PlaceOrder] -->|emits| E((event<br/>OrderPlaced)) E -->|delivered to| S[subscription<br/>StartFulfillmentOnOrderPlaced] S -->|dispatches| C[command<br/>StartFulfillment]
PlaceOrder in the ordering sub-domain knows nothing about fulfillment; it just records that an order was placed. The fulfillment sub-domain listens for that fact and opens a shipment. Decoupling like this is what lets the two teams — and the two sub-domains — move independently.
The pages that follow build this surface one construct at a time: Commands and Queries for the write and read sides, Use cases and sagas for multi-aggregate transactions, Subscriptions for the event reactions, and Policies for the authorization that gates them all.