Concept · SOLID principles
Dependency inversion principle
Business logic owns the interfaces it needs; infrastructure implements them. Dependencies point toward policy, not toward details.
The dependency inversion principle (DIP), the D in SOLID, has two parts:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
"High-level" means policy — business rules like pricing or eligibility. "Low-level" means details — Postgres, an HTTP client, a vendor SDK.
What gets inverted
Without DIP, source dependencies follow the flow of control: billing calls the database, so billing imports the database package. Billing can't be compiled or tested without Postgres, and replacing Postgres means editing billing.
With DIP, billing declares the interface it needs, and the database package implements it:
package billing
type InvoiceStore interface {
Save(ctx context.Context, inv Invoice) error
}
type Service struct{ store InvoiceStore }
func NewService(store InvoiceStore) *Service { return &Service{store: store} }package postgres // imports billing, not the other way round
func (s *InvoiceStore) Save(ctx context.Context, inv billing.Invoice) error { /* SQL */ }Control still flows from billing to Postgres at runtime, but the source code dependency now points from Postgres to billing — inverted. The interface belongs to the high-level module, which is why in Go it lives in the consumer's package, not next to the implementation.
Dependency injection
DIP describes the direction of dependencies; dependency injection is the mechanism for supplying them — passing collaborators in through constructors or parameters instead of creating them inside. In Go, main is the composition root: it builds the concrete types and wires them together. Frameworks exist (Wire, Fx), but plain constructors are enough for most services.
What it buys
- Testability: business logic runs against in-memory fakes.
- Replaceability: swap a payment provider or database by writing a new adapter.
- Stable core: the most important code depends on nothing volatile.
It's the foundation of ports and adapters and clean architecture. Don't add an interface for every dependency by reflex, though: inverting a dependency on something stable and pure (the standard library, a math package) only adds indirection. See the SOLID lesson.