Skip to content

依赖注入模式

概述

依赖注入(Dependency Injection, DI)是一种将依赖关系从代码内部转移到外部配置的设计模式。本规范定义了基于 App 容器的依赖注入实现方式,适用于 Go Web 应用的 Service 层管理。

核心概念

什么是依赖注入

依赖注入是指组件不自己创建依赖对象,而是通过外部注入的方式获取依赖。

对比示例

go
// ❌ 硬编码依赖(不使用 DI)
type OrderService struct {
    db *gorm.DB
}

func NewOrderService() *OrderService {
    // 在 Service 内部创建数据库连接
    db, _ := gorm.Open(mysql.Open("root:password@/db"), &gorm.Config{})
    return &OrderService{db: db}
}

// ✅ 依赖注入(推荐)
func NewOrderService(db *gorm.DB) *OrderService {
    // 数据库连接由外部传入
    return &OrderService{db: db}
}

DI 的优势

  • 可测试性:可注入 Mock 对象进行单元测试
  • 可配置性:不同环境使用不同的依赖实现
  • 解耦:组件不依赖具体实现,只依赖接口
  • 生命周期管理:统一管理依赖对象的创建和销毁

App 容器模式

设计原理

App 结构体作为依赖注入容器,负责:

  1. 持有所有 Service 实例
  2. 管理 Service 的创建和初始化顺序
  3. 提供 Getter 方法供 Handler 获取 Service
  4. 确保 Service 单例(整个应用生命周期内只有一个实例)

App 结构体定义

go
// app/app.go
package app

import (
    "context"
    "fmt"

    "your-project/config"
    "your-project/services"
    "gorm.io/gorm"
)

// App 依赖注入容器
type App struct {
    // 基础设施
    config *config.Config
    db     *gorm.DB

    // Service 实例
    userService     *services.UserService
    productService  *services.ProductService
    orderService    *services.OrderService
    paymentService  *services.PaymentService
    notifyService   *services.NotifyService
    storageService  *services.StorageService
}

// New 创建 App 实例
func New() *App {
    return &App{}
}

初始化流程

完整初始化方法

go
// Init 初始化应用
func (a *App) Init(ctx context.Context, cfg *config.Config) error {
    a.config = cfg

    // 1. 初始化基础设施(数据库连接)
    if err := a.initDatabase(ctx); err != nil {
        return fmt.Errorf("数据库初始化失败: %w", err)
    }

    // 2. 初始化 Service 层
    a.initServices()

    return nil
}

// initDatabase 初始化数据库连接
func (a *App) initDatabase(ctx context.Context) error {
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        a.config.DB.Username,
        a.config.DB.Password,
        a.config.DB.Host,
        a.config.DB.Port,
        a.config.DB.Database,
    )

    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })
    if err != nil {
        return err
    }

    a.db = db
    return nil
}

// initServices 初始化所有 Service
func (a *App) initServices() {
    // 按依赖顺序初始化 Service
    // 1. 无依赖的基础 Service
    a.storageService = services.NewStorageService(a.config)
    a.notifyService = services.NewNotifyService(a.config)

    // 2. 依赖基础 Service 的业务 Service
    a.userService = services.NewUserService(a.db, a.config)
    a.productService = services.NewProductService(a.db, a.storageService)

    // 3. 依赖多个 Service 的复杂 Service
    a.paymentService = services.NewPaymentService(a.db, a.notifyService)
    a.orderService = services.NewOrderService(
        a.db,
        a.productService,
        a.paymentService,
        a.notifyService,
    )
}

初始化顺序原则

必须按依赖关系顺序初始化

1. 基础设施(db、config、logger)

2. 无依赖的基础 Service(StorageService、NotifyService)

3. 依赖基础 Service 的业务 Service(UserService、ProductService)

4. 依赖多个 Service 的复杂 Service(OrderService)

错误示例

go
func (a *App) initServices() {
    // ❌ 错误:OrderService 依赖 ProductService,但 ProductService 还未初始化
    a.orderService = services.NewOrderService(a.db, a.productService, ...)
    a.productService = services.NewProductService(a.db)
}

Service 依赖注入

Service 构造函数设计

Service 构造函数应接受所有依赖作为参数

go
// services/order.go
package services

import (
    "context"

    "your-project/config"
    "gorm.io/gorm"
)

type OrderService struct {
    db             *gorm.DB
    config         *config.Config
    productService *ProductService
    paymentService *PaymentService
    notifyService  *NotifyService
}

// NewOrderService 构造函数 - 注入所有依赖
func NewOrderService(
    db *gorm.DB,
    productService *ProductService,
    paymentService *PaymentService,
    notifyService *NotifyService,
) *OrderService {
    return &OrderService{
        db:             db,
        productService: productService,
        paymentService: paymentService,
        notifyService:  notifyService,
    }
}

关键点

  • ✅ 构造函数明确声明所有依赖
  • 不在 Service 内部创建依赖对象
  • ✅ 依赖对象由 App 容器传入

跨 Service 调用

Service 通过注入的依赖调用其他 Service

go
// CreateOrder 创建订单
func (s *OrderService) CreateOrder(ctx context.Context, userID, productID uint, quantity int) (*models.Order, error) {
    // 1. 调用 ProductService 检查库存
    product, err := s.productService.GetByID(ctx, productID)
    if err != nil {
        return nil, fmt.Errorf("商品查询失败: %w", err)
    }
    if product.Stock < quantity {
        return nil, errors.New("库存不足")
    }

    // 2. 计算金额
    amount := product.Price * float64(quantity)

    // 3. 调用 PaymentService 创建支付订单
    paymentID, err := s.paymentService.CreatePayment(ctx, amount)
    if err != nil {
        return nil, fmt.Errorf("创建支付失败: %w", err)
    }

    // 4. 创建订单
    order := &models.Order{
        UserID:    userID,
        ProductID: productID,
        Quantity:  quantity,
        Amount:    amount,
        PaymentID: paymentID,
        Status:    "pending",
    }
    if err := s.db.WithContext(ctx).Create(order).Error; err != nil {
        return nil, err
    }

    // 5. 调用 NotifyService 发送通知
    s.notifyService.SendOrderCreated(ctx, order.ID, userID)

    return order, nil
}

Getter 方法

定义 Getter 方法

App 提供 Getter 方法供 Handler 获取 Service

go
// app/app.go

// UserService 返回 UserService 实例
func (a *App) UserService() *services.UserService {
    return a.userService
}

// ProductService 返回 ProductService 实例
func (a *App) ProductService() *services.ProductService {
    return a.productService
}

// OrderService 返回 OrderService 实例
func (a *App) OrderService() *services.OrderService {
    return a.orderService
}

// PaymentService 返回 PaymentService 实例
func (a *App) PaymentService() *services.PaymentService {
    return a.paymentService
}

// NotifyService 返回 NotifyService 实例
func (a *App) NotifyService() *services.NotifyService {
    return a.notifyService
}

Handler 使用 Getter

Handler 通过 App 的 Getter 方法获取 Service

go
// handlers/order.go
package handlers

import (
    "net/http"

    "your-project/app"
    "github.com/labstack/echo/v4"
)

type OrderHandler struct {
    orderService *services.OrderService
}

// NewOrderHandler 创建 Handler 实例
func NewOrderHandler(app *app.App) *OrderHandler {
    return &OrderHandler{
        orderService: app.OrderService(),  // ✅ 通过 Getter 获取 Service
    }
}

// Create 创建订单
func (h *OrderHandler) Create(c echo.Context) error {
    req := new(CreateOrderRequest)
    if err := c.Bind(req); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "参数错误"})
    }

    // 调用 Service
    order, err := h.orderService.CreateOrder(
        c.Request().Context(),
        req.UserID,
        req.ProductID,
        req.Quantity,
    )
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
    }

    return c.JSON(http.StatusCreated, order)
}

资源清理

优雅关闭

App 提供 Close 方法清理资源

go
// Close 清理资源
func (a *App) Close(ctx context.Context) error {
    // 1. 关闭数据库连接
    if a.db != nil {
        sqlDB, err := a.db.DB()
        if err != nil {
            return err
        }
        if err := sqlDB.Close(); err != nil {
            return fmt.Errorf("关闭数据库连接失败: %w", err)
        }
    }

    // 2. 清理其他资源(如连接池、定时任务等)
    // ...

    return nil
}

在应用退出时调用

go
// main.go
func main() {
    app := app.New()
    ctx := context.Background()

    if err := app.Init(ctx, config); err != nil {
        log.Fatal(err)
    }

    // 注册退出信号处理
    defer func() {
        if err := app.Close(ctx); err != nil {
            log.Printf("资源清理失败: %v", err)
        }
    }()

    // 启动 HTTP 服务器
    // ...
}

完整示例

1. Service 定义

go
// services/product.go
package services

import (
    "context"

    "your-project/models"
    "gorm.io/gorm"
)

type ProductService struct {
    db             *gorm.DB
    storageService *StorageService
}

func NewProductService(db *gorm.DB, storageService *StorageService) *ProductService {
    return &ProductService{
        db:             db,
        storageService: storageService,
    }
}

func (s *ProductService) GetByID(ctx context.Context, id uint) (*models.Product, error) {
    var product models.Product
    if err := s.db.WithContext(ctx).First(&product, id).Error; err != nil {
        return nil, err
    }
    return &product, nil
}

func (s *ProductService) CreateWithImage(ctx context.Context, name string, price float64, imageData []byte) (*models.Product, error) {
    // 调用 StorageService 上传图片
    imageURL, err := s.storageService.UploadImage(ctx, imageData)
    if err != nil {
        return nil, err
    }

    product := &models.Product{
        Name:     name,
        Price:    price,
        ImageURL: imageURL,
    }
    if err := s.db.WithContext(ctx).Create(product).Error; err != nil {
        return nil, err
    }

    return product, nil
}

2. App 容器完整实现

go
// app/app.go
package app

import (
    "context"
    "fmt"

    "your-project/config"
    "your-project/services"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

type App struct {
    config *config.Config
    db     *gorm.DB

    userService    *services.UserService
    productService *services.ProductService
    orderService   *services.OrderService
    storageService *services.StorageService
    notifyService  *services.NotifyService
}

func New() *App {
    return &App{}
}

func (a *App) Init(ctx context.Context, cfg *config.Config) error {
    a.config = cfg

    if err := a.initDatabase(ctx); err != nil {
        return err
    }

    a.initServices()
    return nil
}

func (a *App) initDatabase(ctx context.Context) error {
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True",
        a.config.DB.Username,
        a.config.DB.Password,
        a.config.DB.Host,
        a.config.DB.Port,
        a.config.DB.Database,
    )
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        return err
    }
    a.db = db
    return nil
}

func (a *App) initServices() {
    // 按依赖顺序初始化
    a.storageService = services.NewStorageService(a.config)
    a.notifyService = services.NewNotifyService(a.config)
    a.userService = services.NewUserService(a.db)
    a.productService = services.NewProductService(a.db, a.storageService)
    a.orderService = services.NewOrderService(a.db, a.productService, a.notifyService)
}

func (a *App) UserService() *services.UserService {
    return a.userService
}

func (a *App) ProductService() *services.ProductService {
    return a.productService
}

func (a *App) OrderService() *services.OrderService {
    return a.orderService
}

func (a *App) Close(ctx context.Context) error {
    if a.db != nil {
        sqlDB, _ := a.db.DB()
        return sqlDB.Close()
    }
    return nil
}

3. Handler 使用

go
// handlers/product.go
package handlers

import (
    "net/http"

    "your-project/app"
    "your-project/services"
    "github.com/labstack/echo/v4"
)

type ProductHandler struct {
    productService *services.ProductService
}

func NewProductHandler(app *app.App) *ProductHandler {
    return &ProductHandler{
        productService: app.ProductService(),
    }
}

func (h *ProductHandler) Get(c echo.Context) error {
    id := c.Param("id")

    product, err := h.productService.GetByID(c.Request().Context(), id)
    if err != nil {
        return c.JSON(http.StatusNotFound, map[string]string{"error": "商品不存在"})
    }

    return c.JSON(http.StatusOK, product)
}

最佳实践

1. Service 依赖原则

遵循以下依赖规则

  • ✅ Service 可以依赖其他 Service
  • ✅ Service 可以依赖配置(*config.Config
  • ✅ Service 可以依赖数据库(*gorm.DB
  • ❌ Service 禁止依赖 Handler
  • ❌ Service 禁止依赖 echo.Context

2. 避免循环依赖

禁止 Service 之间形成循环依赖:

go
// ❌ 错误:循环依赖
func NewOrderService(productService *ProductService) *OrderService {...}
func NewProductService(orderService *OrderService) *ProductService {...}

解决方案

  • 提取共同依赖到独立 Service
  • 使用事件或消息队列解耦
  • 重新设计 Service 职责边界

3. 单例保证

App 容器确保每个 Service 只有一个实例

go
// ✅ 正确:同一个 Service 实例
handler1 := NewUserHandler(app)  // 获取 app.userService
handler2 := NewUserHandler(app)  // 获取同一个 app.userService

检查清单

实施依赖注入时,确保以下要点:

  • [ ] App 结构体包含所有 Service 字段
  • [ ] Service 不在内部创建依赖对象
  • [ ] Service 构造函数明确声明所有依赖参数
  • [ ] App.initServices() 按依赖顺序初始化 Service
  • [ ] App 提供 Getter 方法供 Handler 获取 Service
  • [ ] 不存在循环依赖
  • [ ] App.Close() 清理所有资源

相关规范