Usecase-Centric Design
Many systems claim to be “layered”.
But when you look closer, they are often organized around:
- Controllers
- Services
- Repositories
- Framework abstractions
What is missing is the actual reason the system exists.
Plumego promotes a different organizing principle:
The system should be structured around use cases — the things the system does.
This document explains what usecase-centric design means in practice,
and why it dramatically improves clarity, evolution, and team alignment.
What Is a Usecase?
A usecase represents an intentional action the system can perform.
Examples:
- Create an order
- Cancel a subscription
- Reset a password
- Publish an event
- Generate a report
A usecase answers:
- Who can do this?
- What happens when they do?
- Which rules apply?
- What can go wrong?
It is not a technical concept.
It is a behavioral contract.
The Problem with Layer-Centric Organization
A common project structure looks like this:
handlers/
services/
repositories/
models/
This structure answers:
- “Where is the HTTP code?”
- “Where is the database code?”
But it does not answer:
- “Where is the logic for cancelling an order?”
- “What happens when a user upgrades a plan?”
Behavior becomes scattered across layers and files.
Understanding a single business action requires jumping across the codebase.
Usecases as the System’s Backbone
In a usecase-centric design:
- Each usecase is explicit
- Each usecase has a clear entry point
- Each usecase owns its flow
- Each usecase coordinates domain logic
The system becomes a collection of named behaviors, not technical fragments.
A Typical Usecase Shape
A usecase usually consists of:
- Input (explicit data)
- Dependencies (interfaces)
- Execution logic
- Returned result or error
Example (conceptual):
type CancelOrderUsecase struct {
repo OrderRepository
}
func (u *CancelOrderUsecase) Execute(input Input) error {
order, err := u.repo.FindByID(input.OrderID)
if err != nil {
return err
}
return order.Cancel()
}
This code tells a complete story.
Usecase vs Domain Logic
A crucial distinction:
- Domain defines what is always true
- Usecase defines what happens in a specific scenario
The domain does not know why an operation is triggered.
The usecase does.
Usecases orchestrate domain behavior — they do not replace it.
Usecase-Centric Package Organization
A common Plumego-friendly structure:
internal/
order/
cancel/
usecase.go
input.go
errors.go
create/
usecase.go
input.go
Benefits:
- All logic for a behavior lives together
- Easy to navigate
- Easy to review
- Easy to evolve
This structure scales better than layer-based directories.
Handlers as Usecase Adapters
In a usecase-centric system:
- Handlers adapt HTTP → usecase input
- They do not contain business decisions
- They delegate immediately
Example:
func CancelOrderHandler(ctx *plumego.Context) {
input := parseInput(ctx)
err := cancelOrderUsecase.Execute(input)
handleResult(ctx, err)
}
Handlers become thin translators.
Repositories Serve Usecases, Not the Other Way Around
Repositories exist to support usecases.
They are not generic data access layers.
This implies:
- Interfaces are defined by usecases
- Repositories are injected
- Different usecases may require different interfaces
Avoid “one repository to rule them all”.
Usecases and Authorization
Authorization decisions are often usecase-specific.
Example:
- Who can cancel an order?
- Under what conditions?
Embedding authorization in usecases makes these rules explicit and testable.
Usecases and Naming Discipline
Good usecase names are:
- Verbs
- Explicit
- Domain-aligned
Prefer:
CreateOrderCancelSubscription
Avoid:
OrderServiceHandleOrder
Names communicate intent.
Usecase-Centric Testing
Usecases are ideal test units:
- Dependencies can be mocked
- Behavior is explicit
- Error paths are clear
Most meaningful business tests live at the usecase level.
When Usecase-Centric Design Matters Most
This pattern is especially valuable when:
- The system has non-trivial business rules
- Multiple engineers collaborate
- Features evolve over time
- The codebase is expected to live for years
For trivial CRUD apps, it may feel heavy — and that is acceptable.
Common Anti-Patterns
God Services
Large “service” structs handling many unrelated behaviors.
Generic Repositories
Interfaces that expose too much and encourage misuse.
Usecases Hidden in Handlers
Business logic that never leaves the HTTP layer.
Summary
In Plumego:
- Usecases are first-class citizens
- System structure reflects behavior, not technology
- Handlers adapt, usecases decide, domains enforce
- Code tells stories, not just mechanics
A usecase-centric design makes systems easier to understand —
especially months or years after they are written.
Next
With usecases at the center, the final structural pattern is:
→ Dependency Wiring
This shows how to assemble all usecases, adapters, and infrastructure explicitly into a running system.