Intro
Google wire เป็น package ที่ช่วยจัดการเรื่องของ Dependencies injection (DI) ในภาษา go ซึ่งพัฒนาโดย google หน้าที่ของมันคือจะทำการ generate การทำงานฟังก์ชั่น ทั้งหมดของเราที่มีการ inject ค่าไปยังแต่ละฟังก์ชั่นให้เรียกใช้งานร่วมกัน
Dependencies injection (DI) คือ การนำ dependency ที่อยู่ข้างนอกเข้าไปข้างในฟังก์ชั่นแทนที่สร้างใหม่ในฟังก์ชั่น ประโยชน์คือ
- ลดการผูกมัดของโค้ด (loose coupling)
- เขียน test ง่ายขึ้น เมื่อโค้ดไม่ได้ผูกมัดกัน ทำให้เรา mock dependencies แยกออกมาแล้ว inject เข้าไป test ได้ง่าย
ตัวอย่าง
package main
import "fmt"
type Animal interface {
MakeSound()
}
// Dog
type Dog struct{}
func (d *Dog) MakeSound() {
fmt.Println("Woof!")
}
// Cat
type Cat struct{}
func (c *Cat) MakeSound() {
fmt.Println("Meow!")
}
type Zoo struct {
animals []Animal
}
func (z *Zoo) MakeSounds() {
for _, animal := range z.animals {
animal.MakeSound()
}
}
// NewZoo is a constructor function for Zoo with DI
func NewZoo(animals ...Animal) *Zoo {
return &Zoo{animals: animals}
}
func main() {
// Injecting dependencies
dog := &Dog{}
cat := &Cat{}
zoo := NewZoo(dog, cat) // dog, cat inject to NewZoo
zoo.MakeSounds()
}
2 core concepts
1. Providers เป็นฟังก์ชั่นที่ return เพื่อสร้าง object ใหม่ เช่น
type Message string
func NewMessage() Message {
return Message("Hi there!")
}
สามารถทำเป็น group จะเรียกว่า provider sets ได้ เช่น
package foobarbaz
import (
// ...
"github.com/google/wire"
"errors"
)
// Foo
type Foo struct {
X int
}
func ProvideFoo() Foo {
return Foo{X: 42}
}
// Bar
type Bar struct {
X int
}
func ProvideBar(foo Foo) Bar {
return Bar{X: -foo.X}
}
// Baz
type Baz struct {
X int
}
func ProvideBaz(bar Bar) (Baz, error) {
if bar.X == 0 {
return Baz{}, errors.New("cannot provide baz when bar is zero")
}
return Baz{X: bar.X}, nil
}
// Group provider
var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)
2. Injectors เป็นฟังก์ชั่นที่รวบรวม provider ฟังก์ชั่นทั้งหมดมาทำงานรวมกันหลังจาก generate ตัวอย่าง
// +build wireinject
// The build tag makes sure the stub is not built in the final build.
package main
import (
"context"
"github.com/google/wire"
"example.com/foobarbaz"
)
// function injectors
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
wire.Build(foobarbaz.SuperSet)
return foobarbaz.Baz{}, nil
}
เมื่อรู้จัก providers และ injectors แล้วต่อไปเป็นขั้นตอนการติดตั้งและวิธีใช้งานเบื้องต้นของ go wire
Set up and usage in basic
- ติดตั้ง package google wire
go install github.com/google/wire/cmd/wire@latest
- สร้างฟังก์ชั่น provider ที่ต้องการ เช่น
// greeter/greeter.go
package greeter
type Message string
// provider function
func NewMessage() Message {
return Message("Hi there!")
}
type Greeter struct {
Message Message
}
func (g Greeter) Greet() Message {
return g.Message
}
// provider function
func NewGreeter(m Message) Greeter {
return Greeter{Message: m}
}
type Event struct {
Greeter Greeter
}
func (e Event) Start() {
msg := e.Greeter.Greet()
fmt.Println(msg)
}
func NewEvent(g Greeter) Event {
return Event{Greeter: g}
}
var MainBindingSet = wire.NewSet(NewMessage, NewGreeter, NewEvent)
- สร้างฟังก์ชั้น injector ใน
di/wire.go
// di/wire.go
//go:build wireinject
// +build wireinject
package di
import (
"context"
"github.com/google/wire"
"github.com/kittichanr/go-wire/greeter"
)
func InitializeApplication(ctx context.Context) (greeter.Event, func(), error) {
wire.Build(greeter.MainBindingSet)
return greeter.Event{}, func() {}, nil
}
จำเป็นต้องมี comment go:build
และ +build
เพื่อเวลารัน cli ของ wire แล้วมันมา generate จากฟังก์ชั่นใน file นี้
// go:build wireinject
// +build wireinject
- ใน
main.go
เรียก function injector มาใช้เพื่อ init ฟังก์ชั่นตอน run application
// main.go
package main
import (
"context"
"log"
"github.com/kittichanr/go-wire/di"
)
func main() {
ctx := context.Background()
_, cleanUpFn, err := di.InitializeApplication(ctx)
defer cleanUpFn()
if err != nil {
log.Fatal("initial app failed")
panic(err)
}
}
- เรียกคำสั่ง
wire ./di
ที่ terminal เพื่อ generate function provider ทั้งหมดที่อยู่ใน injector มาทำงานร่วมกันจะได้ไฟล์di/wire_gen.go
มีหน้าตาประมาณนี้
// 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 di
import (
"context"
"github.com/kittichanr/go-wire/greeter"
)
// Injectors from wire.go:
func InitializeApplication(ctx context.Context) (greeter.Event, func(), error) {
message := greeter.NewMessage()
greeterGreeter := greeter.NewGreeter(message)
event := greeter.NewEvent(greeterGreeter)
return event, func() {
}, nil
}
Note: ไฟล์ที่ generate อันนี้ ห้ามแก้ ต้องไปแก้ที่ฟังก์ชั่น provider และเรียกคำสั่ง wire
อีกทีเพื่อ generate ใหม่
สังเกตว่าไฟล์ wire_gen.go
จะ inject function provider ทั้งหมดไว้ให้โดยที่เราไม่ต้องมา inject เอง เย้!!
Conclusion
go wire เป็น tool ที่ช่วยในการ generate code ให้ function ที่เป็น dependencies injection ให้สามารถทำงานร่วมกันได้ทุกฟังก์ชั่น ทำให้เราไม่ต้องเรียกทุกฟังก์ชั่นมา inject เอง และมั่นใจได้ว่าฟังก์ชั่นที่ inject ทุกอันจะทำงานเรียงตามลำดับอย่างถูกต้อง ซึ่งในตัวอย่างเป็นแค่การทำ go wire เบื้องต้นสามารถไปดูเพิ่มเติมได้ที่ go wire
source code example: https://github.com/kittichanr/go-wire-basic
Reference
- https://github.com/google/wire?tab=readme-ov-file
- https://github.com/google/wire/blob/main/docs/guide.md
- https://github.com/google/wire/blob/main/_tutorial/README.md
- https://medium.com/odds-team/%E0%B8%97%E0%B8%B3-dependency-injection-%E0%B9%83%E0%B8%99-go-%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-wire-ca0fc656c286
- https://medium.com/@leelorz6/dependency-injection-%E0%B8%84%E0%B8%B7%E0%B8%AD%E0%B8%AD%E0%B8%B0%E0%B9%84%E0%B8%A3-6a1a8a2996be