Domain and Usecase
In long-lived systems, most architectural decay happens inside the core.
Not in frameworks.
Not in infrastructure.
But in the blurred boundary between what the system is and what the system does.
Plumego strongly encourages a clear separation between:
- Domain — what the system is
- Usecase (Application) — what the system does
This document explains how to structure that separation in practice.
Domain: What the System Is
The domain layer represents the stable core of the system.
It models:
- Core business concepts
- Invariants and rules
- Behaviors that define correctness
Typical domain elements
- Entities
- Value objects
- Domain services
- Domain errors
Example (conceptual):
type Order struct {
ID OrderID
Items []Item
Status Status
}
func (o *Order) Cancel() error {
if o.Status == Shipped {
return ErrCannotCancel
}
o.Status = Cancelled
return nil
}
The domain expresses rules, not workflows.
Domain Characteristics
A well-designed domain layer is:
- Stable over time
- Independent of transport
- Independent of frameworks
- Independent of infrastructure
The domain must not know:
- How a request arrived
- Who initiated the action
- Which database is used
- Which framework is running
If it does, it is no longer a domain.
Usecase: What the System Does
The usecase (application) layer represents behavior.
It answers questions like:
- What happens when a user places an order?
- How do multiple domain objects interact?
- Which operations are allowed in which scenarios?
Usecases orchestrate domain objects to achieve a goal.
Example (conceptual):
type PlaceOrderUsecase struct {
repo OrderRepository
}
func (u *PlaceOrderUsecase) Execute(input PlaceOrderInput) error {
order := NewOrder(input.Items)
if err := u.repo.Save(order); err != nil {
return err
}
return nil
}
The usecase defines intent and flow, not core rules.
Key Differences at a Glance
| Aspect | Domain | Usecase |
|---|---|---|
| Purpose | Define correctness | Coordinate behavior |
| Stability | Very high | Medium |
| Knows about workflows | No | Yes |
| Knows about persistence | No | Via interfaces |
| Knows about HTTP / Plumego | Never | Never |
Both layers are framework-agnostic.
Dependency Direction
The dependency rule is strict:
Usecase → Domain
- Domain must not depend on Usecase
- Usecase may depend on Domain
- Neither depends on Plumego or HTTP
Infrastructure implements interfaces defined by Usecase or Domain.
Where Interfaces Belong
Interfaces should be owned by the layer that depends on them.
Example:
// In usecase layer
type OrderRepository interface {
Save(*Order) error
}
Infrastructure implements this interface:
// In infra/mysql
type OrderRepository struct {}
func (r *OrderRepository) Save(o *Order) error {
// persist to DB
}
This keeps control in the core, not the edges.
Usecase Inputs and Outputs
Usecases should define explicit input and output types.
Avoid passing:
- HTTP-specific types
- Context objects
- Framework structs
Prefer simple, explicit data:
type PlaceOrderInput struct {
UserID string
Items []ItemInput
}
This makes usecases:
- Easy to test
- Reusable across transports
- Clear in intent
Error Responsibility
- Domain errors represent rule violations
- Usecase errors represent failure to complete an operation
Neither layer decides:
- HTTP status codes
- Response formats
- Error serialization
Those decisions belong to handlers.
Common Anti-Patterns
Fat Usecases
Usecases that contain core business rules usually indicate:
- Domain logic was never extracted
- Rules are duplicated
- Stability is compromised
If a rule must be correct everywhere, it belongs in the domain.
Anemic Domain
A domain that only contains data structures, with all logic in usecases, is fragile.
This often leads to:
- Duplicated rules
- Inconsistent behavior
- Hard-to-maintain systems
The domain should do things, not just hold data.
Infrastructure-Driven Domain
If database schemas define your domain model, boundaries are reversed.
Domain should shape persistence — not the other way around.
Testing Implications
With proper separation:
- Domain can be tested without mocks
- Usecases can be tested with stubbed interfaces
- Handlers can be tested independently
This layered testability is one of the main benefits of Plumego’s architecture guidance.
Why Plumego Emphasizes This Separation
Plumego deliberately avoids:
- Auto-wiring
- Active Record patterns
- Framework-driven models
Because these tend to blur domain and application concerns.
Plumego’s thin framework edge makes it natural — and necessary —
to define domain and usecase layers explicitly.
Summary
In a Plumego system:
- Domain defines what must always be true
- Usecase defines what the system does
- Dependencies point inward
- Frameworks stay at the edge
- Infrastructure remains replaceable
This separation is the foundation of long-term maintainability.
Next
With domain and usecase clarified, the next architectural topic is:
This explains how to keep dependency flow correct as systems grow.