golang基于泛型的依赖注入框架插件do的使用

Golang基于泛型的依赖注入框架插件do的使用

简介

do是一个基于Go 1.18+泛型的依赖注入工具包,实现了依赖注入设计模式。它可以替代uber/dig包在简单的Go项目中使用,并提供了类型安全的API。

特性

  • 服务注册
  • 服务调用
  • 服务健康检查
  • 服务关闭
  • 服务生命周期钩子
  • 命名或匿名服务
  • 立即或延迟加载服务
  • 依赖图解析
  • 默认注入器
  • 注入器克隆
  • 服务覆盖
  • 轻量级,无依赖
  • 无需代码生成

安装

go get github.com/samber/do@v1

快速开始

基本示例

import "github.com/samber/do"

func main() {
    injector := do.New()

    // 提供CarService
    do.Provide(injector, NewCarService)

    // 提供EngineService
    do.Provide(injector, NewEngineService)

    car := do.MustInvoke[*CarService](injector)
    car.Start()
    // 输出: "car starting"

    do.HealthCheck[EngineService](injector)
    // 返回: "engine broken"

    injector.Shutdown()
    // 输出: "car stopped"
}

服务定义

type EngineService interface{}

func NewEngineService(i *do.Injector) (EngineService, error) {
    return &engineServiceImplem{}, nil
}

type engineServiceImplem struct {}

// [可选] 实现do.Healthcheckable接口
func (c *engineServiceImplem) HealthCheck() error {
    return fmt.Errorf("engine broken")
}

func NewCarService(i *do.Injector) (*CarService, error) {
    engine := do.MustInvoke[EngineService](i)
    car := CarService{Engine: engine}
    return &car, nil
}

type CarService struct {
    Engine EngineService
}

func (c *CarService) Start() {
    println("car starting")
}

// [可选] 实现do.Shutdownable接口
func (c *CarService) Shutdown() error {
    println("car stopped")
    return nil
}

详细用法

服务注册

匿名服务,延迟加载

type DBService struct {
    db *sql.DB
}

do.Provide[DBService](injector, func(i *Injector) (*DBService, error) {
    db, err := sql.Open(...)
    if err != nil {
        return nil, err
    }
    return &DBService{db: db}, nil
})

命名服务,延迟加载

do.ProvideNamed(injector, "dbconn", func(i *Injector) (*DBService, error) {
    db, err := sql.Open(...)
    if err != nil {
        return nil, err
    }
    return &DBService{db: db}, nil
})

匿名服务,立即加载

type Config struct {
    uri string
}

do.ProvideValue[Config](injector, Config{uri: "postgres://user:pass@host:5432/db"})

命名服务,立即加载

do.ProvideNamedValue(injector, "configuration", Config{uri: "postgres://user:pass@host:5432/db"})

服务调用

调用匿名服务

dbService, err := do.Invoke[DBService](injector)

调用匿名服务(如果未注册会panic)

dbService := do.MustInvoke[DBService](injector)

调用命名服务

config, err := do.InvokeNamed[Config](injector, "configuration")

调用命名服务(如果未注册会panic)

config := do.MustInvokeNamed[Config](injector, "configuration")

服务覆盖

do.Provide[Vehicle](injector, func (i *do.Injector) (Vehicle, error) {
    return &CarImplem{}, nil
})

do.Override[Vehicle](injector, func (i *do.Injector) (Vehicle, error) {
    return &BusImplem{}, nil
})

生命周期钩子

injector := do.NewWithOpts(&do.InjectorOpts{
    HookAfterRegistration: func(injector *do.Injector, serviceName string) {
        fmt.Printf("Service registered: %s\n", serviceName)
    },
    HookAfterShutdown: func(injector *do.Injector, serviceName string) {
        fmt.Printf("Service stopped: %s\n", serviceName)
    },
    Logf: func(format string, args ...any) {
        log.Printf(format, args...)
    },
})

注入器克隆

func TestService(t *testing.T) {
    i := injector.Clone()
    defer i.Shutdown()

    // 用MockService替换Service
    do.Override[Service](i, func (i *do.Injector) (Service, error) {
        return &MockService{}, nil
    }))

    app := do.Invoke[*App](i)
    // 使用模拟服务进行单元测试
}

总结

do是一个简单而强大的依赖注入框架,特别适合在Go 1.18+项目中使用。它提供了完整的生命周期管理、健康检查和优雅关闭等功能,同时保持了轻量级和类型安全的特点。


更多关于golang基于泛型的依赖注入框架插件do的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang基于泛型的依赖注入框架插件do的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 基于泛型的依赖注入框架 do 使用指南

do 是一个基于 Go 泛型实现的轻量级依赖注入框架,它提供了简单易用的 API 来管理依赖关系。下面我将详细介绍如何使用这个框架。

1. 安装 do 框架

go get github.com/samber/do

2. 基本用法

2.1 创建注入器

package main

import (
	"fmt"
	"github.com/samber/do"
)

func main() {
	injector := do.New()
	
	// 注册服务
	do.Provide(injector, func(i *do.Injector) (*ServiceA, error) {
		return &ServiceA{}, nil
	})
	
	// 获取服务
	serviceA, err := do.Invoke[*ServiceA](injector)
	if err != nil {
		panic(err)
	}
	
	serviceA.DoSomething()
}

type ServiceA struct{}

func (s *ServiceA) DoSomething() {
	fmt.Println("ServiceA is doing something")
}

2.2 依赖注入

package main

import (
	"fmt"
	"github.com/samber/do"
)

func main() {
	injector := do.New()
	
	// 注册服务B
	do.Provide(injector, func(i *do.Injector) (*ServiceB, error) {
		return &ServiceB{}, nil
	})
	
	// 注册服务A,它依赖服务B
	do.Provide(injector, func(i *do.Injector) (*ServiceA, error) {
		serviceB, err := do.Invoke[*ServiceB](i)
		if err != nil {
			return nil, err
		}
		return &ServiceA{serviceB: serviceB}, nil
	})
	
	// 使用服务A
	serviceA, err := do.Invoke[*ServiceA](injector)
	if err != nil {
		panic(err)
	}
	
	serviceA.DoSomething()
}

type ServiceA struct {
	serviceB *ServiceB
}

func (s *ServiceA) DoSomething() {
	fmt.Println("ServiceA is doing something")
	s.serviceB.DoSomethingElse()
}

type ServiceB struct{}

func (s *ServiceB) DoSomethingElse() {
	fmt.Println("ServiceB is doing something else")
}

3. 高级特性

3.1 命名服务

do.ProvideNamed(injector, "logger", func(i *do.Injector) (*Logger, error) {
	return &Logger{}, nil
})

logger, err := do.InvokeNamed[*Logger](injector, "logger")

3.2 单例模式

// 默认情况下,Provide 创建的是单例
do.Provide(injector, func(i *do.Injector) (*Database, error) {
	return ConnectToDatabase()
})

// 每次调用都会返回同一个实例
db1, _ := do.Invoke[*Database](injector)
db2, _ := do.Invoke[*Database](injector)
// db1 == db2

3.3 瞬态服务

// 使用 ProvideValue 可以注册非单例服务
do.ProvideValue(injector, &Config{Env: "dev"})

// 或者使用 Provide 但每次都创建新实例
do.Provide(injector, func(i *do.Injector) (*RequestContext, error) {
	return &RequestContext{CreatedAt: time.Now()}, nil
})

3.4 服务健康检查

do.Provide(injector, func(i *do.Injector) (*Database, error) {
	db := ConnectToDatabase()
	do.AddHealthCheck(i, do.HealthCheck{
		Name: "database",
		Check: func() error {
			return db.Ping()
		},
	})
	return db, nil
})

// 检查所有服务健康状态
status := do.HealthCheck(injector)
fmt.Println(status)

4. 生命周期管理

injector := do.New()

// 关闭时会调用所有服务的 Shutdown 方法(如果实现了 Shutdowner 接口)
defer injector.Shutdown()

type Shutdowner interface {
	Shutdown() error
}

type ServiceC struct{}

func (s *ServiceC) Shutdown() error {
	fmt.Println("ServiceC is shutting down")
	return nil
}

do.Provide(injector, func(i *do.Injector) (*ServiceC, error) {
	return &ServiceC{}, nil
})

5. 实际应用示例

package main

import (
	"fmt"
	"github.com/samber/do"
	"log"
)

func main() {
	// 初始化依赖注入容器
	injector := do.New()
	defer injector.Shutdown()

	// 注册配置
	do.ProvideValue(injector, &Config{DatabaseURL: "postgres://user:pass@localhost:5432/db"})

	// 注册数据库连接
	do.Provide(injector, NewDatabase)

	// 注册用户仓库
	do.Provide(injector, NewUserRepository)

	// 注册用户服务
	do.Provide(injector, NewUserService)

	// 使用服务
	userService, err := do.Invoke[*UserService](injector)
	if err != nil {
		log.Fatal(err)
	}

	user, err := userService.GetUser(1)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("User: %+v\n", user)
}

// 以下是各个组件的实现

type Config struct {
	DatabaseURL string
}

type Database struct {
	conn string
}

func NewDatabase(i *do.Injector) (*Database, error) {
	cfg := do.MustInvoke[*Config](i)
	return &Database{conn: cfg.DatabaseURL}, nil
}

func (db *Database) Query(query string) string {
	return fmt.Sprintf("Result of query '%s' from %s", query, db.conn)
}

type UserRepository struct {
	db *Database
}

func NewUserRepository(i *do.Injector) (*UserRepository, error) {
	db := do.MustInvoke[*Database](i)
	return &UserRepository{db: db}, nil
}

func (r *UserRepository) FindByID(id int) string {
	return r.db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %d", id))
}

type UserService struct {
	repo *UserRepository
}

func NewUserService(i *do.Injector) (*UserService, error) {
	repo := do.MustInvoke[*UserRepository](i)
	return &UserService{repo: repo}, nil
}

func (s *UserService) GetUser(id int) (string, error) {
	return s.repo.FindByID(id), nil
}

总结

do 框架提供了以下主要优势:

  1. 基于 Go 泛型,类型安全
  2. 简单易用的 API
  3. 支持单例和瞬态服务
  4. 内置健康检查
  5. 自动生命周期管理
  6. 支持命名服务

这个框架非常适合中小型 Go 项目,可以帮助你更好地组织代码和管理依赖关系。对于更复杂的场景,你可能需要考虑其他更全功能的 DI 框架,但对于大多数应用来说,do 已经足够强大了。

回到顶部