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.