依赖注入模式
概述
依赖注入(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 结构体作为依赖注入容器,负责:
- 持有所有 Service 实例
- 管理 Service 的创建和初始化顺序
- 提供 Getter 方法供 Handler 获取 Service
- 确保 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() 清理所有资源
相关规范
- 分层架构设计 - 分层职责和调用规范
- Service 层设计 - Service 设计详细规范
- 应用生命周期 - 应用启动和关闭流程