This time we will try to make Dependency Injection using the Wire Library from github.com/google/wire. The usefulness of this library is that we can make so many dependencies that we can generate directly concisely and easily.
In this case Dependency Injection is a technique where an object receives another object that is needed when creating the object itself. Usually the receiving object is called client and the process of sending dependencies to the object is called inject. Actually, we often use this dependency injection such as Handler which requires dependencies from Usecase, or creating Usecase objects that require dependencies from Repository objects.
In object-oriented programming we often call it Constructor which is a function that creates an object. Usually in Golang we create a function similar to creating a new object. For example in a usecase like this.
func New(repo repository.ArticleRepository) *Usecase {
return &Usecase{articleRepository:repo}
}
Creating these functions will be easy because we only need a few dependencies. But when our project code gets bigger, there will be more dependencies that need to be injected so we need a way to simplify this injection by utilizing library.
In the project that we have created, we have done manual dependency injection like the code below.
func main() {
fileEnv := ".env"
if os.Getenv("environment") == "development" {
fileEnv = "../.env"
}
err := godotenv.Load(fileEnv)
if err != nil {
log.Fatalf("error loading .env file")
}
// initialize the database
db := database.New()
// initialize repository
repository := mysqlRepository.New(db)
// initialize usecase
articleUsecase := articleUsecase.New(repository)
// handler initialization
articleHandler := httpHandler.New(articleUsecase)
// // initialize new router
router := NewRouter(articleHandler)
server := http.Server{
Addr: "localhost:3000",
Handler: router,
}
err = server.ListenAndServe()
if err != nil {
panic(err)
}
}
Library Dependency Injection
There are many libraries that have been provided on the Internet and several github repositories if you try to search for them but there are some of the most widely used including
- https://github.com/google/wire
- https://github.com/uber-go/fx
- https://github.com/golobby/container
- and many others
This time we will try the library from Google, namely Wire as the dependency injection library that we will use. The one we chose is the most popular library from Golang. And besides that, Google Wire is a compile-based library, which means that we will generate the code instead of using reflection, so that Google Wire becomes faster, because the result of compilation is code that has been generated without using reflection again.
Adding Google Wire Dependencies
Enter the project then open the terminal and type the command below.
go get github.com/google/wire
Furthermore, to generate dependency injection files we also need to install the terminal library with this command.
go install github.com/google/wire/cmd/wire@latest
If it is installed, it will be seen in the terminal as below.
➜ wire help
Usage: wire <flags> <subcommand> <subcommand args>
Subcommands:
check print any Wire errors found
commands list all command names
diff output a diff between existing wire_gen.go files and what gen would generate
flags describe all known top-level flags
gen generates the wire_gen.go file for each package
help describe subcommands and their syntax
show describe all top-level provider sets
➜ learn-golang-restful git:(main) ✗
Provider Introduction
Dependency Injection requires a form of constuctor which in Google Wire is called Provider.
Let’s say the example of our project is in the usecase.
type ArticleRepository struct {
}
type ArticleUsecase struct {
*ArticleRepository
}
Then usually we will create the constructor or provider like this.
func New() *ArticleStore {
return &ArticleStore{}
}
func New(repo repository.ArticleRepository) *Usecase {
return &Usecase{
articleRepository:repo
}
}
Introduction to Injectors
After we have created the provider, next we need to create an injector which is a function constructor that has the contents of the configuration that we inform to Google Wire (generator). We will not use this injector in the program but this injector is a function that will be used by Google Wire to auto generate dependency injection code.
When we create the injector file we need to add the first line of the file like this.
//go:build wireinject
// +build wireinject
After getting to know injector and provider the next step is that we will use the terminal command line Google Wire to do auto generate dependency injection code. The command will run on the folder and read the package in the folder then the wire_gen.go
file will be made which contains the automatic dependency injection code.
Provider Set
Google Wire has this feature to do Provider grouping. Provider set is very useful when we have a lot of program code so that it is easier to read when grouping the Provider data. An example of the application of Provider is as below.
var articleSet = wire.NewSet(mysqlRepository.New, postgresRepository.New)
var categorySet = wire.NewSet(mysqlRepository.New, postgresRepository.New)
func InizializedArticleUsecase() usecase.ArticleUsecase {
wire.Build(articleSet, categorySet, usecase.New)
}
Binding Interface
We usually use interfaces as struct contracts. By default, Google Wire uses native data types to perform dependency injection, so if there is a parameter in the form of an interface and no Provider returns the interface, it is considered an error. In cases like this we can tell Google Wire if we want to bind an interface by giving an interface that uses a provider with the appropriate type.
In this project we can see in the repository or usecase that has a struct interface then when making the injector it must use binding. The example is as below.
wire.Build(
mysqlRepository.New,
wire.Bind(new(repository.ArticleRepository), new(*mysqlRepository.ArticleStore)),
)
Struct Provider
We can create a Struct Provider where the struct that we can make as a Provider is automatically called a Provider. So if we want to do dependency injection on the field contained in the struct, we only need to mention the name of the file to be injected or do injection into all files using the * (star) character. Example as below.
wire.Build(
Usecase,
wire.Struct(new(repository.ArticleStore), "config")
)
Implementation of Auto Generate Dependency Injection on Project
Previously the code in the main.go
file we made manual dependency injection, so now we will try to change using the Google Wire generator. First we create a folder in pkg/wire/
and create an injector.go
file then fill in the file as below.
//go:build wireinject
// +build wireinject
package wire
import (
"net/http"
"github.com/google/wire"
httpHandler "github.com/santekno/learn-golang-restful/internal/delivery/http"
"github.com/santekno/learn-golang-restful/internal/repository"
mysqlRepository "github.com/santekno/learn-golang-restful/internal/repository/mysql"
"github.com/santekno/learn-golang-restful/internal/usecase"
articleUsecase "github.com/santekno/learn-golang-restful/internal/usecase/article"
"github.com/santekno/learn-golang-restful/pkg/database"
"github.com/santekno/learn-golang-restful/pkg/router"
)
var articleSet = wire.NewSet(
mysqlRepository.New,
wire.Bind(new(repository.ArticleRepository), new(*mysqlRepository.ArticleStore)),
articleUsecase.New,
wire.Bind(new(usecase.ArticleUsecase), new(*articleUsecase.Usecase)),
httpHandler.New,
)
func InitializedServer() *http.Server {
wire.Build(
database.New,
articleSet,
router.NewRouter,
router.NewServer,
)
return nil
}
First we create an articleSet to group the article repository, usecase and handler by creating something like this.
var articleSet = wire.NewSet(
mysqlRepository.New,
wire.Bind(new(repository.ArticleRepository), new(*mysqlRepository.ArticleStore)),
articleUsecase.New,
wire.Bind(new(usecase.ArticleUsecase), new(*articleUsecase.Usecase)),
httpHandler.New,
)
Here we can see mysqlRepository.New
has a return Interface then we need to make a binding from the repository Interface article into the implementation it will look like below.
mysqlRepository.New,
wire.Bind(new(repository.ArticleRepository), new(*mysqlRepository.ArticleStore)),
Similarly, when the usecase needs to have an interface to the handler, it will create a binding like this.
articleUsecase.new,
wire.Bind(new(usecase.ArticleUsecase), new(*articleUsecase.Usecase)),
And finally we create an initialization function that returns *http.Server
.
func InitializedServer() *http.Server {
wire.Build(
database.New,
articleSet,
router.NewRouter,
router.NewServer,
)
return nil
}
After completing the injector we will try to do auto generate with the command below.
wire
And a file will be created with the name wire_gen.go
with the contents of the file like this.
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package wire
import (
"github.com/google/wire"
http2 "github.com/santekno/learn-golang-restful/internal/delivery/http"
"github.com/santekno/learn-golang-restful/internal/repository"
"github.com/santekno/learn-golang-restful/internal/repository/mysql"
"github.com/santekno/learn-golang-restful/internal/usecase"
"github.com/santekno/learn-golang-restful/internal/usecase/article"
"github.com/santekno/learn-golang-restful/pkg/database"
"github.com/santekno/learn-golang-restful/pkg/router"
"net/http"
)
// Injectors from injector.go:
func InitializedServer() *http.Server {
db := database.New()
articleStore := mysql.New(db)
usecase := article.New(articleStore)
delivery := http2.New(usecase)
httprouterRouter := router.NewRouter(delivery)
server := router.NewServer(httprouterRouter)
return server
}
// injector.go:
var articleSet = wire.NewSet(mysql.New, wire.Bind(new(repository.ArticleRepository), new(*mysql.ArticleStore)), article.New, wire.Bind(new(usecase.ArticleUsecase), new(*article.Usecase)), http2.New)
Now we just need to change the file a little in main.go
to be like this.
package main
import (
"log"
"os"
_ "github.com/go-sql-driver/mysql"
"github.com/joho/godotenv"
"github.com/santekno/learn-golang-restful/pkg/wire"
)
func main() {
fileEnv := ".env"
if os.Getenv("environment") == "development" {
fileEnv = "../.env"
}
err := godotenv.Load(fileEnv)
if err != nil {
log.Fatalf("error loading .env file")
}
server := wire.InitializedServer()
err = server.ListenAndServe()
if err != nil {
panic(err)
}
}
It’s simpler, isn’t it? Indeed, at the initial stage we are a bit complicated and there are so many things that we will define, but in the future if we want to add a new provider, we don’t start from the beginning but only add the newSet or binding.