Golang / GoLang Production Patterns and Web Standards Interview Questions
How do you implement dependency injection in Go without a framework?
Go's idiomatic approach to dependency injection is constructor-based: pass dependencies as arguments to constructors that return concrete types. This is simpler, more testable, and more readable than reflection-based DI frameworks.
// Define dependencies as interfaces (for testability) type UserRepository interface { FindByID(ctx context.Context, id int) (*User, error) Save(ctx context.Context, user *User) error } type EmailSender interface { Send(ctx context.Context, to, subject, body string) error } // Service accepts interfaces â can be mocked in tests type UserService struct { repo UserRepository email EmailSender logger *slog.Logger } // Constructor injection â explicit, no magic func NewUserService( repo UserRepository, email EmailSender, logger *slog.Logger, ) *UserService { return &UserService{repo: repo, email: email, logger: logger} } func (s *UserService) Register(ctx context.Context, req RegisterRequest) (*User, error) { user := &User{Name: req.Name, Email: req.Email} if err := s.repo.Save(ctx, user); err != nil { return nil, fmt.Errorf("UserService.Register: %w", err) } s.email.Send(ctx, user.Email, "Welcome!", "Thanks for joining.") return user, nil } // Wire everything together in main() func main() { db, _ := sql.Open("postgres", os.Getenv("DATABASE_URL")) logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) repo := postgres.NewUserRepository(db) email := smtp.NewEmailSender(os.Getenv("SMTP_HOST")) svc := NewUserService(repo, email, logger) h := handlers.NewUserHandler(svc) mux := http.NewServeMux() mux.Handle("/users/", h) http.ListenAndServe(":8080", mux) }
Testing advantage: because dependencies are injected as interfaces, tests pass mock implementations without any framework or code generation. A mock UserRepository is just a struct implementing the interface with test-controlled responses.
More Related questions...