Concept · SOLID principles

Dependency inversion principle

Business logic owns the interfaces it needs; infrastructure implements them. Dependencies point toward policy, not toward details.

3 minDIPSOLIDdependency injectionDI

The dependency inversion principle (DIP), the D in SOLID, has two parts:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. 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:

go
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} }
go
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.

Learn it in depth