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.