Skip to content

二、核心概念铺垫 ​

在深入源码之前,我们需要了解一些核心概念。这些概念对于理解 Kratos 的设计至关重要。

2.1 什么是依赖注入? ​

2.1.1 生活中的比喻 ​

想象一下,你要去餐厅吃饭:

传统方式(自己 new):

go
// 你自己去做饭
type Chef struct{}

func (c *Chef) Cook(food string) {
    println("烹饪:", food)
}

// 点菜
chef := new(Chef)
chef.Cook("宫保鸡丁")

依赖注入(餐厅模式):

go
// 餐厅提供厨师,你只需要点菜
type Restaurant struct {
    chef *Chef  // 厨师是餐厅"注入"给你的
}

func (r *Restaurant) Order(food string) {
    r.chef.Cook(food)  // 餐厅帮你安排
}

// 去餐厅吃饭
restaurant := &Restaurant{chef: &Chef{}}
restaurant.Order("宫保鸡丁")

2.1.2 代码中的依赖注入 ​

没有依赖注入:

go
type UserService struct {
    db *sql.DB  // 自己创建
}

func NewUserService() *UserService {
    db, _ := sql.Open("mysql", "...")
    return &UserService{db: db}
}

// 问题:测试困难,无法替换数据库

使用依赖注入:

go
type UserService struct {
    db *sql.DB  // 由外部注入
}

func NewUserService(db *sql.DB) *UserService {
    return &UserService{db: db}
}

// 测试时,可以传入 mock 数据库
func TestUserService() {
    mockDB := &MockDB{}  // 模拟数据库
    service := NewUserService(mockDB)  // 注入
    // 测试逻辑...
}

2.1.3 依赖注入的优势 ​

┌─────────────────────────────────────────────────────────────┐
│                   依赖注入的优势                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 降低耦合度                                             │
│     └── 组件之间不直接依赖,通过接口解耦                    │
│                                                             │
│  2. 提高可测试性                                            │
│     └── 轻松替换为 mock 对象进行单元测试                    │
│                                                             │
│  3. 提高代码复用                                            │
│     └── 同一组件可以在不同场景复用                          │
│                                                             │
│  4. 更好地面向接口编程                                      │
│     └── 依赖抽象而非具体实现                                │
│                                                             │
│  5. 集中管理依赖                                            │
│     └── 配置文件或容器统一管理依赖创建                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.2 什么是依赖注入容器? ​

2.2.1 概念解释 ​

依赖注入容器就像一个大管家,帮你管理所有的依赖对象:

┌─────────────────────────────────────────────┐
│              依赖注入容器                     │
│                                             │
│   ┌─────────────┐  ┌─────────────┐        │
│   │  UserRepo   │  │  OrderRepo  │        │
│   │  (用户仓库)  │  │  (订单仓库)  │        │
│   └──────┬──────┘  └──────┬──────┘        │
│          │                │                │
│          ▼                ▼                │
│   ┌─────────────────────────────────┐     │
│   │         ServiceContext          │     │
│   │    (服务上下文,统一管理)        │     │
│   │                                 │     │
│   │   - UserRepo                   │     │
│   │   - OrderRepo                  │     │
│   │   - Redis                      │     │
│   │   - Config                     │     │
│   └─────────────────────────────────┘     │
│                    │                        │
│                    ▼                        │
│   ┌─────────────────────────────────┐     │
│   │         Handler/Logic           │     │
│   │      (业务逻辑层使用)            │     │
│   │                                 │     │
│   │   func GetUser(c *Context) {   │     │
│   │       c.svc.UserRepo.Find()   │     │
│   │   }                            │     │
│   └─────────────────────────────────┘     │
└─────────────────────────────────────────────┘

2.2.2 容器的工作流程 ​

┌─────────────────────────────────────────────────────────────┐
│                   容器工作流程                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 注册阶段                                                │
│     └── 告诉容器:我需要这些依赖                             │
│     ┌─────────────────────────────────────────┐            │
│     │  container.Register(&UserRepo{})         │            │
│     │  container.Register(&OrderRepo{})       │            │
│     │  container.Register(&Redis{})            │            │
│     └─────────────────────────────────────────┘            │
│                          │                                   │
│                          ▼                                   │
│  2. 解析阶段                                                │
│     └── 容器自动创建依赖并组装                               │
│     ┌─────────────────────────────────────────┐            │
│     │  userRepo := container.Resolve()         │            │
│     │  orderRepo := container.Resolve()         │            │
│     └─────────────────────────────────────────┘            │
│                          │                                   │
│                          ▼                                   │
│  3. 使用阶段                                                │
│     └── 业务代码直接使用注入的对象                           │
│     ┌─────────────────────────────────────────┐            │
│     │  handler := &Handler{                   │            │
│     │      userRepo: userRepo,                │            │
│     │      orderRepo: orderRepo,              │            │
│     │  }                                      │            │
│     └─────────────────────────────────────────┘            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.3 什么是 Wire? ​

2.3.1 Wire 简介 ​

Wire 是 Google 开发的编译时依赖注入工具,它的特点是:

┌─────────────────────────────────────────────────────────────┐
│                      Wire 特点                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ✓ 编译时检查                                              │
│     └── 在编译时就检查依赖关系是否正确                       │
│                                                             │
│  ✓ 零运行时开销                                             │
│     └── 生成的代码就是普通的函数调用                         │
│                                                             │
│  ✓ 类型安全                                                 │
│     └── 编译时确保类型匹配                                   │
│                                                             │
│  ✓ 易于理解                                                │
│     └── 代码即配置,所见即所得                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.3.2 Wire 使用示例 ​

1. 定义 Provider(提供者)

go
// provider.go

// 数据库 Provider
func NewDB(conf *Config) (*sql.DB, error) {
    return sql.Open("mysql", conf.Database.DSN)
}

// Redis Provider
func NewRedis(conf *Config) (*redis.Client, error) {
    return redis.New(conf.Redis.Addr), nil
}

// 用户仓库 Provider
func NewUserRepo(db *sql.DB) *UserRepo {
    return &UserRepo{db: db}
}

// 用户服务 Provider
func NewUserService(repo *UserRepo) *UserService {
    return &UserService{repo: repo}
}

2. 编写 Wire 指令

go
// wire.go
//go:build wireinject
// +build wireinject

func InitializeApp(conf *Config) (*App, error) {
    // 声明依赖关系
    wire.Build(
        NewDB,
        NewRedis,
        NewUserRepo,
        NewUserService,
    )
    return nil, nil
}

3. 自动生成代码

Wire 会自动生成 wire_gen.go:

go
// wire_gen.go (自动生成)
func InitializeApp(conf *Config) (*App, error) {
    db, err := NewDB(conf)
    if err != nil {
        return nil, err
    }
    
    redis, err := NewRedis(conf)
    if err != nil {
        return nil, err
    }
    
    userRepo := NewUserRepo(db)
    userService := NewUserService(userRepo)
    
    return &App{
        userService: userService,
        redis:      redis,
    }, nil
}

2.3.3 Wire 工作原理图解 ​

┌─────────────────────────────────────────────────────────────┐
│                    Wire 工作原理                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  源代码 (wire.go)                                          │
│  ┌─────────────────────────────────────────┐              │
│  │ func InitializeApp() {                  │              │
│  │   wire.Build(                           │              │
│  │     NewDB,                               │              │
│  │     NewRedis,                            │              │
│  │     NewUserRepo,                         │              │
│  │     NewUserService,                      │              │
│  │   )                                      │              │
│  │ }                                        │              │
│  └─────────────────────────────────────────┘              │
│           │                                                │
│           ▼  go generate ./...                           │
│  ┌─────────────────────────────────────────┐              │
│  │  Wire 分析依赖关系:                       │              │
│  │                                         │              │
│  │  UserService → UserRepo → DB             │              │
│  │  UserService → UserRepo → DB             │              │
│  │  UserRepo → DB                           │              │
│  │  Redis                                  │              │
│  └─────────────────────────────────────────┘              │
│           │                                                │
│           ▼  自动生成                                      │
│  ┌─────────────────────────────────────────┐              │
│  │  wire_gen.go (生成代码)                  │              │
│  │                                         │              │
│  │  func InitializeApp() (*App, error) {    │              │
│  │      db := NewDB(conf)                 │              │
│  │      redis := NewRedis(conf)            │              │
│  │      userRepo := NewUserRepo(db)        │              │
│  │      userService := NewUserService(...) │              │
│  │      return &App{...}, nil              │              │
│  │  }                                      │              │
│  └─────────────────────────────────────────┘              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.4 Kratos 中的依赖注入 ​

2.4.1 Kratos 使用 Wire 的方式 ​

go
// kratos 项目的 wire.go
//go:build wireinject
// +build wireinject

func newApp(*conf.Config, *http.Server, *grpc.Server) (*kratos.App, error) {
    wire.Build(
        kratos.New,
        http.NewServer,
        grpc.NewServer,
        NewUserRepo,
        NewUserService,
    )
    return nil, nil
}

2.4.2 ServiceContext(服务上下文) ​

Kratos 使用 ServiceContext 统一管理所有依赖:

go
// service.go
type ServiceContext struct {
    // 数据库
    UserRepo model.UserRepo
    OrderRepo model.OrderRepo
    
    // 缓存
    Redis *redis.Client
    
    // 配置
    Config *conf.Config
}

// 在 Handler 中使用
func (l *LoginLogic) Login() error {
    // 通过 ServiceContext 获取依赖
    user, err := l.svcCtx.UserRepo.FindByUsername(l.req.Username)
    if err != nil {
        return err
    }
    // ...
}

2.4.3 完整示例 ​

┌─────────────────────────────────────────────────────────────┐
│              Kratos 依赖注入流程                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 定义 Provider                                           │
│     ┌─────────────────────────────────────────┐            │
│     │  func NewUserRepo(db *sql.DB) *UserRepo │            │
│     │  func NewUserService(repo *UserRepo)     │            │
│     └─────────────────────────────────────────┘            │
│                          │                                   │
│                          ▼                                   │
│  2. Wire 指令                                             │
│     ┌─────────────────────────────────────────┐            │
│     │  wire.Build(                             │            │
│     │    NewUserRepo,                          │            │
│     │    NewUserService,                       │            │
│     │  )                                      │            │
│     └─────────────────────────────────────────┘            │
│                          │                                   │
│                          ▼                                   │
│  3. 生成代码                                               │
│     ┌─────────────────────────────────────────┐            │
│     │  userRepo := NewUserRepo(db)             │            │
│     │  userService := NewUserService(userRepo) │            │
│     └─────────────────────────────────────────┘            │
│                          │                                   │
│                          ▼                                   │
│  4. 使用                                                   │
│     ┌─────────────────────────────────────────┐            │
│     │  svcCtx := &ServiceContext{              │            │
│     │      UserRepo: userRepo,                 │            │
│     │  }                                      │            │
│     │                                         │            │
│     │  handler := &Handler{                   │            │
│     │      svcCtx: svcCtx,                    │            │
│     │  }                                      │            │
│     └─────────────────────────────────────────┘            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.5 本章小结 ​

┌─────────────────────────────────────────────────────────────┐
│                      本章总结                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ✓ 依赖注入是一种设计模式                                   │
│     └── 由外部注入依赖,而非内部创建                          │
│                                                             │
│  ✓ 依赖注入容器统一管理依赖创建                              │
│     └── 提高代码可测试性和可维护性                            │
│                                                             │
│  ✓ Wire 是编译时依赖注入工具                                │
│     └── 编译时检查,零运行时开销                            │
│                                                             │
│  ✓ Kratos 使用 Wire 进行依赖注入                           │
│     └── 通过 ServiceContext 统一管理                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

下章预告 ​

下一章我们将学习整体架构,包括:

  • Kratos 的模块划分
  • 核心数据结构
  • 各层之间的关系

敬请期待!