Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dependency Injection Easy

Dependency injection (DI) in Go means passing dependencies (usually as interfaces) into a struct or function rather than having them create their own. This makes code testable, composable, and decoupled from concrete implementations. No framework needed — Go interfaces and constructors are sufficient.

Implementation

package order

// Repository is the dependency interface — any storage backend can satisfy it.
type Repository interface {
	Save(o Order) error
	FindByID(id string) (Order, error)
}

// Notifier is another dependency.
type Notifier interface {
	Notify(userID, message string) error
}

// Service is the business logic layer. Dependencies are injected via the constructor.
type Service struct {
	repo     Repository
	notifier Notifier
}

func NewService(repo Repository, notifier Notifier) *Service {
	return &Service{repo: repo, notifier: notifier}
}

func (s *Service) PlaceOrder(o Order) error {
	if err := s.repo.Save(o); err != nil {
		return fmt.Errorf("save order: %w", err)
	}
	return s.notifier.Notify(o.UserID, "Your order has been placed")
}

Usage

// Production wiring — real implementations.
db := postgres.NewOrderRepo(connStr)
email := smtp.NewNotifier(smtpHost)
svc := order.NewService(db, email)

// Test wiring — mock implementations.
func TestPlaceOrder(t *testing.T) {
	mockRepo := &MockRepo{SaveFn: func(o Order) error { return nil }}
	mockNotify := &MockNotifier{NotifyFn: func(uid, msg string) error { return nil }}

	svc := order.NewService(mockRepo, mockNotify)
	err := svc.PlaceOrder(Order{ID: "1", UserID: "alice"})
	if err != nil {
		t.Fatal(err)
	}
}

Rules of Thumb

  • Accept interfaces, return structs. Define the interface where it’s consumed (not where it’s implemented).
  • Inject dependencies through constructors (NewService(...)) — avoid global state and init-time wiring.
  • Keep interfaces small. One or two methods is ideal — Go’s implicit interface satisfaction makes this natural.
  • Don’t reach for a DI framework. Constructor injection + interfaces covers 99% of Go use cases.