Dependency Injection


🚀 Tổng quan DI trong Golang

✳️ Dependency Injection là gì?

Là kỹ thuật thiết kế để inject dependencies (phụ thuộc) vào một object thay vì tự tạo ra chúng. Nó giúp:

  • Decouple (giảm phụ thuộc cứng)

  • Dễ unit test (vì có thể mock)

  • Dễ maintainextend


⚙️ 1. Manual DI (cách Go thường dùng)

Golang không có DI framework built-in như Java Spring, nhưng ta có thể dùng Constructor Injection thủ công.

🎯 Ví dụ thực tế

Giả sử bạn có UserService phụ thuộc vào UserRepository:

type UserRepository interface {
    FindByID(id int) (*User, error)
}

type userRepoImpl struct {}

func (r *userRepoImpl) FindByID(id int) (*User, error) {
    return &User{ID: id, Name: "Titan"}, nil
}
type UserService struct {
    repo UserRepository
}

func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

func (s *UserService) GetUser(id int) (*User, error) {
    return s.repo.FindByID(id)
}

🔧 Khởi tạo:

func main() {
    repo := &userRepoImpl{}
    service := NewUserService(repo)

    user, _ := service.GetUser(1)
    fmt.Println(user)
}

✅ Đây là DI dạng Constructor Injection, đơn giản, rõ ràng và dễ test/mock.


🧪 2. Dễ test nhờ DI

type mockUserRepo struct{}

func (m *mockUserRepo) FindByID(id int) (*User, error) {
    return &User{ID: id, Name: "Mocked"}, nil
}

func TestGetUser(t *testing.T) {
    service := NewUserService(&mockUserRepo{})
    user, _ := service.GetUser(42)
    if user.Name != "Mocked" {
        t.Fatal("expected mocked user")
    }
}

🧱 3. Struct Composition: DI nâng cao

Bạn có thể gom các services lại thành "container" hoặc module:

type Services struct {
    UserService *UserService
}

func InitServices() *Services {
    userRepo := &userRepoImpl{}
    userService := NewUserService(userRepo)

    return &Services{
        UserService: userService,
    }
}

🧰 4. Dùng Google Wire (DI Compile-time Generator)

Nếu project lớn, manual DI dài dòng, bạn có thể dùng Google Wire:

go install github.com/google/wire/cmd/wire@latest

Khai báo:

var ProviderSet = wire.NewSet(
    NewUserRepo,
    NewUserService,
)

Tạo file wire.go:

func InitializeService() *UserService {
    wire.Build(ProviderSet)
    return nil
}
wire  # tạo file wire_gen.go

wire giúp generate code, nhưng vẫn type-safe, compile-time, không runtime magic.


🔁 5. DI runtime: dig (Uber)

Nếu muốn DI container runtime, dùng dig từ Uber:

c := dig.New()
c.Provide(NewUserRepo)
c.Provide(NewUserService)

err := c.Invoke(func(s *UserService) {
    user, _ := s.GetUser(1)
    fmt.Println(user)
})

🧠 dig giống kiểu Spring DI, nhưng nhẹ hơn.


✅ Senior-Level Checklist

Mục tiêu
Status

Manual DI bằng constructor injection

Tách interface để dễ mock

Gom dependencies thành struct container (modular)

Biết dùng Google Wire (compile-time DI)

Biết dùng dig (runtime DI)

Tránh dùng singleton cứng trong service logic


🤝 Tổng kết

  • Manual DI là chuẩn mực trong Go → rõ ràng, testable, không magic

  • Với project lớn, dùng Google Wire để automate wiring

  • Khi cần DI động (plugin, runtime), cân nhắc Uber Dig

  • Đừng lạm dụng singleton → nên inject để dễ test và mock


Last updated