Commands
Opening an account or adding a cart line needs input, a handler, and a result the caller can check. An Arc command keeps the input with the behavior that handles it. Arc supplies the HTTP endpoint, command result, and generated TypeScript proxy, so you do not repeat that plumbing for each operation.
Start with a handler that calls your application service. Arc uses CQRS (Command Query Responsibility Segregation): commands change state, while queries read it. Your service can use any suitable storage; a command does not have to return an event or use Chronicle.
Your first command
Section titled “Your first command”This is a declaration fragment using the shared CartLineId, Sku, and Quantity concepts and fully implemented ICartService from the model-bound command example:
using Cratis.Arc.Commands.ModelBound;
[Command]public record AddItemToCart(Sku Sku, Quantity Quantity){ public CartLineId Handle(ICartService carts) => carts.AddItem(Sku, Quantity);}Arc discovers the public instance Handle() on the [Command] record. It resolves ICartService from the command scope, waits for the operation, and places the CartLineId in the response. The service owns the cart write; declaring the command does not supply storage. Follow the linked example to implement and register that service, then execute the command before adding pipeline extensions.
Choose the return shape by what the caller needs. Returning a value reports an outcome; it does not by itself make a service write transactional:
voidorTask— no response value. A task is awaited; this is not fire-and-forget execution.- A value or
Task<T>— an unhandled value becomes the typed response. - An operation or
CommandOperations— declare one or many server-side actions, with optional compensation. - A tuple — combine one response with command operations and other values consumed by response value handlers.
Cratis.Monads.Result<TSuccess, TError>— process the active alternative. A recognized validation value becomes a rejection; arbitrary error types are not automatically failures.
Use provided data when fetching handler input separately makes the operation clearer.
Two ways to define a command
Section titled “Two ways to define a command”| Style | Choose it when |
|---|---|
| Model-bound | You want input and behavior together with minimal endpoint boilerplate. |
| Controller-based | You need MVC action control or are integrating existing controllers. |
The model-bound pipeline and MVC action filters are different execution paths. Do not assume extensions or authorization attributes for one automatically govern the other.
Build on the basics
Section titled “Build on the basics”Add validation when input needs rules, and authorization when the operation needs access checks. Pre-flight validation can give a form feedback before submission, but the caller must still check the execution result.
| Next concern | Read |
|---|---|
| Validate input | Validation |
| Protect commands | Model-bound authorization |
| Check without invoking the handler | Pre-flight validation |
| Execute from application code | Command pipeline |
| Keep inline side effects out of the decision | Command operations · Testing operations |
| Understand scalar and tuple responses | Response value handlers · Examples |
| Carry cross-cutting data | Command context |
| Extend execution | Filters · Execution scopes |
Optional Chronicle integration
Section titled “Optional Chronicle integration”If your application uses event sourcing, Chronicle command integration adds returned-event handling. Its transactional command behavior coordinates that integration’s work; it does not turn arbitrary service writes into a transaction. You do not need Chronicle for service-backed commands or Guid responses.
The frontend follows the backend
Section titled “The frontend follows the backend”The proxy generator generates the TypeScript command contract from the backend. After building the backend, use commands in React to submit it and inspect the result. Continue to queries to read the data your commands change.