Skip to content
Santekno.com | Level Up Your Engineering Skills
ID
📖 0%
02 Apr 2024 · 7 mnt baca ·Artikel 89 / 119
Go

12 Membuat Dependecy Injection Library Google Wire

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

Kali ini kita akan memcoba membuat dependency Injection menggunakan Library Wire dari github.com/google/wire. Kegunaan dari library ini yaitu kita bisa membuat dependency yang begitu banyak bisa kita generate langsung dengan ringkas dan mudah.

Dalam hal ini Dependency Injection merupakan sebuah teknik yang mana sebuah objek menerima objek lain yang dibutuhkan ketika pembuatan objek itu sendiri. Biasanya objek yang menerima itu disebut client dan proses mengirim dependencies ke objek tersebut disebut inject. Sebenarnya dependency injection ini sering kita gunakan seperti Handler yang membutuhkan dependencies dari Usecase, atau membuat objek Usecase yang membutuhkan dependencies dari objek Repository.

Dalam pemrograman berorientasi objek sering kita sebut dengan Constructor yang mana ini adalah fungsi yang membuat sebuah objek. Biasanya dalam Golang kita membuat fungsi mirip dengan membuat objek baru. Misalnya pada usecase seperti ini.

go
1func New(repo repository.ArticleRepository) *Usecase {
2	return &Usecase{articleRepository: repo}
3}

Buat fungsi tersebut akan mudah kita buat karena dependency yang kita butuhkan hanya sedikit. Namun saat kode projek kita semakin besar maka akan semakin banyak dependency yang perlu diinjek sehingga kita perlu cara mempermudah injection ini dengan memanfaatkan library.

Pada projek yang sudah kita buat ini kita sudah melakukan manual dependency injection seperti kode dibawah ini.

go
 1func main() {
 2	fileEnv := ".env"
 3	if os.Getenv("environment") == "development" {
 4		fileEnv = "../.env"
 5	}
 6
 7	err := godotenv.Load(fileEnv)
 8	if err != nil {
 9		log.Fatalf("error loading .env file")
10	}
11
12	// inisialisasi database
13	db := database.New()
14
15	// inisialisasi repository
16	repository := mysqlRepository.New(db)
17	// inisialisasi usecase
18	articleUsecase := articleUsecase.New(repository)
19	// inisialisasi handler
20	articleHandler := httpHandler.New(articleUsecase)
21	// // inisialisasi new router
22	router := NewRouter(articleHandler)
23
24	server := http.Server{
25		Addr:    "localhost:3000",
26		Handler: router,
27	}
28
29	err = server.ListenAndServe()
30	if err != nil {
31		panic(err)
32	}
33}

Library Dependency Injection

Banyak sekali library yang telah disediakan di Internet dan beberapa repository github jika kalian mencoba mencarinya tetapi ada beberapa yang paling banyak digunakan diantaranya

Pada kali ini kita akan mencoba library dari Google yaitu Wire sebagai library dependency injection yang akan kita gunakan. Salah satu yang kita pilih ini adalah library paling populer dari Golang. Dan selain itu Google Wire ini merupakan library yang berbasis compile yang artinya kode-nya akan kita generate bukan menggunakan reflection, sehingga Google Wire ini menjadi lebih cepat, karena hasil dari kompilasi adalah kode yang sudah digenerate tanpa menggunakan reflection lagi.

Menambahkan Dependency Google Wire

Masuk ke dalam projek yang lalu buka terminal dan ketik perintah dibawah ini.

bash
1go get github.com/google/wire

Selanjutnya untuk melakukan generate file dependency injection kita juga perlu meng-install library terminal-nya dengan perintah ini.

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

Jika sudah terinstall maka akan terlihat pada terminal seperti dibawah ini.

bash
 1➜ wire help
 2Usage: wire <flags> <subcommand> <subcommand args>
 3
 4Subcommands:
 5        check            print any Wire errors found
 6        commands         list all command names
 7        diff             output a diff between existing wire_gen.go files and what gen would generate
 8        flags            describe all known top-level flags
 9        gen              generate the wire_gen.go file for each package
10        help             describe subcommands and their syntax
11        show             describe all top-level provider sets
12
13➜  learn-golang-restful git:(main)

Pengenalan Provider

Dependency Injection itu kita memerlukan bentuk constuctor yang dalam Google Wire ini disebut dengan Provider.

Misalkan contoh dari projek kita yaitu pada usecase.

go
1type ArticleRepository struct {
2}
3
4type ArticleUsecase struct {
5  *ArticleRepository
6}

Maka biasanya kita akan membuat constructor atau provider-nya seperti ini.

go
1func New() *ArticleStore {
2	return &ArticleStore{}
3}
4
5func New(repo repository.ArticleRepository) *Usecase {
6	return &Usecase{
7    articleRepository: repo
8  }
9}

Pengenalan Injector

Selah kita membuat provider, selanjutnya kita perlu membuat injector yang mana adalah sebuah function constructor yang memiliki isi berupa konfigurasi yang kita informasikan ke Google Wire (generator). Injector ini tidak akan kita pakai dalam program tetapi injector ini adalah fungsi yang akan digunakan oleh Google Wire untuk melakukan auto generate kode dependency injection.

Ketika kita membuat file injector kita perlu menambahkan pada baris pertama pada file tersebut seperti ini.

go
1//go:build wireinject
2// +build wireinject

Setelah mengenal injector dan provider langkah selanjutnya yaitu kita akan menggunakan terminal command line Google Wire untuk melakukan auto generate kode dependency injection. Perintah itu akan berjalan pada folder dan membaca package yang ada pada folder tersebut lalu akan terbuat file wire_gen.go yang isinya adalah kode otomatis dependency injection.

Provider Set

Google Wire memiliki fitur ini untuk melakukan grouping Provider. Provider set ini sangat berguna ketika kode program kita sudah banyak sehingga lebih mudah untuk dibaca ketika melakukan grouping data Provider-nya. Contoh penerapan Provider misalnya seperti dibawah ini.

go
1var articleSet = wire.NewSet(mysqlRepository.New, postgresRepository.New)
2
3var categorySet = wire.NewSet(mysqlRepository.New, postgresRepository.New)
4
5func InizializedArticleUsecase() usecase.ArticleUsecase {
6  wire.Build(articleSet, categorySet, usecase.New)
7}

Binding Interface

Kita biasanya menggunakan interface sebagai kontrak struct. Secara default, Google Wire menggunakan tipe data asli untuk melakukan dependency injection, sehingga jika terdapat parameter berupa interface maka tidak ada Provider yang mengembalikan interface tersebut maka dianggap error. Pada kasus seperti ini kita bisa memberi tahu Google Wire jika ingin melakukan binding interface dengan meberi sebuah interface yang menggunakan provider dengan tipe sesuai.

Pada projek ini kita bisa melihat pada repository ataupun usecase yang memiliki struct interface maka saat pembuatan injector-nya pasti menggunakan binding. Contoh-nya seperti dibawah ini.

go
1wire.Build(
2	mysqlRepository.New,
3	wire.Bind(new(repository.ArticleRepository), new(*mysqlRepository.ArticleStore)),
4)

Struct Provider

Kita bisa membuat Struct Provider yang mana struct yang bida kita jadikan sebagai Provider secara otomatis kita sebut menjadi Provider. Maka jika kita ingin melakukan dependency injection terhadap field yang terdapat pada struct tersebut kita cukup menyebutkan nama filed yang akan diinject atau lakukan injection ke dalam semua filed dengan menggunakan karakter * (bintang). Contoh seperti dibawah ini.

go
1wire.Build(
2	Usecase,
3  wire.Struct(new(repository.ArticleStore),"config")
4)

Implementasi Auto Generate Dependency Injection Pada Projek

Sebelumnya kode pada file main.go itu kita membuat manual dependency injection, maka sekarang kita akan mencoba mengubah menggunakan generator Google Wire. Pertama kita buat folder pada pkg/wire/ dan buat file injector.go lalu isi pada file tersebut seperti dibawah ini.

go
 1//go:build wireinject
 2// +build wireinject
 3
 4package wire
 5
 6import (
 7	"net/http"
 8
 9	"github.com/google/wire"
10	httpHandler "github.com/santekno/learn-golang-restful/internal/delivery/http"
11	"github.com/santekno/learn-golang-restful/internal/repository"
12	mysqlRepository "github.com/santekno/learn-golang-restful/internal/repository/mysql"
13	"github.com/santekno/learn-golang-restful/internal/usecase"
14	articleUsecase "github.com/santekno/learn-golang-restful/internal/usecase/article"
15	"github.com/santekno/learn-golang-restful/pkg/database"
16	"github.com/santekno/learn-golang-restful/pkg/router"
17)
18
19var articleSet = wire.NewSet(
20	mysqlRepository.New,
21	wire.Bind(new(repository.ArticleRepository), new(*mysqlRepository.ArticleStore)),
22	articleUsecase.New,
23	wire.Bind(new(usecase.ArticleUsecase), new(*articleUsecase.Usecase)),
24	httpHandler.New,
25)
26
27func InitializedServer() *http.Server {
28	wire.Build(
29		database.New,
30		articleSet,
31		router.NewRouter,
32		router.NewServer,
33	)
34	return nil
35}

Pertama kita buat articleSet untuk melakukan grouping pada article repository, usecase dan handler dengan membuat seperti ini.

go
1var articleSet = wire.NewSet(
2	mysqlRepository.New,
3	wire.Bind(new(repository.ArticleRepository), new(*mysqlRepository.ArticleStore)),
4	articleUsecase.New,
5	wire.Bind(new(usecase.ArticleUsecase), new(*articleUsecase.Usecase)),
6	httpHandler.New,
7)

Disini bisa kita lihat mysqlRepository.New memiliki return Interface maka kita perlu membuat binding dari repository Interface article ke dalam implementasinya maka akan terlihat seperti dibawah ini.

go
1  mysqlRepository.New,
2	wire.Bind(new(repository.ArticleRepository), new(*mysqlRepository.ArticleStore)),

Begitu pun sama halnya ketika usecase perlu memiliki interface terhadap handler, maka akan membuat binding seperti ini.

go
1	articleUsecase.New,
2	wire.Bind(new(usecase.ArticleUsecase), new(*articleUsecase.Usecase)),

Dan terakhir kita membuat fungsi inisialisasi yang memberikan return *http.Server.

go
1func InitializedServer() *http.Server {
2	wire.Build(
3		database.New,
4		articleSet,
5		router.NewRouter,
6		router.NewServer,
7	)
8	return nil
9}

Setelah selesai menyelesaikan injector kita akan coba melakukan auto generate dengan perintah dibawah ini.

bash
1wire

Dan akan terbuat file dengan nama wire_gen.go dengan isi file seperti ini.

go
 1// Code generated by Wire. DO NOT EDIT.
 2
 3//go:generate go run -mod=mod github.com/google/wire/cmd/wire
 4//go:build !wireinject
 5// +build !wireinject
 6
 7package wire
 8
 9import (
10	"github.com/google/wire"
11	http2 "github.com/santekno/learn-golang-restful/internal/delivery/http"
12	"github.com/santekno/learn-golang-restful/internal/repository"
13	"github.com/santekno/learn-golang-restful/internal/repository/mysql"
14	"github.com/santekno/learn-golang-restful/internal/usecase"
15	"github.com/santekno/learn-golang-restful/internal/usecase/article"
16	"github.com/santekno/learn-golang-restful/pkg/database"
17	"github.com/santekno/learn-golang-restful/pkg/router"
18	"net/http"
19)
20
21// Injectors from injector.go:
22
23func InitializedServer() *http.Server {
24	db := database.New()
25	articleStore := mysql.New(db)
26	usecase := article.New(articleStore)
27	delivery := http2.New(usecase)
28	httprouterRouter := router.NewRouter(delivery)
29	server := router.NewServer(httprouterRouter)
30	return server
31}
32
33// injector.go:
34
35var 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)

Nah tinggal kita ubah sedikit file pada main.go menjadi seperti ini.

go
 1package main
 2
 3import (
 4	"log"
 5	"os"
 6
 7	_ "github.com/go-sql-driver/mysql"
 8	"github.com/joho/godotenv"
 9	"github.com/santekno/learn-golang-restful/pkg/wire"
10)
11
12func main() {
13	fileEnv := ".env"
14	if os.Getenv("environment") == "development" {
15		fileEnv = "../.env"
16	}
17
18	err := godotenv.Load(fileEnv)
19	if err != nil {
20		log.Fatalf("error loading .env file")
21	}
22
23	server := wire.InitializedServer()
24
25	err = server.ListenAndServe()
26	if err != nil {
27		panic(err)
28	}
29}

Lebih simpel bukan? memang pada tahapan awal kita agak ribet dan banyak sekali yang akan kita definisikan tetapi ke depannya jika kita ingin menambahkan provider yang baru tidak dimulai lagi dari awal tetapi hanya tinggal menambahkan newSet ataupun binding-nya saja.

Artikel Terkait

💬 Komentar