Skip to content

Command

In the CQRS pattern, a Command is a message that represents an intention to change the state of the system. A command is sent to the system and is handled by a CommandHandler. The command handler is responsible for validating the command and emitting events as a result of the command being executed.

Command interface

In comby an Command is defined as an interface as follows:

go
type Command interface {
	// GetInstanceId retrieves the unique instance ID (default 1) associated with
	// running application instance in a distributed environment.
	GetInstanceId() int64

	// SetInstanceId sets the unique instance ID of the running application for the command.
	// In distributed systems with multiple write instances, system clocks
	// may not always be synchronized. To minimize dependency on system time,
	// the InstanceId is used. This ID helps distinguish between different
	// write instances, allowing read models to account for this information
	// when processing events.
	SetInstanceId(instId int64) error

	// GetCommandUuid retrieves the unique identifier for the command.
	GetCommandUuid() string

	// SetCommandUuid sets the unique identifier for the command.
	SetCommandUuid(commandUuid string) error

	// GetTenantUuid retrieves the tenant's unique identifier associated with the command.
	GetTenantUuid() string

	// SetTenantUuid sets the tenant's unique identifier for the command.
	SetTenantUuid(tenantUuid string) error

	// GetDomain retrieves the domain to which the command belongs.
	GetDomain() string

	// SetDomain sets the domain for the command.
	SetDomain(domain string) error

	// GetDomainCmdName retrieves the name of the domain-specific command.
	GetDomainCmdName() string

	// SetDomainCmdName sets the name of the domain-specific command.
	SetDomainCmdName(domainCmdName string) error

	// GetDomainCmdBytes retrieves the underlying domain-specific command as a byte slice.
	GetDomainCmdBytes() []byte

	// SetDomainCmdBytes sets byte slice representation of the underlying DomainCmd.
	SetDomainCmdBytes(data []byte) error

	// GetDomainCmd retrieves the underlying domain-specific command in its runtime representation.
	GetDomainCmd() DomainCmd

	// SetDomainCmd sets the underlying domain-specific command in its runtime representation.
	SetDomainCmd(domainCmd DomainCmd) error

	// GetCreatedAt retrieves the timestamp when the command was created.
	GetCreatedAt() int64

	// SetCreatedAt sets the creation timestamp for the command.
	SetCreatedAt(createdAt int64) error

	// GetReqCtx retrieves the request context associated with the command.
	GetReqCtx() *RequestContext

	// SetReqCtx sets the request context for the command.
	SetReqCtx(reqCtx *RequestContext) error
}

The Command interface defines the contract for handling commands in a distributed, event-sourced system. It includes methods for managing a command's metadata, such as instance identifiers, tenant and domain details, versioning, and creation timestamps, ensuring that commands are uniquely identifiable and context-aware.

The GetInstanceId and SetInstanceId methods manage the unique instance ID of the running application in a distributed environment. This ID, distinct from system clocks, helps disambiguate write instances and enables accurate processing of events by readmodels. Similarly, the GetCommandUuid and SetCommandUuid methods handle the unique identifier for the command itself, while GetTenantUuid and SetTenantUuid manage the tenant’s unique identifier associated with the command.

Domain-related methods include GetDomain and SetDomain for retrieving and assigning the domain to which the command belongs, as well as GetDomainCmdName and SetDomainCmdName for specifying the name of the domain-specific command. The command's data can be managed through GetDomainCmdBytes and SetDomainCmdBytes, which handle the command as a byte slice, and GetDomainCmd and SetDomainCmd, which deal with its runtime representation.

Additionally, the interface includes GetCreatedAt and SetCreatedAt methods for managing the command's creation timestamp. Context-related information is addressed through GetReqCtx and SetReqCtx, which retrieve and set the request context associated with the command.

Base Command

The BaseCommand struct provides a foundational implementation of the Command interface, offering default values for its fields and ensuring consistent metadata management across commands. The structure of BaseCommand is defined as follows:

go
type DomainCmd any

type BaseCommand struct {
	// Unique application instance ID (default: 1)
	InstanceId int64 `json:"instanceId,omitempty"`

	// CommandUuid is the unique identifier for the command.
	CommandUuid string `json:"commandUuid,omitempty"`

	// TenantUuid is the unique identifier of the tenant associated with the command.
	TenantUuid string `json:"tenantUuid,omitempty"`

	// Domain specifies the domain to which the command belongs.
	Domain string `json:"domain,omitempty"`

	// Domain command specific name (type) of the command.
	DomainCmdName string `json:"domainCmdName,omitempty"`

	// DomainCmdBytes contains the byte array representation of the domain-specific command data.
	DomainCmdBytes []byte `json:"domainCmdBytes,omitempty"`

	// DomainCmd holds the runtime representation of the domain-specific command data.
	DomainCmd DomainCmd `json:"-"`

	// CreatedAt is the timestamp when the command was created.
	CreatedAt int64 `json:"createdAt,omitempty"`

	// ReqCtx contains the request context information for the command.
	ReqCtx *RequestContext `json:"reqCtx,omitempty"`
}

The BaseCommand is a foundational implementation of the Command interface in comby, providing a standardized structure for managing commands in an event-sourced, distributed environment. It encapsulates key metadata, domain-specific information, and execution context to ensure commands are uniquely identifiable, correctly routed, and executed with consistency.

At its core, BaseCommand includes fields such as InstanceId, a unique identifier for the originating application instance, and CommandUuid, a globally unique identifier for the command itself. These identifiers are crucial for maintaining command traceability, especially in distributed systems with multiple write instances.

The TenantUuid field associates the command with a specific tenant, while Domain and DomainCmdName provide the domain context and type of the command. Domain-specific data can be stored and retrieved in both serialized (DomainCmdBytes) and runtime (DomainCmd) representations, ensuring flexibility and compatibility with diverse processing requirements.

The CreatedAt timestamp records when the command was created, enabling temporal analysis and debugging.

The ReqCtx field integrates a RequestContext object, which carries additional metadata and execution parameters, such as trace IDs, sender information, and execution flags. This ensures that commands are executed within the correct contextual framework, accounting for specific tenant or aggregate targets and any execution constraints.

The BaseCommand is instantiated using the NewBaseCommand method, which sets default values, including generating a unique CommandUuid and initializing the RequestContext. For domain-specific commands, the NewCommand method simplifies creation by serializing the provided DomainCmd and associating it with the new BaseCommand.

Usage

The NewCommand function in comby simplifies the creation of domain-specific commands, allowing developers to seamlessly integrate them into the framework.

For example, consider a domain command MarkOrderPaid designed to handle the process of marking an order as paid. This command includes fields such as OrderUuid and PaymentUuid, which represent the identifiers for the order and its associated payment, respectively.

To create a new command for this domain operation, use the following approach:

go
// domain command
type MarkOrderPaid struct {
	OrderUuid   string `json:"orderUuid"`
	PaymentUuid string `json:"paymentUuid"`
}

// create new command
cmd, _ := comby.NewCommand("Order", &MarkOrderPaid{
	OrderUuid:   "KnownOrderUuid",
	PaymentUuid: "KnownPaymentUuid",
})

// dispatch to facade ...

Here, the NewCommand function is used to initialize a new command for the Order domain, encapsulating the MarkOrderPaid domain event. The framework automatically serializes the domain-specific data and associates it with the command's metadata, such as its Domain and DomainCmdName.

Once the command is created, it can be dispatched to the Facade for processing, where it will trigger the corresponding CommandHandler.