---
title: Use Dependency Injection
impact: HIGH
impactDescription: enables testability and loose coupling
tags: dependency-injection, testing, coupling, architecture, quality
---

## Use Dependency Injection

Direct instantiation creates tight coupling, making testing difficult and changes risky. DI enables mockability, replaceability, and testability.

**Incorrect (hardcoded dependencies):**

```go
type OrderService struct {
	db     *PostgresDatabase
	mailer *SendGridMailer
}

func NewOrderService() *OrderService {
	return &OrderService{
		db:     NewPostgresDatabase(), // Hardcoded dependency
		mailer: NewSendGridMailer(),   // Hardcoded dependency
	}
}

func (s *OrderService) CreateOrder(data OrderData) error {
	order, err := s.db.Insert("orders", data)
	if err != nil {
		return err
	}
	return s.mailer.Send(data.Email, "Order created")
}
```

**Correct (injected dependencies via interfaces):**

```go
type Database interface {
	Insert(table string, data any) (any, error)
}

type Mailer interface {
	Send(to string, message string) error
}

type OrderService struct {
	db     Database
	mailer Mailer
}

func NewOrderService(db Database, mailer Mailer) *OrderService {
	return &OrderService{
		db:     db,
		mailer: mailer,
	}
}

func (s *OrderService) CreateOrder(data OrderData) error {
	order, err := s.db.Insert("orders", data)
	if err != nil {
		return err
	}
	return s.mailer.Send(data.Email, "Order created")
}

// Usage
service := NewOrderService(
	NewPostgresDatabase(connString),
	NewSendGridMailer(apiKey),
)

// Testing
mockDb := new(MockDatabase)
mockMailer := new(MockMailer)
testService := NewOrderService(mockDb, mockMailer)
```

**Benefits:**
- Easy mocking for unit tests
- Swappable implementations
- Clear dependencies visible in constructor/factory
- Supports interface-based design (accept interfaces, return structs)

**Tools:** GolangCI-Lint, Manual review
