# Dependency Injection Pattern in Go ## Pattern Overview Dependency Injection (DI) is a pattern where dependencies are provided to a component rather than the component creating them itself. In Go, this is typically done through constructor functions that accept dependencies as parameters. ## Pattern Structure ```go // Define dependencies as interfaces (optional but recommended) type Database interface { Query(query string) (Result, error) } // Component accepts dependencies via constructor type Service struct { db Database logger Logger config *Config } // Constructor injects dependencies func NewService(db Database, logger Logger, config *Config) *Service { return &Service{ db: db, logger: logger, config: config, } } ``` ## Real Implementation from Project ### Handler with Dependencies ```go // internal/handlers/cv.go // CVHandler handles CV-related HTTP requests type CVHandler struct { tmpl *templates.Manager // Injected template manager host string // Injected host configuration } // NewCVHandler creates a new CV handler with injected dependencies func NewCVHandler(tmpl *templates.Manager, host string) *CVHandler { return &CVHandler{ tmpl: tmpl, host: host, } } // Methods use injected dependencies func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) { // Use injected template manager if err := h.tmpl.Render(w, "index.html", data); err != nil { // ... } // Use injected host for absolute URLs canonicalURL := fmt.Sprintf("http://%s/", h.host) } ``` ### Template Manager with Dependencies ```go // internal/templates/manager.go // Manager handles template rendering type Manager struct { templates map[string]*template.Template config *config.TemplateConfig // Injected configuration mu sync.RWMutex } // NewManager creates template manager with injected config func NewManager(config *config.TemplateConfig) (*Manager, error) { m := &Manager{ templates: make(map[string]*template.Template), config: config, // Store injected config } // Use config to load templates if err := m.loadTemplates(); err != nil { return nil, err } return m, nil } // Methods use injected config func (m *Manager) loadTemplates() error { // Use injected config files, err := filepath.Glob(m.config.Dir + "/*.html") // ... } ``` ### Main Function - Wiring Dependencies ```go // main.go func main() { // Load configuration cfg := config.Load() // Create template manager (with config dependency) tmplManager, err := templates.NewManager(cfg.Templates) if err != nil { log.Fatal(err) } // Create handlers (with template manager dependency) cvHandler := handlers.NewCVHandler(tmplManager, cfg.Server.Host) healthHandler := handlers.NewHealthHandler() // Setup routes (with handler dependencies) handler := routes.Setup(cvHandler, healthHandler) // Start server server := &http.Server{ Addr: cfg.Server.Port, Handler: handler, } log.Printf("Server starting on %s", cfg.Server.Port) if err := server.ListenAndServe(); err != nil { log.Fatal(err) } } ``` ## Benefits of Dependency Injection ### 1. Testability ```go // Without DI: Hard to test type Handler struct { // Creates dependencies internally } func NewHandler() *Handler { db := database.Connect("prod-db") // Can't mock! return &Handler{db: db} } // With DI: Easy to test type Handler struct { db Database // Interface } func NewHandler(db Database) *Handler { return &Handler{db: db} } // Test with mock func TestHandler(t *testing.T) { mockDB := &MockDatabase{} handler := NewHandler(mockDB) // Test with mock } ``` ### 2. Flexibility ```go // Switch implementations without changing handler code // Production realDB := &PostgresDB{conn: conn} handler := NewHandler(realDB) // Testing mockDB := &MockDB{} handler := NewHandler(mockDB) // Development localDB := &SQLiteDB{path: "dev.db"} handler := NewHandler(localDB) ``` ### 3. Explicit Dependencies ```go // Clear what a component needs func NewService( db Database, cache Cache, logger Logger, config *Config, ) *Service { // Dependencies are explicit and visible return &Service{ db: db, cache: cache, logger: logger, config: config, } } ``` ## Constructor Patterns ### 1. Simple Constructor ```go // Direct initialization func NewHandler(tmpl *templates.Manager, host string) *Handler { return &Handler{ tmpl: tmpl, host: host, } } ``` ### 2. Constructor with Validation ```go // Validate dependencies func NewHandler(tmpl *templates.Manager, host string) (*Handler, error) { if tmpl == nil { return nil, errors.New("template manager is required") } if host == "" { return nil, errors.New("host is required") } return &Handler{ tmpl: tmpl, host: host, }, nil } ``` ### 3. Constructor with Options ```go // Options pattern for many optional dependencies type HandlerOptions struct { Host string Timeout time.Duration MaxRetries int } func NewHandler(tmpl *templates.Manager, opts *HandlerOptions) *Handler { // Apply defaults if opts == nil { opts = &HandlerOptions{ Host: "localhost:8080", Timeout: 30 * time.Second, MaxRetries: 3, } } return &Handler{ tmpl: tmpl, host: opts.Host, timeout: opts.Timeout, maxRetries: opts.MaxRetries, } } ``` ### 4. Functional Options ```go // Functional options pattern type HandlerOption func(*Handler) func WithTimeout(d time.Duration) HandlerOption { return func(h *Handler) { h.timeout = d } } func WithLogger(logger Logger) HandlerOption { return func(h *Handler) { h.logger = logger } } func NewHandler(tmpl *templates.Manager, opts ...HandlerOption) *Handler { h := &Handler{ tmpl: tmpl, timeout: 30 * time.Second, // Default } // Apply options for _, opt := range opts { opt(h) } return h } // Usage handler := NewHandler( tmplManager, WithTimeout(10*time.Second), WithLogger(logger), ) ``` ## Interface-Based DI ### Define Interfaces ```go // Define interface for dependencies type TemplateRenderer interface { Render(w io.Writer, name string, data interface{}) error } type DataLoader interface { LoadCV(lang string) (*CV, error) LoadUI(lang string) (*UI, error) } // Handler depends on interfaces, not concrete types type Handler struct { tmpl TemplateRenderer data DataLoader } func NewHandler(tmpl TemplateRenderer, data DataLoader) *Handler { return &Handler{ tmpl: tmpl, data: data, } } ``` ### Benefits of Interfaces ```go // Easy to mock for testing type MockRenderer struct { RenderCalled bool RenderError error } func (m *MockRenderer) Render(w io.Writer, name string, data interface{}) error { m.RenderCalled = true return m.RenderError } // Test with mock func TestHandler(t *testing.T) { mock := &MockRenderer{} handler := NewHandler(mock, nil) // Test handler.Home(w, r) // Verify if !mock.RenderCalled { t.Error("expected Render to be called") } } ``` ## Dependency Injection Patterns ### 1. Constructor Injection (Most Common in Go) ```go type Service struct { db Database } func NewService(db Database) *Service { return &Service{db: db} } ``` ### 2. Method Injection (Less Common) ```go type Service struct { // No db field } func (s *Service) Process(db Database, data Data) error { // db passed per-method call return db.Save(data) } ``` ### 3. Property Injection (Avoid in Go) ```go // Not idiomatic Go type Service struct { DB Database // Public field set after construction } service := &Service{} service.DB = db // Set dependency manually - DON'T DO THIS ``` ## Testing with Dependency Injection ### Mock Dependencies ```go // internal/handlers/cv_pages_test.go func TestHome(t *testing.T) { // Create real template manager for test cfg := &config.TemplateConfig{ Dir: "../../templates", PartialsDir: "../../templates/partials", HotReload: true, } tmplManager, err := templates.NewManager(cfg) if err != nil { t.Fatal(err) } // Inject into handler handler := handlers.NewCVHandler(tmplManager, "localhost:8080") // Test req := httptest.NewRequest(http.MethodGet, "/?lang=en", nil) w := httptest.NewRecorder() handler.Home(w, req) // Verify if w.Code != http.StatusOK { t.Errorf("expected 200, got %d", w.Code) } } ``` ### Test Doubles ```go // Create test double that implements interface type StubRenderer struct { rendered bool data interface{} } func (s *StubRenderer) Render(w io.Writer, name string, data interface{}) error { s.rendered = true s.data = data fmt.Fprintf(w, "Test") return nil } func TestWithStub(t *testing.T) { stub := &StubRenderer{} handler := NewHandler(stub, "test:8080") handler.Home(w, req) if !stub.rendered { t.Error("expected template to be rendered") } } ``` ## Dependency Injection Containers Go doesn't have built-in DI containers like some languages, but libraries exist: ### Wire (Google) ```go // wire.go //go:build wireinject import "github.com/google/wire" func InitializeHandler() (*handlers.CVHandler, error) { wire.Build( config.Load, templates.NewManager, handlers.NewCVHandler, ) return &handlers.CVHandler{}, nil } // Wire generates code at compile time ``` ### Dig (Uber) ```go import "go.uber.org/dig" func main() { container := dig.New() // Register constructors container.Provide(config.Load) container.Provide(templates.NewManager) container.Provide(handlers.NewCVHandler) // Invoke err := container.Invoke(func(h *handlers.CVHandler) { // Use handler }) } ``` ### Manual Wiring (Recommended for Simple Apps) ```go // main.go - Manual wiring is clear and simple func main() { cfg := config.Load() tmpl, _ := templates.NewManager(cfg.Templates) handler := handlers.NewCVHandler(tmpl, cfg.Server.Host) // Clear dependency graph } ``` ## Best Practices ### ✅ DO ```go // Accept dependencies via constructor func NewHandler(db Database, logger Logger) *Handler { return &Handler{db: db, logger: logger} } // Depend on interfaces, not concrete types type Handler struct { db Database // Interface } // Make dependencies explicit func NewService(db Database, cache Cache, queue Queue) *Service { // All dependencies visible in signature } // Validate dependencies func NewHandler(db Database) (*Handler, error) { if db == nil { return nil, errors.New("database is required") } return &Handler{db: db}, nil } // Keep constructors simple func NewHandler(tmpl *templates.Manager, host string) *Handler { return &Handler{tmpl: tmpl, host: host} } ``` ### ❌ DON'T ```go // DON'T create dependencies inside components func NewHandler() *Handler { db := connectDatabase() // Wrong! Hard to test return &Handler{db: db} } // DON'T use global variables var globalDB Database func (h *Handler) Save() { globalDB.Save() // Wrong! Hidden dependency } // DON'T make dependencies public type Handler struct { DB Database // Wrong! Should be private } // DON'T over-complicate with DI containers for simple apps // Manual wiring in main() is often clearer ``` ## Circular Dependencies ### Problem ```go // ServiceA depends on ServiceB type ServiceA struct { b *ServiceB } // ServiceB depends on ServiceA type ServiceB struct { a *ServiceA } // Can't construct either! ``` ### Solution: Interfaces ```go // Break cycle with interface type BInterface interface { DoB() } type ServiceA struct { b BInterface // Depends on interface } type ServiceB struct { // No dependency on A } func (b *ServiceB) DoB() {} // Can construct b := &ServiceB{} a := &ServiceA{b: b} ``` ## Related Patterns - **Handler Pattern**: Uses DI for template managers - **Singleton Pattern**: Often combined with DI - **Factory Pattern**: Can be used with DI ## Further Reading - [Dependency Injection in Go](https://blog.drewolson.org/dependency-injection-in-go) - [Google Wire](https://github.com/google/wire) - [Uber Dig](https://github.com/uber-go/dig)