Skip to main content

Dependency Inversion Principle (DIP) in Go

What is Dependency Inversion?

Dependency Inversion is one of the SOLID principles of software design, and in simple words it's about making high-level policy code independent of low-level detail code.

Principle:

  • High-level modules (business rules, policies) should not depend on low-level modules (frameworks, databases, file systems).
  • Both should depend on abstractions (interfaces).
  • Abstractions should not depend on details, but details should depend on abstractions.

Without inversion:

  • The core logic is tied to specific technologies (e.g., SQL database, HTTP client).
  • Swapping implementations is hard.
  • Tests become cumbersome because the real dependencies cannot be replaced easily.

With inversion:

  • Business logic doesn't care how persistence, networking, or logging is implemented.
  • Dependencies (e.g., mock DB, fake HTTP) can be substituted during testing.
  • Low-level details can be evolved or replaced without rewriting high-level logic.

Dependency Inversion is about:

  • Making policies independent of details.
  • Depending on stable abstractions instead of volatile implementations.
  • Achieving flexibility, testability, and maintainability.

Go Example

❌ Without Dependency Inversion:

type UserService struct {
db *sql.DB // depends directly on low-level SQL
}

func (s *UserService) GetUser(id int) (*User, error) {
row := s.db.QueryRow("SELECT id, name FROM users WHERE id=?", id)
// ...
}

Here, UserService depends on *sql.DB. In order to switch to MongoDB or an in-memory store, the service must be rewritten.

✅ With Dependency Inversion:

// Abstraction (producer-defined interface)
type UserRepository interface {
GetByID(id int) (*User, error)
}

// High-level policy depends on abstraction, not detail.
type UserService struct {
repo UserRepository
}

func (s *UserService) GetUser(id int) (*User, error) {
return s.repo.GetByID(id)
}

Different implementations can be plugged in:

type SQLUserRepo struct { db *sql.DB }
func (r *SQLUserRepo) GetByID(id int) (*User, error) { /* ... */ }

type MemoryUserRepo struct { data map[int]*User }
func (r *MemoryUserRepo) GetByID(id int) (*User, error) { return r.data[id], nil }

Now, UserService works with any repo, without knowing about SQL or memory details.