Reference
Command
Reference for go-sequent's Command interface and Meta: command types, aggregate ids, tenant and idempotency fields, audit snapshots with AuditOf, and Validate hooks.
Command
Package: command
A command is an intent to change the system. It has a type name, an optional primary aggregate id, and metadata. Commands enter through CommandService.Execute, where middleware, AuthZ and optional validation run before a handler sees them.
The interface
type Command interface {
CommandType() string
AggregateID() (uuid.UUID, bool)
Meta() *Meta
}
CommandType()returns a stable name such asbank.Deposit. The router and AuthZ allow-lists use it.AggregateID()returns the primary aggregate and whether it is set. It is stored with the command record.Meta()returns the command’s metadata.
Embed command.Base to get Meta():
type Deposit struct {
command.Base
AccountID uuid.UUID `json:"account_id"`
Amount types.Money `json:"amount"`
}
func (c *Deposit) CommandType() string { return "bank.Deposit" }
func (c *Deposit) AggregateID() (uuid.UUID, bool) {
return c.AccountID, c.AccountID != uuid.Nil
}
Base holds a Meta value in field M. Base.Meta() calls EnsureMeta, which fills defaults on first use.
Meta
| Field | Role |
|---|---|
CommandID |
Unique id of this command. Used for audit and as the foreign key target for idempotency rows. |
IdempotencyKey |
Optional. Per-tenant idempotency key. Requires TenantID. |
TenantID |
Tenant isolation unit. Usually copied from the context by tenancy middleware. |
UserID |
Actor issuing the command. |
CorrelationID |
Groups a business conversation. |
CausationID |
Links to the command or event that caused this one. |
CausedByAggregate, CausedBySequence |
Set when a workflow triggers the command. |
IssuedAt |
Time the command was issued. |
Source |
Audit label such as api, workflow, admin. |
AuthContext |
Opaque claims. Not necessarily persisted. |
EnsureMeta(m) fills CommandID with a new UUID when empty, IssuedAt with the current UTC time, and CorrelationID with CommandID.
Do not trust a client-supplied UserID on its own. Authentication belongs to your host application. Put verified claims in AuthContext and check them in an Authorizer.
Validation
Implement Validate() error to check input shape before the handler runs:
func (c *Deposit) Validate() error {
if c.AccountID == uuid.Nil {
return fmt.Errorf("account_id required")
}
if c.Amount.IsZero() || c.Amount.IsNegative() {
return fmt.Errorf("amount must be positive")
}
return nil
}
CommandService detects the command.Validator interface and calls it after filters and before dispatch. Rules that need aggregate state belong in aggregate methods, not here.
Audit snapshot
command.AuditOf(cmd) returns an AuditSnapshot suitable for persistence:
audit := command.AuditOf(cmd)
// audit.CommandID, CommandType, TenantID, UserID,
// IdempotencyKey, CorrelationID, CausationID, Source, IssuedAt
On a successful command the store also writes a command record (commands table) through command.RecordOf. It carries the id, type, aggregate id, tenant, user, idempotency key, issue time, and the command payload as JSON.
Money in commands
Use types.Money for any amount field. testkit.BanFloat64Money(cmd) and types.AssertNoFloatMoney(cmd) fail when a command or event has a float64 money field. Run one in a test or at startup. See Money.
Multiple commands in one call
err := svc.Execute(ctx, cmd1, cmd2) // one transaction, one UnitOfWork
Commands run in order. Workflows may enqueue more commands with command.Enqueue; they run in the same transaction after the ones already queued. See Workflow.
Related
Docs track the go-sequent library. When APIs drift, the source wins.