二、核心概念铺垫
在深入源码之前,我们需要了解一些核心概念。这些概念对于理解 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 的模块划分
- 核心数据结构
- 各层之间的关系
敬请期待!