

Golang Dependency Injection Using Wire
source link: https://dev.to/clavinjune/golang-dependency-injection-using-wire-20h2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Introduction
When it comes to maintaining a large application codebase, sometimes you are confused with dependencies. Moreover, when you create a new struct that depends on a struct with many attributes, you need to provide those attributes manually. What if those attributes need other structs? It will take forever.
Dependency injection is a technique you need to learn to maintain a large application codebase. It helps you to provide the necessary attribute to a struct automatically. Using dependency injection from a small codebase is a good investment. Although a dependency injection on a small code base seems like an over-engineered solution, it will help you whenever your codebase grows big or when the team members are increasing.
In Go, there are many dependency injection tools out there. You can choose whichever you want. Some dependency injection tools work at runtime levels, which use reflection to provide the dependencies but are hard to debug. Google Wire provides a dependency injection tool that works at compile-time. Google Wire utilises code generation to help developers achieve a dependency injection pattern at compile-time. So, in this article, you will learn how to implement a dependency injection pattern using Google Wire.
Directory Structure
tree .
.
├── cmd
│ └── web
│ └── main.go
├── domain
│ └── user.go
├── go.mod
├── go.sum
└── user
├── handler.go
├── provider.go
├── repository.go
├── service.go
└── wire.go
4 directories, 9 files
Enter fullscreen mode
Exit fullscreen mode
Let's say you have this example
module structure.
domain/user.go
domain/user.go
is where you put your structs and interfaces, for example:
package domain
import (
"context"
"net/http"
)
type (
User struct {
ID string `json:"id"`
Username string `json:"username"`
}
UserEntity struct {
ID string
Username string
Password string
}
UserRepository interface {
FetchByUsername(ctx context.Context, username string) (*UserEntity, error)
}
UserService interface {
FetchByUsername(ctx context.Context, username string) (*User, error)
}
UserHandler interface {
FetchByUsername() http.HandlerFunc
}
)
Enter fullscreen mode
Exit fullscreen mode
user/
directory is where you put your implementation for those interfaces, for example:
user/handler.go
package user
import (
"example/domain"
"net/http"
)
type handler struct {
svc domain.UserService
}
func (h *handler) FetchByUsername() http.HandlerFunc {
panic("implement me")
}
Enter fullscreen mode
Exit fullscreen mode
user/service.go
package user
import (
"context"
"example/domain"
)
type service struct {
repo domain.UserRepository
}
func (s *service) FetchByUsername(ctx context.Context, username string) (*domain.User, error) {
panic("implement me")
}
Enter fullscreen mode
Exit fullscreen mode
user/repository.go
package user
import (
"context"
"database/sql"
"example/domain"
)
type repository struct {
db *sql.DB
}
func (r *repository) FetchByUsername(ctx context.Context, username string) (*domain.UserEntity, error) {
panic("implement me")
}
Enter fullscreen mode
Exit fullscreen mode
As you can see from the code above:
-> Handler depends on Service
-> Service depends on Repository
-> Repository depends on Database Connection
Enter fullscreen mode
Exit fullscreen mode
It is pretty much straightforward. So If you want to create the handler
, first you need to create a database connection
, repository
, and finally the service
. That's all for one domain. If you have multiple domains, and each struct has many attributes, it will be more bulky, right? Before it gets more complicated, let's try to use Google Wire
.
Using Google Wire
# installing wire code generator
$ go install github.com/google/wire/cmd/wire@latest
# downloading wire module
$ go get github.com/google/wire@latest
go get: added github.com/google/wire v0.5.0
Enter fullscreen mode
Exit fullscreen mode
After installing Google Wire, let's talk about provider
.
Provider
Provider
is an initializer function where you create a single struct. For example:
func ProvideRepository(db *sql.DB) *repository {
return &repository{
db: db,
}
}
Enter fullscreen mode
Exit fullscreen mode
Pretty much it. Other than that, you can also create a singleton provider using sync.Once
to keep the creation only running once. For example, here is what your user/provider.go
file contents look like:
package user
import (
"database/sql"
"example/domain"
"sync"
)
var (
hdl *handler
hdlOnce sync.Once
svc *service
svcOnce sync.Once
repo *repository
repoOnce sync.Once
)
func ProvideHandler(svc domain.UserService) *handler {
hdlOnce.Do(func() {
hdl = &handler{
svc: svc,
}
})
return hdl
}
func ProvideService(repo domain.UserRepository) *service {
svcOnce.Do(func() {
svc = &service{
repo: repo,
}
})
return svc
}
func ProvideRepository(db *sql.DB) *repository {
repoOnce.Do(func() {
repo = &repository{
db: db,
}
})
return repo
}
Enter fullscreen mode
Exit fullscreen mode
Provider Set
As it sounds, a provider set
is a set of providers. It groups providers into one. If you are going to use those providers are frequently together, a provider set will be helpful. You can add a provider set to your user/provider.go
file.
...
ProviderSet wire.ProviderSet = wire.NewSet(
ProvideHandler,
ProvideService,
ProvideRepository,
)
...
Enter fullscreen mode
Exit fullscreen mode
So, in case you need to import your providers one by one, you can easily import your provider set.
Interface Binding
Interface binding
is needed to bind an abstract interface to its concrete implementation. Your UserService interface
depends on a UserRepository interface
, but your repository provider
returns a pointer of the repository struct
, not a UserRepository interface
. There are two ways to fix this problem:
- Change
ProvideRepository
's return type todomain.UserRepository
- Bind
*repository
to thedomain.UserRepository
But since there's a Go proverb that says accept interfaces, return structs
. It would be wise if you chose the second option. Now you can bind your interfaces inside the provider set.
...
ProviderSet wire.ProviderSet = wire.NewSet(
ProvideHandler,
ProvideService,
ProvideRepository,
// bind each one of the interfaces
wire.Bind(new(domain.UserHandler), new(*handler)),
wire.Bind(new(domain.UserService), new(*service)),
wire.Bind(new(domain.UserRepository), new(*repository)),
)
...
Enter fullscreen mode
Exit fullscreen mode
user/wire.go
Now, after all the dependencies have a provider, let's move on to the user/wire.go
file.
// +build wireinject
package user
import (
"database/sql"
"github.com/google/wire"
)
func Wire(db *sql.DB) *handler {
panic(wire.Build(ProviderSet))
}
Enter fullscreen mode
Exit fullscreen mode
You need to define the +build wireinject
tag to make Google Wire generate the complete code from your current file. wire.Build
is flexible. You can refer to the documentation to use all the possibilities. In this case, since you have defined all the providers inside the provider set, you only need to put the provider set as an argument.
Generate the Code
Now, you can use wire CLI tools to generate the complete code by executing wire ./...
command in your project root directory.
$ wire ./...
wire: example/user: wrote /tmp/example/user/wire_gen.go
Enter fullscreen mode
Exit fullscreen mode
Here is the content of wire_gen.go
file:
// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package user
import (
"database/sql"
)
// Injectors from wire.go:
func Wire(db *sql.DB) *handler {
userRepository := ProvideRepository(db)
userService := ProvideService(userRepository)
userHandler := ProvideHandler(userService)
return userHandler
}
Enter fullscreen mode
Exit fullscreen mode
Wire
function automatically generated by wire. You can directly use the Wire
function inside your main function.
package main
import (
"database/sql"
"example/user"
"net/http"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "")
if err != nil {
panic(err)
}
defer db.Close()
userHandler := user.Wire(db)
http.Handle("/user", userHandler.FetchByUsername())
http.ListenAndServe(":8000", nil)
}
Enter fullscreen mode
Exit fullscreen mode
Conclusion
Using Google Wire on a small codebase seems like a waste, but it helps You create a cleaner constructor function. Instead of initializing all the structs at the main function, you can depend on Wire to inject all the dependencies.
Thank you for reading!
Recommend
-
39
What is a dependency? A dependency is a piece of code (either a library, class, object or any other data type) required by another piece of code to work. Put simply, if module A re...
-
16
Testing external dependencies using dependency injection
-
9
FsAdvent 2020 - Dependency Injection Using Flexible Types and Type Inference 12/5/2020 Dependency Injection Using Flexible Types and Type Inference...
-
13
Dependency Injection in C++ Using Variadic Templates A few days ago I was reading up on variadic templates and it occurred to me that they could be used to automate dependency injection. So I started playing with some code...
-
14
wire与依赖注入 Wire 是一个的Golang依赖注入工具,通过自动生成代码的方式在 编译期 完成依赖注入,Java体系中最出名的 Spring 框架采用
-
7
Dependency Injection in JavaScript — the Best Tool You’re Not Using for your TestsLet me introduce you to your new testing best friendPhoto by
-
10
Lightweight dependency injection and unit testing using async functionsVery often, making code easy to unit test tends to go hand-in-hand with improving that code’s separation of concerns, its state management, and its overall archit...
-
13
Google’s Wire: Automated Dependency Injection in Go
-
14
Sinatra is often seen as a tool for simple APIs, but it can also be used to manage big applications. dry-rb libraries can help you create a modular architecture with system wide dependency injection for your application. Table of co...
-
10
How To Set Up Dependency Injection in Lambda Functions Using Annotations Framework Learn how to set up Dependency Injection when building Lambda Functions using the Annotatios framework. ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK