博客 / 詳情

返回

使用 Gozz 進行自動化的依賴注入和AOP代理

Gozz 是一個十分強大的註解分析及模板化代碼生成工具

依賴注入和AOP是其內置插件中提供的強大功能之一。

這是筆者在以往工作中在多個團隊成功落地,已經使用了4年以上的成熟方案,最近終於有時間進行開源。

這裏搬運一個 中文文檔 提供的示例二 比如下面是一個很常見的應用 基本層級代碼

package overview02

import (
    "context"
    "database/sql"
    "fmt"
    "net/http"
    "time"

    "github.com/go-redis/redis/v8"
)

type (
    // root config for unmarshal config file
    Config struct {
        Server ServerConfig `yaml:"server"`
        Sql    SqlConfig    `yaml:"sql"`
        Redis  RedisConfig  `yaml:"redis"`
    }

    // http server config
    ServerConfig struct {
        Addr string `yaml:"addr"`
    }

    // sql config
    SqlConfig struct {
        Dsn string `yaml:"dsn"`
    }

    // redis config
    RedisConfig struct {
        Host string `yaml:"host"`
        Port string `yaml:"port"`
    }
)

// provide http server from server config
func ProvideHttpServer(config ServerConfig) *http.Server {
    return &http.Server{
        Addr: config.Addr,
    }
}

// interface of sql connection
type SqlConn interface {
    QueryContext(ctx context.Context, statement string, args ...interface{}) (rows *sql.Rows, err error)
}

// interface of key value store
type Store interface {
    Get(ctx context.Context, key string) (value []byte, err error)
    Set(ctx context.Context, key string, value []byte, exp time.Duration) (err error)
}

// provide sql connection from sql config
func ProvideSql(config SqlConfig) (*sql.DB, error) {
    return sql.Open("mysql", config.Dsn)
}

// provide kv store from redis config
func ProvideRedisStore(config RedisConfig) (*redis.Client, error) {
    rdb := redis.NewClient(&redis.Options{
        Addr: fmt.Sprintf("%s:%s", config.Host, config.Port),
    })
    return rdb, nil
}

type RedisStore struct {
    redis.Cmdable
}

func (s RedisStore) Set(ctx context.Context, key string, value []byte, exp time.Duration) (err error) {
    return s.Cmdable.Set(ctx, key, value, exp).Err()
}

func (s RedisStore) Get(ctx context.Context, key string) (value []byte, err error) {
    return s.Cmdable.Get(ctx, key).Bytes()
}

// biz service handler
type ServiceHandler interface {
    GetInt(ctx context.Context) (int, error)
    GetString(ctx context.Context) (string, error)
}

// implement of server handler
type ServerHandlerImpl struct {
    Sql   SqlConn
    Store Store
}

func (impl *ServerHandlerImpl) GetInt(ctx context.Context) (int, error) {
    panic("not implemented")
}

func (impl *ServerHandlerImpl) GetString(ctx context.Context) (string, error) {
    panic("not implemented")
}

// the entry of application
type Application interface {
    Run()
}

// web application implement
type application struct {
    Server  *http.Server
    Handler ServiceHandler
}

func (application application) Run() {
    panic("not implemented")
}

我們只需要在對象的註釋中加上一些註解 然後運行 gozz 工具 注意 +zz:wire 這樣的聲明

package overview02

import (
    "context"
    "database/sql"
    "fmt"
    "net/http"
    "time"

    "github.com/go-redis/redis/v8"
)

//go:generate gozz run -p "wire" ./

type (
    // root config for unmarshal config file
    // +zz:wire:field=*
    Config struct {
        Server ServerConfig `yaml:"server"`
        Sql    SqlConfig    `yaml:"sql"`
        Redis  RedisConfig  `yaml:"redis"`
    }

    // http server config
    ServerConfig struct {
        Addr string `yaml:"addr"`
    }

    // sql config
    SqlConfig struct {
        Dsn string `yaml:"dsn"`
    }

    // redis config
    RedisConfig struct {
        Host string `yaml:"host"`
        Port string `yaml:"port"`
    }
)

// provide http server from server config
// +zz:wire
func ProvideHttpServer(config ServerConfig) *http.Server {
    return &http.Server{
        Addr: config.Addr,
    }
}

// interface of sql connection
type SqlConn interface {
    QueryContext(ctx context.Context, statement string, args ...interface{}) (rows *sql.Rows, err error)
}

// interface of key value store
type Store interface {
    Get(ctx context.Context, key string) (value []byte, err error)
    Set(ctx context.Context, key string, value []byte, exp time.Duration) (err error)
}

// provide sql connection from sql config
// +zz:wire:bind=SqlConn
func ProvideSql(config SqlConfig) (*sql.DB, error) {
    return sql.Open("mysql", config.Dsn)
}

// provide kv store from redis config
// +zz:wire:bind=redis.Cmdable
func ProvideRedisStore(config RedisConfig) (*redis.Client, error) {
    rdb := redis.NewClient(&redis.Options{
        Addr: fmt.Sprintf("%s:%s", config.Host, config.Port),
    })
    return rdb, nil
}

// +zz:wire:bind=Store
type RedisStore struct {
    redis.Cmdable
}

func (s RedisStore) Set(ctx context.Context, key string, value []byte, exp time.Duration) (err error) {
    return s.Cmdable.Set(ctx, key, value, exp).Err()
}

func (s RedisStore) Get(ctx context.Context, key string) (value []byte, err error) {
    return s.Cmdable.Get(ctx, key).Bytes()
}

// biz service handler
type ServiceHandler interface {
    GetInt(ctx context.Context) (int, error)
    GetString(ctx context.Context) (string, error)
}

// implement of server handler
// +zz:wire:bind=ServiceHandler:aop
type ServerHandlerImpl struct {
    Sql   SqlConn
    Store Store
}

func (impl *ServerHandlerImpl) GetInt(ctx context.Context) (int, error) {
    panic("not implemented")
}

func (impl *ServerHandlerImpl) GetString(ctx context.Context) (string, error) {
    panic("not implemented")
}

// the entry of application
// +zz:wire:inject=./:param=*Config
type Application interface {
    Run()
}

// web application implement
// +zz:wire:bind=Application
type application struct {
    Server  *http.Server
    Handler ServiceHandler
}

func (application application) Run() {
    panic("not implemented")
}

你會發現完整的依賴注入代碼就被生成好了:

// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//+build !wireinject

package overview02

// Injectors from wire_zinject.go:

// github.com/go-zing/gozz-doc-examples/overview02.Application
func Initialize_Application(config *Config) (Application, func(), error) {
    serverConfig := config.Server
    server := ProvideHttpServer(serverConfig)
    sqlConfig := config.Sql
    db, err := ProvideSql(sqlConfig)
    if err != nil {
        return nil, nil, err
    }
    redisConfig := config.Redis
    client, err := ProvideRedisStore(redisConfig)
    if err != nil {
        return nil, nil, err
    }
    redisStore := &RedisStore{
        Cmdable: client,
    }
    serverHandlerImpl := &ServerHandlerImpl{
        Sql:   db,
        Store: redisStore,
    }
    overview02_impl_aop_ServiceHandler := &_impl_aop_ServiceHandler{
        _aop_ServiceHandler: serverHandlerImpl,
    }
    overview02Application := &application{
        Server:  server,
        Handler: overview02_impl_aop_ServiceHandler,
    }
    return overview02Application, func() {
    }, nil
}

而且還可以生成自動化注入的 AOP 代理:

// Code generated by gozz:wire github.com/go-zing/gozz. DO NOT EDIT.

package overview02

import (
    "context"
)

type _aop_interceptor interface {
    Intercept(v interface{}, name string, params, results []interface{}) (func(), bool)
}

// ServiceHandler
type (
    _aop_ServiceHandler      ServiceHandler
    _impl_aop_ServiceHandler struct{ _aop_ServiceHandler }
)

func (i _impl_aop_ServiceHandler) GetInt(p0 context.Context) (r0 int, r1 error) {
    if t, x := i._aop_ServiceHandler.(_aop_interceptor); x {
        if up, ok := t.Intercept(i._aop_ServiceHandler, "GetInt",
            []interface{}{&p0},
            []interface{}{&r0, &r1},
        ); up != nil {
            defer up()
        } else if !ok {
            return
        }
    }
    return i._aop_ServiceHandler.GetInt(p0)
}

func (i _impl_aop_ServiceHandler) GetString(p0 context.Context) (r0 string, r1 error) {
    if t, x := i._aop_ServiceHandler.(_aop_interceptor); x {
        if up, ok := t.Intercept(i._aop_ServiceHandler, "GetString",
            []interface{}{&p0},
            []interface{}{&r0, &r1},
        ); up != nil {
            defer up()
        } else if !ok {
            return
        }
    }
    return i._aop_ServiceHandler.GetString(p0)
}

Gozz 還提供了運行時庫的工具 ,可以協助我們對對象進行運行時分析,並輸出架構圖

圖片

Gozz 還提供了一系列強大的內置插件:

  • Api 基於 interface 快速提供 API接口
  • Doc 基於註釋生成運行時文檔
  • Impl 同步 interfaceimplement
  • Option 生成 Funcitonal Options 風格代碼
  • Orm 生成數據實體映射代碼
  • Tag 模版化管理結構體字段標籤
  • Wire 自動化依賴注入 及 AOP代理

更多有趣的使用可以訪問 Github 探索,也歡迎提 Issue 和 Star

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.