Dependency Direction
Layering only works if dependency direction is enforced.
You can have perfect folder names and clean diagrams,
but if dependencies point the wrong way, the architecture will still collapse over time.
This document explains how Plumego expects dependency direction to work,
and how to keep it correct as systems grow.
The One Rule That Matters
Everything in this document can be reduced to a single rule:
Dependencies must always point inward.
“Inward” means toward:
- More stable code
- More fundamental business rules
- Less volatile abstractions
“Inward” never means toward frameworks, infrastructure, or delivery mechanisms.
Stability as the Compass
To understand dependency direction, forget layers for a moment.
Instead, think in terms of stability.
- Code that changes often is unstable
- Code that rarely changes is stable
Correct dependency direction always points:
Unstable → Stable
Never the other way around.
Mapping Stability to Layers
In a typical Plumego system:
| Layer | Stability |
|---|---|
| HTTP / Plumego | Low |
| Infrastructure | Low |
| Application (Usecase) | Medium |
| Domain | High |
Therefore, valid dependency directions are:
- HTTP → Application
- Application → Domain
- Infrastructure → Application / Domain
Invalid directions include:
- Domain → HTTP
- Domain → Infrastructure
- Application → HTTP
If you see these, architecture is already compromised.
Dependency Direction vs Call Direction
A common source of confusion:
“But the handler calls the usecase, so isn’t that a dependency?”
Call direction and dependency direction are not the same.
- Call direction: who calls whom at runtime
- Dependency direction: who imports whom at compile time
Example:
// handler package
import "app/usecase"
func Handle(ctx *Context) {
usecase.Execute()
}
- Call direction: Handler → Usecase
- Dependency direction: Handler → Usecase
This is correct, because Handler is more unstable.
The reverse must never happen.
Dependency Inversion in Practice
Often, inner layers need to use functionality provided by outer layers
(e.g. persistence, messaging, external APIs).
This is solved via dependency inversion.
Step 1: Define an interface inward
// in application layer
type UserRepository interface {
FindByID(id string) (*User, error)
}
Step 2: Implement it outward
// in infrastructure layer
type MySQLUserRepository struct {}
func (r *MySQLUserRepository) FindByID(id string) (*User, error) {
// database logic
}
Step 3: Inject inward
usecase := NewUserUsecase(repo)
The dependency arrow remains correct.
Where Interfaces Should Live
A critical rule:
Interfaces belong to the layer that depends on them.
Not where they are implemented.
This ensures:
- Inner layers control their needs
- Outer layers remain replaceable
- Dependencies remain inverted
Violating this rule is one of the fastest ways to lose architectural control.
Using Go Packages to Enforce Direction
Go’s package system is a powerful enforcement tool.
Recommended techniques:
Use internal/
- Prevents imports from unintended places
- Enforces module-level boundaries
Avoid circular dependencies
Circular imports are often a sign of:
- Poor boundary definition
- Mixed responsibilities
- Hidden coupling
Resolve them by moving abstractions inward.
Common Dependency Direction Violations
Domain importing infrastructure
import "infra/mysql"
This couples core rules to volatile details.
Usecase returning HTTP-specific results
return http.StatusNotFound
This leaks transport concerns inward.
Infrastructure driving domain design
When database schemas dictate domain models,
dependencies are flowing in the wrong direction conceptually — even if imports look correct.
Dependency Direction as a Review Criterion
Dependency direction should be part of every code review.
Questions to ask:
- Is this import necessary?
- Does this layer really depend on that one?
- Who owns this abstraction?
Treat dependency violations as architectural bugs, not stylistic issues.
How Plumego Helps (and Where It Stops)
Plumego helps by:
- Keeping the framework surface small
- Avoiding implicit coupling mechanisms
- Encouraging explicit boundaries
But Plumego does not enforce dependency direction automatically.
It provides the environment — discipline must come from the team.
Long-Term Consequences of Broken Direction
When dependency direction is ignored:
- Framework upgrades become rewrites
- Tests become brittle
- Refactoring becomes risky
- System understanding degrades rapidly
Most “legacy systems” are not old —
they are systems where dependency direction was never enforced.
Summary
Correct dependency direction means:
- Dependencies point inward
- Stability guides decisions
- Interfaces are owned by inner layers
- Infrastructure remains replaceable
- Frameworks stay at the edges
This is not an academic concern.
It is the difference between a system that evolves
and one that slowly traps its maintainers.
Next
With dependency direction understood, the next architectural topic is:
This explains how Plumego supports growth from a single service to multiple services without architectural collapse.