Operations such as placing an order, cancelling it, and issuing a refund often need the same extra capabilities: audit history, delayed execution, undo/redo, and a clean separation between the code that requests an action and the code that performs it. If those concerns are embedded directly in handlers, the workflow becomes tightly coupled and hard to extend.
The Command pattern wraps each operation in a value that satisfies a small interface. An invoker executes commands, stores history, and coordinates undo/redo. In Go, the pattern works well when you combine interfaces, explicit result values, and ordinary synchronization primitives.
types.go place_order_command.go cancel_order_command.go refund_order_command.go order_command_invoker.go example.go
gofmt ✓
package main
import " time "
// CommandResult represents the result of executing a command
type CommandResult struct {
Success bool `json:"success"`
Message string `json:"message"`
Data interface {} `json:"data,omitempty"`
}
// OrderData represents order information
type OrderData struct {
OrderID string `json:"orderId"`
Amount float64 `json:"amount,omitempty"`
Status string `json:"status"`
Timestamp time . Time `json:"timestamp"`
}
// Command defines the interface for all commands
type Command interface {
Execute () CommandResult
Undo () CommandResult
Name () string
} package main
import " time "
// PlaceOrderCommand represents a command to place an order
type PlaceOrderCommand struct {
orderID string
amount float64
executed bool
}
// NewPlaceOrderCommand creates a new place order command
func NewPlaceOrderCommand ( orderID string , amount float64 ) * PlaceOrderCommand {
return & PlaceOrderCommand {
orderID: orderID,
amount: amount,
}
}
// Execute performs the place order operation
func ( c * PlaceOrderCommand ) Execute () CommandResult {
if c.executed {
return CommandResult {
Success: false ,
Message: "Order " + c.orderID + " has already been placed" ,
}
}
c.executed = true
return CommandResult {
Success: true ,
Message: "Order " + c.orderID + " placed successfully" ,
Data: OrderData {
OrderID: c.orderID,
Amount: c.amount,
Status: "placed" ,
Timestamp: time. Now (),
},
}
}
// Undo reverses the place order operation
func ( c * PlaceOrderCommand ) Undo () CommandResult {
if ! c.executed {
return CommandResult {
Success: false ,
Message: "Order " + c.orderID + " was never placed — cannot undo" ,
}
}
c.executed = false
return CommandResult {
Success: true ,
Message: "Order " + c.orderID + " placement reversed" ,
Data: OrderData {
OrderID: c.orderID,
Status: "reversed" ,
Timestamp: time. Now (),
},
}
}
// Name returns the command name
func ( c * PlaceOrderCommand ) Name () string {
return "PlaceOrder"
} package main
import " time "
// CancelOrderCommand represents a command to cancel an order
type CancelOrderCommand struct {
orderID string
executed bool
}
// NewCancelOrderCommand creates a new cancel order command
func NewCancelOrderCommand ( orderID string ) * CancelOrderCommand {
return & CancelOrderCommand {
orderID: orderID,
}
}
// Execute performs the cancel order operation
func ( c * CancelOrderCommand ) Execute () CommandResult {
if c.executed {
return CommandResult {
Success: false ,
Message: "Order " + c.orderID + " has already been cancelled" ,
}
}
c.executed = true
return CommandResult {
Success: true ,
Message: "Order " + c.orderID + " cancelled successfully" ,
Data: OrderData {
OrderID: c.orderID,
Status: "cancelled" ,
Timestamp: time. Now (),
},
}
}
// Undo reverses the cancel order operation
func ( c * CancelOrderCommand ) Undo () CommandResult {
if ! c.executed {
return CommandResult {
Success: false ,
Message: "Order " + c.orderID + " was never cancelled — cannot undo" ,
}
}
c.executed = false
return CommandResult {
Success: true ,
Message: "Order " + c.orderID + " cancellation reversed" ,
Data: OrderData {
OrderID: c.orderID,
Status: "restored" ,
Timestamp: time. Now (),
},
}
}
// Name returns the command name
func ( c * CancelOrderCommand ) Name () string {
return "CancelOrder"
} package main
import " time "
// RefundOrderCommand represents a command to refund an order
type RefundOrderCommand struct {
orderID string
amount float64
executed bool
}
// NewRefundOrderCommand creates a new refund order command
func NewRefundOrderCommand ( orderID string , amount float64 ) * RefundOrderCommand {
return & RefundOrderCommand {
orderID: orderID,
amount: amount,
}
}
// Execute performs the refund order operation
func ( c * RefundOrderCommand ) Execute () CommandResult {
if c.executed {
return CommandResult {
Success: false ,
Message: "Order " + c.orderID + " has already been refunded" ,
}
}
c.executed = true
return CommandResult {
Success: true ,
Message: "Order " + c.orderID + " refunded successfully" ,
Data: OrderData {
OrderID: c.orderID,
Amount: c.amount,
Status: "refunded" ,
Timestamp: time. Now (),
},
}
}
// Undo reverses the refund order operation
func ( c * RefundOrderCommand ) Undo () CommandResult {
if ! c.executed {
return CommandResult {
Success: false ,
Message: "Order " + c.orderID + " was never refunded — cannot undo" ,
}
}
c.executed = false
return CommandResult {
Success: true ,
Message: "Order " + c.orderID + " refund reversed" ,
Data: OrderData {
OrderID: c.orderID,
Status: "refund_reversed" ,
Timestamp: time. Now (),
},
}
}
// Name returns the command name
func ( c * RefundOrderCommand ) Name () string {
return "RefundOrder"
} package main
import (
" errors "
" sync "
)
// OrderCommandInvoker manages command execution, undo/redo, and history
type OrderCommandInvoker struct {
history [] Command
current int
mu sync . RWMutex
}
// NewOrderCommandInvoker creates a new command invoker
func NewOrderCommandInvoker () * OrderCommandInvoker {
return & OrderCommandInvoker {
history: make ([] Command , 0 ),
current: - 1 ,
}
}
// Execute runs a command and adds it to history
func ( i * OrderCommandInvoker ) Execute ( cmd Command ) ( CommandResult , error ) {
i.mu. Lock ()
defer i.mu. Unlock ()
result := cmd. Execute ()
if ! result.Success {
return result, errors. New (result.Message)
}
// Remove any commands after current position (for redo functionality)
i.history = i.history[:i.current + 1 ]
// Add command to history
i.history = append (i.history, cmd)
i.current ++
return result, nil
}
// Undo reverses the last executed command
func ( i * OrderCommandInvoker ) Undo () ( CommandResult , error ) {
i.mu. Lock ()
defer i.mu. Unlock ()
if i.current < 0 {
return CommandResult {}, errors. New ( "no commands to undo" )
}
cmd := i.history[i.current]
result := cmd. Undo ()
if ! result.Success {
return result, errors. New (result.Message)
}
i.current --
return result, nil
}
// Redo re-executes the next command in history
func ( i * OrderCommandInvoker ) Redo () ( CommandResult , error ) {
i.mu. Lock ()
defer i.mu. Unlock ()
if i.current >= len (i.history) - 1 {
return CommandResult {}, errors. New ( "no commands to redo" )
}
i.current ++
cmd := i.history[i.current]
result := cmd. Execute ()
if ! result.Success {
i.current -- // rollback on failure
return result, errors. New (result.Message)
}
return result, nil
}
// GetHistory returns a list of executed command names
func ( i * OrderCommandInvoker ) GetHistory () [] string {
i.mu. RLock ()
defer i.mu. RUnlock ()
history := make ([] string , 0 , len (i.history))
for j, cmd := range i.history {
status := "pending"
if j <= i.current {
status = "executed"
}
history = append (history, cmd. Name () + " (" + status + ")" )
}
return history
}
// ExecuteAsync runs a command asynchronously
func ( i * OrderCommandInvoker ) ExecuteAsync ( cmd Command , resultChan chan<- CommandResult , errorChan chan<- error ) {
go func () {
result, err := i. Execute (cmd)
if err != nil {
errorChan <- err
return
}
resultChan <- result
}()
} package main
import (
" fmt "
" log "
" time "
)
func main () {
// Create command invoker
invoker := NewOrderCommandInvoker ()
// Create commands
placeCmd := NewPlaceOrderCommand ( "ORD-001" , 99.99 )
cancelCmd := NewCancelOrderCommand ( "ORD-001" )
refundCmd := NewRefundOrderCommand ( "ORD-002" , 49.99 )
fmt. Println ( "=== Executing Commands ===" )
// Execute commands
if result, err := invoker. Execute (placeCmd); err != nil {
log. Printf ( "Error executing place command: %v " , err)
} else {
fmt. Printf ( "Executed: %s - %s\n " , placeCmd. Name (), result.Message)
}
if result, err := invoker. Execute (cancelCmd); err != nil {
log. Printf ( "Error executing cancel command: %v " , err)
} else {
fmt. Printf ( "Executed: %s - %s\n " , cancelCmd. Name (), result.Message)
}
if result, err := invoker. Execute (refundCmd); err != nil {
log. Printf ( "Error executing refund command: %v " , err)
} else {
fmt. Printf ( "Executed: %s - %s\n " , refundCmd. Name (), result.Message)
}
fmt. Printf ( " \n History: %v\n " , invoker. GetHistory ())
fmt. Println ( " \n === Undoing Commands ===" )
// Undo operations
if result, err := invoker. Undo (); err != nil {
log. Printf ( "Error undoing: %v " , err)
} else {
fmt. Printf ( "Undid: %s\n " , result.Message)
}
if result, err := invoker. Undo (); err != nil {
log. Printf ( "Error undoing: %v " , err)
} else {
fmt. Printf ( "Undid: %s\n " , result.Message)
}
fmt. Printf ( "History after undo: %v\n " , invoker. GetHistory ())
fmt. Println ( " \n === Redoing Commands ===" )
// Redo operations
if result, err := invoker. Redo (); err != nil {
log. Printf ( "Error redoing: %v " , err)
} else {
fmt. Printf ( "Redid: %s\n " , result.Message)
}
fmt. Printf ( "Final History: %v\n " , invoker. GetHistory ())
fmt. Println ( " \n === Async Execution Example ===" )
// Demonstrate async execution
asyncCmd := NewPlaceOrderCommand ( "ORD-003" , 29.99 )
resultChan := make ( chan CommandResult , 1 )
errorChan := make ( chan error , 1 )
invoker. ExecuteAsync (asyncCmd, resultChan, errorChan)
select {
case result := <- resultChan:
fmt. Printf ( "Async result: %s - %s\n " , asyncCmd. Name (), result.Message)
case err := <- errorChan:
log. Printf ( "Async error: %v " , err)
case <- time. After ( 2 * time.Second):
fmt. Println ( "Async execution timed out" )
}
}
Think of a restaurant ticket. A server writes down a request as a discrete order, the kitchen executes it later, and the restaurant can track, retry, or cancel that ticket without the customer needing to know the cooking details.
| Pros | Cons |
| --- | --- |
| Treats operations as values that can be queued, retried, or stored. | Adds boilerplate types for each operation. |
| Separates the requester from the executor cleanly. | Undo logic can be hard or impossible for some commands. |
| Supports history, undo, and audit-friendly workflows. | May be unnecessary overhead for simple direct service calls. |