Commands
In most applications, the code that requests a change (an API controller, a background job, a saga) is not the code that performs the change (the domain model). A command bridges that gap: it captures the caller's intent as an explicit, immutable data object that can be validated, serialized, routed, retried, and audited — all before any business logic runs.
Commands and events are complementary halves of a message-driven architecture. They differ in tense and semantics:
| Command | Event | |
|---|---|---|
| Tense | Imperative — do this | Past tense — this happened |
| Meaning | An intent that may be rejected | A fact that has already occurred |
| Naming | PlaceOrder, CancelReservation |
OrderPlaced, ReservationCancelled |
| Consumers | Exactly one command handler | Zero or more event handlers |
| Outcome | May succeed or fail | Always represents committed state |
Facts
Commands express intentions.
Commands are not queries; they do not request data but rather express an intention to perform an action that changes the state of the system.
Commands are essentially Data Transfer Objects (DTO).
They can only hold simple fields and Value Objects.
Commands are immutable.
Commands should be designed to be immutable once created. This ensures that the intention they represent cannot be altered after they are sent.
Commands trigger domain logic.
Commands are processed by command handlers, which interact with the domain model to execute the intended action.
Commands are named with verbs.
Commands should be named clearly and concisely, typically using verbs to
indicate the action to be performed, such as CreateOrder,
UpdateCustomerInfo, or CancelReservation. These terms should match with
concepts in Ubiquitous Language.
Structure
Commands have metadata.
Headers and metadata such as timestamps, unique identifiers, and version numbers are included in commands for precise tracking of origin and intent.
Commands are versioned.
Each command is assigned a version number (defaulting to 1), ensuring that
commands can evolve over time. Set __version__ on the class to override:
@domain.command(part_of=Order)
class PlaceOrder:
__version__ = 2
...
Since commands are handled by a single aggregate through a command handler, there is seldom a need to support multiple versions of commands at the same time.
Commands are timestamped.
Each command carries a timestamp indicating when the command was initiated, which is crucial for processing incoming commands chronologically.
Commands are written into streams.
Commands are written to and read from streams. Review the section on Streams for a deep-dive.
Command objects are always valid.
Like other elements in Protean, commands are validated as soon as they are initialized to ensure they contain all required information and that the data is in the correct format.
Persistence
Commands do not persist data directly.
Commands themselves do not persist data; they trigger domain operations that result in changes to the state of aggregates, which are then persisted by repositories.
Commands can result in events.
Once a command has been successfully handled, it may result in domain events being published. These events can then be used to notify other parts of the system about the changes.
Best Practices
Keep commands simple.
Commands should be simple and focused on a single responsibility. This makes them easier to understand and maintain.
Use a consistent naming convention.
Maintain a consistent naming convention for commands to ensure clarity and uniformity across the system.
Ensure idempotency.
Command handling should be idempotent, meaning that handling the same command multiple times should result in the same state without unintended side effects.
Secure sensitive data.
Be mindful of sensitive data within commands, especially when they are transmitted over a network. Ensure that appropriate security measures are in place to protect this data.
Next steps
For practical details on defining and processing commands in Protean, see the guide:
- Commands — Defining commands, submitting them, idempotency, and processing modes.
For design guidance:
- Command Idempotency — Ensuring commands can be safely retried without side effects.