Skip to content

Commands

Commands represent actions or operations that change the state of the system. They encapsulate the intent to perform a specific task, often containing data necessary for the action, and are (typically) processed by command handlers to ensure business rules and invariants are upheld.

In Protean, command objects are essentially DTOs (Data Transfer Objects) that carry intent and information necessary to perform a specific action.

Key Facts

  • Commands are unique throughout the domain.
  • Commands are typically named using imperative verbs that clearly describe the intended action or change. E.g. CreateOrder, UpdateCustomerAddress, ShipProduct, and CancelReservation.
  • Commands are typically related to an aggregate, because aggregates are the entry point for all modifications, ensuring consistency and enforcing business rules.
  • When commands represent a domain concept that spans across aggregates, one aggregate takes the responsibility of processing the command and raising events to eventually make the rest of the system consistent.

Defining Commands

A command is defined with the Domain.command decorator:

from datetime import datetime, timezone

from protean import Domain
from protean.fields import DateTime, Identifier

publishing = Domain(__file__, "Publishing", load_toml=False)


def utc_now():
    return datetime.now(timezone.utc)


@publishing.command(part_of="Article")
class PublishArticle:
    article_id = Identifier(required=True)
    published_at = DateTime(default=utc_now)


@publishing.aggregate
class Article:
    article_id = Identifier(required=True)
    published_at = DateTime(default=utc_now)

A command is always associated with an aggregate class with the part_of option, as seen in the example above.

Submitting Commands

Since a Protean domain constructs and manages the object graph of all domain elements, you don't need to identify the appropriate Command Handler for your commands.

You can simply submit the command to the domain for processing with domain.process.

In [1]: command = PublishArticle(article_id="1")

In [2]: publishing.process(command)

Workflow

Command objects are often instantiated by the API controller, which acts as the entry point for external requests into the system. When a client makes a request to the API, the controller receives this request and translates the incoming data into the appropriate command object.

In Protean, the API controller submits the command to the domain object, which then dispatches the command to the appropriate command handler. We will explore how the domain identifies the command handler in the Command Handlers section.

sequenceDiagram
  autonumber
  API Controller->>Domain: command object
  Domain-->>API Controller: acknowledge reciept
  Domain->>Command Handler: command object
  Command Handler->>Command Handler: Process command

Immutability

Like Events, Commands in Protean are immutable. This means that once a command is created, it cannot be changed.

In [1]: from datetime import datetime, timedelta

In [2]: publish_article_command = PublishArticle(article_id="1")

In [3]: publish_article_command
Out[3]: <PublishArticle: PublishArticle object ({'article_id': '1', 'published_at': '2024-05-28 17:47:35.570857+00:00'})>

In [4]: publish_article_command.published_at = datetime.now() - timedelta(hours=24)
...
IncorrectUsageError: 'Command Objects are immutable and cannot be modified once created'
}