The Problem
Order placement often touches multiple services such as inventory, billing, and shipping. If every handler coordinates those services directly, the workflow gets repeated in many places and callers become coupled to low-level sequencing details.
The Solution
Facade introduces a higher-level API over a cluster of subsystems. In Go, it is usually a simple service struct with a method that orchestrates the underlying collaborators. The facade does not hide the subsystems from existence; it just gives common workflows a more coherent entry point.
Structure
The Subsystems
Each subsystem exposes low-level operations: Reserve(sku, qty), Charge(card, amount), Schedule(address, items). Each is a standalone service with its own errors and domain logic.
flowchart LR Client["Client"] Facade["OrderFacade"] Inventory["InventoryService"] Billing["BillingService"] Shipping["ShippingService"] Client -->|"PlaceOrder()"| Facade Facade -->|"Reserve()"| Inventory Facade -->|"Charge()"| Billing Facade -->|"Schedule()"| Shipping
- Subsystems: Inventory, billing, and shipping services expose low-level operations.
- Facade:
OrderFacadecoordinates the workflow in one place. - Client: Calls the simplified facade method instead of orchestrating the subsystems manually.
Implementation
This example wraps reservation, charging, and shipment scheduling behind a single PlaceOrder method. The handler-level caller only cares about the outcome of the business workflow.
package main
type InventoryService struct {
stock map[string]int
}
func NewInventoryService() InventoryService {
return InventoryService{stock: map[string]int{"KB-001": 12}}
}
func (s *InventoryService) Reserve(sku string, qty int) bool {
available := s.stock[sku]
if available < qty {
return false
}
s.stock[sku] = available - qty
return true
} Real-World Analogy
Think of a hotel concierge. Instead of calling housekeeping, transport, dining, and ticketing yourself, you make one request to the concierge and they coordinate the underlying services for you.
Pros and Cons
| Pros | Cons |
|---|---|
| Gives callers a simpler entry point for a common workflow. | Can turn into a god object if every workflow gets stuffed into it. |
| Removes repetitive orchestration from many call sites. | May hide lower-level capabilities that some advanced callers still need. |
| Concentrates sequencing logic in one place. | Adds another layer that must stay aligned with changing subsystems. |
Best Practices
- Keep the facade aligned with business workflows instead of turning it into a generic god object.
- Expose lower-level services directly where advanced use cases need them. A facade should simplify, not imprison.
- Inject the subsystems so orchestration can be tested without real infrastructure.
- Use a facade to remove repeated sequencing logic, not to hide every dependency automatically.
- Model failures explicitly because orchestration code is where partial-success bugs tend to surface.
When to Use
- A workflow repeatedly coordinates several subsystems in the same order.
- Callers should depend on one business-oriented entry point instead of many low-level services.
- You want to make common use cases obvious without deleting the lower-level APIs.
When NOT to Use
- There is no stable workflow to simplify.
- The facade would just forward one method to one dependency without adding clarity.
- You are using it to hide poor subsystem boundaries rather than fixing them.