Wire
Wire adalah alat dependency injection open source dari Google konsep dependency injection ini sangat populer di framework Spring Java di Go juga ada beberapa library dependency injection seperti dig open source dari Uber Namun dependency injection wire tidak berdasarkan mekanisme refleksi bahasa secara ketat wire sebenarnya adalah generator kode konsep dependency injection hanya tercermin dalam penggunaan jika ada masalah dapat ditemukan selama periode generasi kode.
Alamat Repositori: google/wire: Compile-time Dependency Injection for Go (github.com)
Alamat Dokumentasi: wire/docs/guide.md at main · google/wire (github.com)
Instalasi
Instal alat generator kode
go install github.com/google/wire/cmd/wire@latestInstal dependensi source code
go get github.com/google/wireMemulai
Dependency injection di wire berdasarkan dua elemen provider dan injector.
provider dapat berupa constructor yang disediakan developer seperti berikut Provider harus terbuka untuk eksternal.
package foobarbaz
type Foo struct {
X int
}
// Constructor Foo
func ProvideFoo() Foo {
return Foo{X: 42}
}Dengan parameter
package foobarbaz
// ...
type Bar struct {
X int
}
// ProvideBar returns a Bar: a negative Foo.
func ProvideBar(foo Foo) Bar {
return Bar{X: -foo.X}
}Juga dapat memiliki parameter dan return value
package foobarbaz
import (
"context"
"errors"
)
type Baz struct {
X int
}
// ProvideBaz returns a value if Bar is not zero.
func ProvideBaz(ctx context.Context, 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
}Juga dapat mengombinasikan provider
package foobarbaz
import (
// ...
"github.com/google/wire"
)
// ...
var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)TIP
Wire memiliki ketentuan berikut untuk return value provider
- Return value pertama adalah nilai yang disediakan provider
- Return value kedua harus berupa
func() | error - Return value ketiga jika return value kedua adalah
funcmaka return value ketiga haruserror
injector adalah fungsi yang dihasilkan oleh wire bertanggung jawab untuk memanggil provider sesuai urutan yang ditentukan signature injector didefinisikan oleh developer wire menghasilkan body fungsi spesifik dengan memanggil wire.Build untuk mendeklarasikan deklarasi ini tidak boleh dipanggil lebih-lebih lagi dikompilasi.
func Build(...interface{}) string {
return "implementation not generated, run wire"
}// +build wireinject
// Build tag memastikan stub tidak dibangun dalam build final.
package main
import (
"context"
"github.com/google/wire"
"example.com/foobarbaz"
)
// injector yang didefinisikan
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
wire.Build(foobarbaz.MegaSet)
return foobarbaz.Baz{}, nil
}Kemudian jalankan
wireakan menghasilkan wire_gen.go konten sebagai berikut
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//+build !wireinject
package main
import (
"example.com/foobarbaz"
)
// injector yang sebenarnya dihasilkan
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
foo := foobarbaz.ProvideFoo()
bar := foobarbaz.ProvideBar(foo)
baz, err := foobarbaz.ProvideBaz(ctx, bar)
if err != nil {
return foobarbaz.Baz{}, err
}
return baz, nil
}Kode yang dihasilkan hampir tidak memiliki dependensi pada wire dapat bekerja normal tanpa wire juga dan selanjutnya jalankan go generate dapat menghasilkan lagi setelahnya developer memanggil injector yang sebenarnya dihasilkan dengan mengirimkan parameter yang sesuai untuk menyelesaikan dependency injection. Apakah seluruh proses kode cukup sederhana terasa seperti hanya menyediakan beberapa constructor kemudian menghasilkan fungsi yang memanggil constructor akhirnya memanggil fungsi ini dengan mengirimkan parameter sepertinya tidak melakukan hal yang terlalu rumit menulis tangan juga bisa benar begitu wire hanya melakukan hal sederhana ini hanya saja dari menulis tangan menjadi otomatis generate. Menurut filosofi wire dependency injection seharusnya memang sesederhana ini tidak perlu dikomplekskan.
Contoh
Berikut melalui studi kasus untuk memperdalam pemahaman ini adalah contoh inisialisasi app.
HttpServer provider menerima parameter net.Addr mengembalikan pointer dan error
var ServerProviderSet = wire.NewSet(NewHttpserver)
type HttpServer struct {
net.Addr
}
func NewHttpserver(addr net.Addr) (*HttpServer, error) {
return &HttpServer{addr}, nil
}MysqlClient dan System provider berikut sama
var DataBaseProviderSet = wire.NewSet(NewMysqlClient)
type MysqlClient struct {
}
var SystemSet = wire.NewSet(NewApp)
type System struct {
server *HttpServer
data *MysqlClient
}
func (s *System) Run() {
log.Printf("app run on %s", s.server.String())
}
func NewApp(server *HttpServer, data *MysqlClient) (System, error) {
return System{server: server, data: data}, nil
}Setelah provider didefinisikan perlu mendefinisikan injector sebaiknya buat file wire.go baru untuk mendefinisikan
//go:build wireinject
// +build wireinject
package main
import (
"github.com/google/wire"
"net"
)
// mendefinisikan injector
func initSystemServer(serverAddr net.Addr, dataAddr string) (System, error) {
// memanggil provider sesuai urutan
panic(wire.Build(DataBaseProviderSet, ServerProviderSet, SystemSet))
}+build wireinject untuk mengabaikan injector ini saat kompilasi. Kemudian jalankan perintah berikut jika output berikut berarti berhasil generate.
$ wire
$ wire: golearn: wrote /golearn/wire_gen.goKode setelah generate sebagai berikut
// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package main
import (
"net"
)
// Injectors from wire.go:
// mendefinisikan injector
func initSystemServer(serverAddr net.Addr, dataAddr string) (System, error) {
httpServer, err := NewHttpserver(serverAddr)
if err != nil {
return System{}, err
}
mysqlClient, err := NewMysqlClient(dataAddr)
if err != nil {
return System{}, err
}
system, err := NewApp(httpServer, mysqlClient)
if err != nil {
return System{}, err
}
return system, nil
}Dapat dilihat logika sangat jelas urutan pemanggilan juga benar terakhir melalui injector yang dihasilkan untuk menjalankan app.
package main
import (
"github.com/google/wire"
"log"
"net"
"net/netip"
)
func main() {
server, err := initSystemServer(
net.TCPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:8080")),
"mysql:localhost:3306/test")
if err != nil {
panic(err)
}
server.Run()
}Output terakhir sebagai berikut
2023/08/01 19:20:48 app run on 127.0.0.1:8080Ini adalah contoh penggunaan yang sangat sederhana.
Penggunaan Lanjutan
Binding Interface
Kadang-kadang saat dependency injection akan menginjeksikan implementasi konkret ke interface. Wire saat dependency injection berdasarkan pencocokan tipe.
type Fooer interface {
Foo() string
}
type MyFooer string
func (b *MyFooer) Foo() string {
return string(*b)
}
func provideMyFooer() *MyFooer {
b := new(MyFooer)
*b = "Hello, World!"
return b
}
type Bar string
func provideBar(f Fooer) string {
// f akan menjadi *MyFooer.
return f.Foo()
}Parameter provider provideBar adalah tipe interface sebenarnya adalah *MyFooer agar saat generate kode provider dapat mencocokan dengan benar kita dapat mengikat dua tipe tersebut sebagai berikut
Parameter pertama adalah tipe pointer interface konkret parameter kedua adalah tipe pointer implementasi konkret.
func Bind(iface, to interface{}) Bindingvar Set = wire.NewSet(
provideMyFooer,
wire.Bind(new(Fooer), new(*MyFooer)),
provideBar)Binding Value
Saat menggunakan wire.Build tidak perlu provider menyediakan nilai juga dapat menggunakan wire.Value untuk menyediakan nilai konkret. wire.Value mendukung ekspresi untuk constructing nilai ekspresi ini akan disalin ke injector saat generate kode sebagai berikut.
type Foo struct {
X int
}
func injectFoo() Foo {
wire.Build(wire.Value(Foo{X: 42}))
return Foo{}
}Injector yang dihasilkan
func injectFoo() Foo {
foo := _wireFooValue
return foo
}
var (
_wireFooValue = Foo{X: 42}
)Jika ingin mengikat nilai tipe interface dapat menggunakan wire.InterfaceValue
func injectReader() io.Reader {
wire.Build(wire.InterfaceValue(new(io.Reader), os.Stdin))
return nil
}Constructor Struct
Dalam providerset dapat menggunakan wire.Struct untuk menggunakan return value provider lain membangun struktur tipe yang ditentukan.
Parameter pertama harus mengirimkan tipe pointer struktur berikutnya adalah nama field.
func Struct(structType interface{}, fieldNames ...string) StructProviderContoh sebagai berikut
type Foo int
type Bar int
func ProvideFoo() Foo {/* ... */}
func ProvideBar() Bar {/* ... */}
type FooBar struct {
MyFoo Foo
MyBar Bar
}
var Set = wire.NewSet(
ProvideFoo,
ProvideBar,
wire.Struct(new(FooBar), "MyFoo", "MyBar"))
func injectFooBar() FoodBar {
wire.Build(Set)
}Injector yang dihasilkan mungkin seperti berikut
func injectFooBar() FooBar {
foo := ProvideFoo()
bar := ProvideBar()
fooBar := FooBar{
MyFoo: foo,
MyBar: bar,
}
return fooBar
}Jika ingin mengisi semua field dapat menggunakan * misalnya
wire.Struct(new(FooBar), "*")Default adalah constructing tipe struktur jika ingin constructing tipe pointer dapat memodifikasi return value signature injector
func injectFooBar() *FoodBar {
wire.Build(Set)
}Jika ingin mengabaikan field dapat menambahkan tag sebagai berikut
type Foo struct {
mu sync.Mutex `wire:"-"`
Bar Bar
}Cleanup
Jika nilai yang di-construct provider perlu pekerjaan收尾 setelah digunakan (seperti menutup file) provider dapat mengembalikan closure untuk melakukan operasi seperti ini injector tidak akan memanggil fungsi cleanup ini kapan memanggilnya diserahkan kepada caller injector sebagai berikut.
type Data struct {
// TODO wrapped database client
}
// NewData .
func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) {
cleanup := func() {
log.NewHelper(logger).Info("closing the data resources")
}
return &Data{}, cleanup, nil
}Kode yang sebenarnya dihasilkan mungkin seperti berikut
func wireApp(confData *conf.Data, logger log.Logger) (func(), error) {
dataData, cleanup, err := data.NewData(confData, logger)
if err != nil {
return nil, nil, err
}
// inject data
// ...
return app, func() {
cleanup()
}, nil
}Tipe Duplikat
Parameter input provider sebaiknya tidak duplikat terutama untuk tipe dasar
type FooBar struct {
foo string
bar string
}
func NewFooBar(foo string, bar string) FooBar {
return FooBar{
foo: foo,
bar: bar,
}
}
func InitializeFooBar(a string, b string) FooBar {
panic(wire.Build(NewFooBar))
}Dalam kasus ini generate kode akan error
provider has multiple parameters of type stringWire tidak dapat membedakan bagaimana menginjeksikan parameter ini untuk menghindari konflik dapat menggunakan alias tipe.
