Dependency Injection in Go: for Beginners

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why is Dependency Injection Important in Go?

\n\n

Go (Golang) is a language known for its simplicity and performance. However, when your project starts to grow, the code can quickly turn into "spaghetti," where functions and structures are tightly coupled to each other. This is where Dependency Injection (DI) comes to the rescue.

\n\n

For beginner developers, DI might seem like a complex concept from the world of Java or C#. But in Go, this pattern is implemented elegantly and without frameworks. In this article, we will break down what Dependency Injection is, why it's needed, and how to apply it correctly in Go with practical examples.

\n\n

If you want to write testable, flexible, and maintainable code in Golang, this article is for you.

\n\n

What is Dependency Injection (DI)?

\n\n

Dependency Injection is a design pattern where an object receives its dependencies (other objects it needs to function) from the outside, rather than creating them internally.

\n\n

In simple terms: instead of writing myService := MyService{} inside your code, you "inject" a ready-made instance of myService into a constructor or method.

\n\n

The Problem Without DI

\n\n

Imagine you have an application for sending notifications. Without DI, the code might look like this:

\n\n
package main\n\nimport "fmt"\n\ntype EmailNotifier struct{}\n\nfunc (e EmailNotifier) Send(message string) {\n    fmt.Println("Sending email:", message)\n}\n\ntype UserService struct {\n    notifier EmailNotifier // Tight coupling\n}\n\nfunc NewUserService() *UserService {\n    return &UserService{\n        notifier: EmailNotifier{}, // Creating dependency internally\n    }\n}\n\nfunc (u *UserService) NotifyUser(message string) {\n    u.notifier.Send(message)\n}\n\nfunc main() {\n    service := NewUserService()\n    service.NotifyUser("Hello!")\n}\n
\n\n

What's wrong here?

\n
    \n
  • Tight coupling: UserService is tied to EmailNotifier. If you want to replace Email with SMS, you'll have to change the UserService code.
  • \n
  • Testing difficulty: You cannot test UserService without actually sending an email. This is critical for unit tests.
  • \n
\n\n

The Solution with DI

\n\n

Now let's rewrite the same example using Dependency Injection:

\n\n
package main\n\nimport "fmt"\n\n// Notifier — an interface that abstracts sending notifications\ntype Notifier interface {\n    Send(message string)\n}\n\ntype EmailNotifier struct{}\n\nfunc (e EmailNotifier) Send(message string) {\n    fmt.Println("Sending email:", message)\n}\n\ntype SMSNotifier struct{}\n\nfunc (s SMSNotifier) Send(message string) {\n    fmt.Println("Sending SMS:", message)\n}\n\ntype UserService struct {\n    notifier Notifier // Dependency via interface\n}\n\n// NewUserService accepts Notifier from the outside\nfunc NewUserService(n Notifier) *UserService {\n    return &UserService{notifier: n}\n}\n\nfunc (u *UserService) NotifyUser(message string) {\n    u.notifier.Send(message)\n}\n\nfunc main() {\n    // Injecting EmailNotifier\n    emailService := NewUserService(EmailNotifier{})\n    emailService.NotifyUser("Hello via email!")\n\n    // Injecting SMSNotifier — without changing UserService!\n    smsService := NewUserService(SMSNotifier{})\n    smsService.NotifyUser("Hello via SMS!")\n}\n
\n\n

Now UserService doesn't know which specific notifier is being used. It simply calls the Send method on the passed object. This is Dependency Injection.

\n\n

Key Benefits of DI in Go

\n\n

1. Testability (Mock Objects)

\n\n

DI makes your code ideal for unit tests. You can easily replace a real dependency with a "stub" (mock).

\n\n
package main\n\nimport "testing"\n\ntype MockNotifier struc                

Blogs

Book Recommendations