371 lines
17 KiB
Markdown
371 lines
17 KiB
Markdown
# 依赖注入模式
|
|
|
|
<cite>
|
|
**本文引用的文件**
|
|
- [cmd/server/main.go](file://cmd/server/main.go)
|
|
- [internal/router/router.go](file://internal/router/router.go)
|
|
- [internal/config/config.go](file://internal/config/config.go)
|
|
- [internal/config/database.go](file://internal/config/database.go)
|
|
- [internal/config/meilisearch.go](file://internal/config/meilisearch.go)
|
|
- [internal/config/redis.go](file://internal/config/redis.go)
|
|
- [internal/database/mysql.go](file://internal/database/mysql.go)
|
|
- [internal/cache/redis.go](file://internal/cache/redis.go)
|
|
- [internal/search/meilisearch.go](file://internal/search/meilisearch.go)
|
|
- [pkg/logger/logger.go](file://pkg/logger/logger.go)
|
|
- [internal/handler/health.go](file://internal/handler/health.go)
|
|
- [internal/handler/brand.go](file://internal/handler/brand.go)
|
|
- [internal/handler/model.go](file://internal/handler/model.go)
|
|
- [internal/handler/model_list.go](file://internal/handler/model_list.go)
|
|
- [internal/handler/device.go](file://internal/handler/device.go)
|
|
</cite>
|
|
|
|
## 目录
|
|
1. [引言](#引言)
|
|
2. [项目结构](#项目结构)
|
|
3. [核心组件](#核心组件)
|
|
4. [架构总览](#架构总览)
|
|
5. [详细组件分析](#详细组件分析)
|
|
6. [依赖关系分析](#依赖关系分析)
|
|
7. [性能考虑](#性能考虑)
|
|
8. [故障排查指南](#故障排查指南)
|
|
9. [结论](#结论)
|
|
10. [附录](#附录)
|
|
|
|
## 引言
|
|
本文件围绕 Luxsin 应用 API 的依赖注入模式展开,重点说明项目采用的“构造函数注入”与“手动依赖注入(工厂模式)”。我们将从入口程序开始,逐步剖析路由初始化过程中如何注入数据库连接、日志记录器、Redis 客户端与搜索客户端;同时给出替代方案(手动工厂),解释其带来的测试性增强、松耦合设计与配置灵活性,并总结生命周期管理与循环依赖规避策略。
|
|
|
|
## 项目结构
|
|
该项目采用按领域分层与功能模块划分相结合的方式组织代码:
|
|
- 入口与启动:cmd/server/main.go 负责加载配置、构建日志、数据库、搜索与缓存实例,并交由路由工厂生成 HTTP 引擎。
|
|
- 配置层:internal/config 下包含数据库、Redis、Meilisearch 的配置加载与校验逻辑。
|
|
- 基础设施:internal/database、internal/cache、internal/search 提供连接与客户端工厂。
|
|
- Web 层:internal/router 路由工厂负责注册中间件与控制器。
|
|
- 控制器层:internal/handler 各处理器通过构造函数接收依赖,体现构造函数注入。
|
|
- 日志:pkg/logger 提供基于环境的 Zap 日志工厂。
|
|
|
|
```mermaid
|
|
graph TB
|
|
subgraph "启动层"
|
|
MAIN["cmd/server/main.go<br/>加载配置/构建基础设施/启动HTTP服务"]
|
|
end
|
|
subgraph "配置层"
|
|
CFG["internal/config/*.go<br/>配置加载与校验"]
|
|
end
|
|
subgraph "基础设施"
|
|
DBF["internal/database/mysql.go<br/>数据库工厂"]
|
|
RCF["internal/cache/redis.go<br/>Redis工厂"]
|
|
MSF["internal/search/meilisearch.go<br/>搜索工厂"]
|
|
LOG["pkg/logger/logger.go<br/>日志工厂"]
|
|
end
|
|
subgraph "Web层"
|
|
RT["internal/router/router.go<br/>路由工厂"]
|
|
end
|
|
subgraph "控制器层"
|
|
H1["internal/handler/health.go"]
|
|
H2["internal/handler/brand.go"]
|
|
H3["internal/handler/model.go"]
|
|
H4["internal/handler/model_list.go"]
|
|
H5["internal/handler/device.go"]
|
|
end
|
|
MAIN --> CFG
|
|
MAIN --> DBF
|
|
MAIN --> RCF
|
|
MAIN --> MSF
|
|
MAIN --> LOG
|
|
MAIN --> RT
|
|
RT --> H1
|
|
RT --> H2
|
|
RT --> H3
|
|
RT --> H4
|
|
RT --> H5
|
|
```
|
|
|
|
图表来源
|
|
- [cmd/server/main.go:22-64](file://cmd/server/main.go#L22-L64)
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
|
|
- [internal/database/mysql.go:14-46](file://internal/database/mysql.go#L14-L46)
|
|
- [internal/cache/redis.go:10-16](file://internal/cache/redis.go#L10-L16)
|
|
- [internal/search/meilisearch.go:17-20](file://internal/search/meilisearch.go#L17-L20)
|
|
- [pkg/logger/logger.go:8-19](file://pkg/logger/logger.go#L8-L19)
|
|
|
|
章节来源
|
|
- [cmd/server/main.go:22-64](file://cmd/server/main.go#L22-L64)
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
|
|
|
|
## 核心组件
|
|
- 配置加载与校验:集中于 internal/config,支持从环境变量或默认值加载数据库、Redis、Meilisearch 参数,并进行必要校验。
|
|
- 基础设施工厂:
|
|
- 数据库工厂:打开连接池并执行健康检查。
|
|
- Redis 工厂:创建 Redis 客户端。
|
|
- 搜索工厂:创建 Meilisearch 客户端并绑定索引。
|
|
- 日志工厂:根据环境选择生产/开发配置。
|
|
- 路由工厂:注册全局中间件与各控制器,控制器通过构造函数注入依赖。
|
|
- 控制器:每个处理器以结构体形式持有依赖(数据库、日志、搜索、Redis),通过 NewXxxHandler 构造函数完成注入。
|
|
|
|
章节来源
|
|
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
|
|
- [internal/config/database.go:17-71](file://internal/config/database.go#L17-L71)
|
|
- [internal/config/meilisearch.go:14-50](file://internal/config/meilisearch.go#L14-L50)
|
|
- [internal/config/redis.go:16-56](file://internal/config/redis.go#L16-L56)
|
|
- [internal/database/mysql.go:14-46](file://internal/database/mysql.go#L14-L46)
|
|
- [internal/cache/redis.go:10-16](file://internal/cache/redis.go#L10-L16)
|
|
- [internal/search/meilisearch.go:17-20](file://internal/search/meilisearch.go#L17-L20)
|
|
- [pkg/logger/logger.go:8-19](file://pkg/logger/logger.go#L8-L19)
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
|
|
## 架构总览
|
|
下图展示了从入口到路由与控制器的依赖注入流程:入口程序负责构建所有外部依赖,然后将这些依赖作为参数传入路由工厂,再由路由工厂注入到各个控制器中。
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Entrypoint as "入口(main.go)"
|
|
participant Cfg as "配置(config.Load)"
|
|
participant Log as "日志(logger.New)"
|
|
participant DB as "数据库(database.Open)"
|
|
participant MS as "搜索(search.NewClient)"
|
|
participant RC as "缓存(cache.NewClient)"
|
|
participant RT as "路由(router.New)"
|
|
participant H as "控制器(handler.*)"
|
|
Entrypoint->>Cfg : 加载配置
|
|
Entrypoint->>Log : 创建日志实例
|
|
Entrypoint->>DB : 打开数据库连接
|
|
Entrypoint->>MS : 初始化搜索客户端
|
|
Entrypoint->>RC : 初始化Redis客户端
|
|
Entrypoint->>RT : 传入依赖构建引擎
|
|
RT->>H : 为各控制器注入依赖
|
|
Entrypoint->>Entrypoint : 启动HTTP服务
|
|
```
|
|
|
|
图表来源
|
|
- [cmd/server/main.go:22-64](file://cmd/server/main.go#L22-L64)
|
|
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
|
|
- [pkg/logger/logger.go:8-19](file://pkg/logger/logger.go#L8-L19)
|
|
- [internal/database/mysql.go:14-46](file://internal/database/mysql.go#L14-L46)
|
|
- [internal/search/meilisearch.go:17-20](file://internal/search/meilisearch.go#L17-L20)
|
|
- [internal/cache/redis.go:10-16](file://internal/cache/redis.go#L10-L16)
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
|
|
## 详细组件分析
|
|
|
|
### 入口程序中的依赖注入与生命周期
|
|
入口程序负责:
|
|
- 加载配置并校验;
|
|
- 构建日志、数据库、搜索与缓存实例;
|
|
- 将这些实例注入路由工厂,生成 HTTP 引擎;
|
|
- 启动服务并在退出信号到来时优雅关闭。
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
Start(["程序启动"]) --> LoadCfg["加载配置(config.Load)"]
|
|
LoadCfg --> BuildLogger["创建日志(logger.New)"]
|
|
BuildLogger --> OpenDB["打开数据库(database.Open)"]
|
|
OpenDB --> InitSearch["初始化搜索(search.NewClient)"]
|
|
InitSearch --> InitRedis["初始化Redis(cache.NewClient)"]
|
|
InitRedis --> BuildRouter["构建路由(router.New)"]
|
|
BuildRouter --> RunServer["启动HTTP服务"]
|
|
RunServer --> WaitSignal["等待退出信号"]
|
|
WaitSignal --> GracefulShutdown["优雅关闭(Shutdown)"]
|
|
GracefulShutdown --> End(["结束"])
|
|
```
|
|
|
|
图表来源
|
|
- [cmd/server/main.go:22-95](file://cmd/server/main.go#L22-L95)
|
|
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
|
|
- [pkg/logger/logger.go:8-19](file://pkg/logger/logger.go#L8-L19)
|
|
- [internal/database/mysql.go:14-46](file://internal/database/mysql.go#L14-L46)
|
|
- [internal/search/meilisearch.go:17-20](file://internal/search/meilisearch.go#L17-L20)
|
|
- [internal/cache/redis.go:10-16](file://internal/cache/redis.go#L10-L16)
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
|
|
章节来源
|
|
- [cmd/server/main.go:22-95](file://cmd/server/main.go#L22-L95)
|
|
|
|
### 路由工厂与控制器注入
|
|
路由工厂统一注册中间件并创建各控制器实例,控制器通过各自的 NewXxxHandler 构造函数注入依赖。该模式体现了“构造函数注入”,确保依赖在对象创建时明确、不可变且可替换。
|
|
|
|
```mermaid
|
|
classDiagram
|
|
class RouterFactory {
|
|
+New(log, db, searchClient, redis) *gin.Engine
|
|
}
|
|
class HealthHandler {
|
|
+Check(c)
|
|
}
|
|
class BrandHandler {
|
|
-repo BrandRepository
|
|
-log Logger
|
|
+GetBrand(c)
|
|
}
|
|
class ModelHandler {
|
|
-repo ModelRepository
|
|
-log Logger
|
|
+GetModel(c)
|
|
}
|
|
class ModelListHandler {
|
|
-search SearchClient
|
|
-log Logger
|
|
+ModelList(c)
|
|
}
|
|
class DeviceHandler {
|
|
-redis RedisClient
|
|
-log Logger
|
|
+ReportDevInfo(c)
|
|
}
|
|
RouterFactory --> HealthHandler : "创建"
|
|
RouterFactory --> BrandHandler : "创建"
|
|
RouterFactory --> ModelHandler : "创建"
|
|
RouterFactory --> ModelListHandler : "创建"
|
|
RouterFactory --> DeviceHandler : "创建"
|
|
```
|
|
|
|
图表来源
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
- [internal/handler/health.go:10-18](file://internal/handler/health.go#L10-L18)
|
|
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
|
|
- [internal/handler/model.go:19-24](file://internal/handler/model.go#L19-L24)
|
|
- [internal/handler/model_list.go:19-24](file://internal/handler/model_list.go#L19-L24)
|
|
- [internal/handler/device.go:19-24](file://internal/handler/device.go#L19-L24)
|
|
|
|
章节来源
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
|
|
### 数据库连接注入与生命周期
|
|
- 入口程序调用数据库工厂打开连接并执行健康检查,随后将 *sql.DB 注入路由工厂。
|
|
- 路由工厂将数据库实例注入到需要持久化的控制器(如品牌、型号列表)。
|
|
- 入口程序在程序退出前关闭数据库连接,保证资源回收。
|
|
|
|
章节来源
|
|
- [cmd/server/main.go:38-42](file://cmd/server/main.go#L38-L42)
|
|
- [internal/database/mysql.go:14-46](file://internal/database/mysql.go#L14-L46)
|
|
- [internal/router/router.go:22-23](file://internal/router/router.go#L22-L23)
|
|
|
|
### 日志记录器注入
|
|
- 入口程序创建日志实例后,将其注入到路由工厂与各控制器,用于统一的日志输出与上下文记录。
|
|
|
|
章节来源
|
|
- [cmd/server/main.go:32-36](file://cmd/server/main.go#L32-L36)
|
|
- [internal/router/router.go:18](file://internal/router/router.go#L18)
|
|
|
|
### Redis 客户端注入
|
|
- 入口程序创建 Redis 客户端后,注入到路由工厂与设备上报控制器,用于存储设备信息。
|
|
- 控制器在处理请求时使用 Redis 客户端执行 HSet 等操作。
|
|
|
|
章节来源
|
|
- [cmd/server/main.go:56-57](file://cmd/server/main.go#L56-L57)
|
|
- [internal/cache/redis.go:10-16](file://internal/cache/redis.go#L10-L16)
|
|
- [internal/router/router.go:25](file://internal/router/router.go#L25)
|
|
- [internal/handler/device.go:71](file://internal/handler/device.go#L71)
|
|
|
|
### 搜索客户端注入
|
|
- 入口程序创建搜索客户端后,注入到模型列表控制器,用于检索模型数据。
|
|
- 控制器在处理请求时调用搜索客户端执行查询。
|
|
|
|
章节来源
|
|
- [cmd/server/main.go:50-54](file://cmd/server/main.go#L50-L54)
|
|
- [internal/search/meilisearch.go:17-20](file://internal/search/meilisearch.go#L17-L20)
|
|
- [internal/router/router.go:24](file://internal/router/router.go#L24)
|
|
- [internal/handler/model_list.go:37](file://internal/handler/model_list.go#L37)
|
|
|
|
### 手动依赖注入(工厂模式)替代方案
|
|
当不使用第三方 DI 容器时,可通过“手动依赖注入(工厂模式)”实现相同目标:
|
|
- 在入口程序中集中创建所有依赖(配置、日志、数据库、搜索、缓存)。
|
|
- 将这些依赖作为参数传递给路由工厂,再由路由工厂注入到控制器。
|
|
- 优点:无需引入额外依赖,控制权完全在应用内,便于单元测试替换依赖。
|
|
|
|
章节来源
|
|
- [cmd/server/main.go:22-64](file://cmd/server/main.go#L22-L64)
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
|
|
## 依赖关系分析
|
|
- 入口程序对配置、日志、数据库、搜索、缓存与路由有直接依赖。
|
|
- 路由工厂对日志、数据库、搜索、Redis 与控制器有依赖。
|
|
- 控制器对各自使用的基础设施(数据库、日志、搜索、Redis)有依赖。
|
|
- 配置层对环境变量与默认值有依赖,负责参数校验。
|
|
|
|
```mermaid
|
|
graph LR
|
|
MAIN["入口(main.go)"] --> CFG["配置(config)"]
|
|
MAIN --> LOG["日志(logger)"]
|
|
MAIN --> DB["数据库(factory)"]
|
|
MAIN --> MS["搜索(factory)"]
|
|
MAIN --> RC["缓存(factory)"]
|
|
MAIN --> RT["路由(router)"]
|
|
RT --> H1["健康控制器"]
|
|
RT --> H2["品牌控制器"]
|
|
RT --> H3["型号控制器"]
|
|
RT --> H4["模型列表控制器"]
|
|
RT --> H5["设备控制器"]
|
|
H2 --> DB
|
|
H3 --> DB
|
|
H4 --> MS
|
|
H5 --> RC
|
|
```
|
|
|
|
图表来源
|
|
- [cmd/server/main.go:22-64](file://cmd/server/main.go#L22-L64)
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
|
|
- [internal/handler/model.go:19-24](file://internal/handler/model.go#L19-L24)
|
|
- [internal/handler/model_list.go:19-24](file://internal/handler/model_list.go#L19-L24)
|
|
- [internal/handler/device.go:19-24](file://internal/handler/device.go#L19-L24)
|
|
|
|
章节来源
|
|
- [cmd/server/main.go:22-64](file://cmd/server/main.go#L22-L64)
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
|
|
## 性能考虑
|
|
- 数据库连接池:入口程序在数据库工厂中设置最大并发连接数、空闲连接数与连接生命周期,有助于提升并发场景下的稳定性与性能。
|
|
- 日志级别:根据环境选择生产/开发配置,减少不必要的编码开销。
|
|
- 搜索与缓存:合理设置搜索客户端与 Redis 客户端的超时与重试策略,避免阻塞请求线程。
|
|
|
|
章节来源
|
|
- [internal/database/mysql.go:33-35](file://internal/database/mysql.go#L33-L35)
|
|
- [pkg/logger/logger.go:10-17](file://pkg/logger/logger.go#L10-L17)
|
|
|
|
## 故障排查指南
|
|
- 配置加载失败:检查环境变量是否正确设置,确认配置加载函数返回的错误信息。
|
|
- 数据库连接失败:查看数据库工厂的健康检查与错误返回,确认主机、端口、用户名与密码。
|
|
- 搜索客户端异常:确认搜索主机、API 密钥与索引名称是否正确。
|
|
- Redis 连接异常:确认 Redis 主机、端口与数据库编号。
|
|
- 日志初始化失败:确认环境变量与日志工厂的配置分支。
|
|
|
|
章节来源
|
|
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
|
|
- [internal/config/database.go:57-71](file://internal/config/database.go#L57-L71)
|
|
- [internal/config/meilisearch.go:39-50](file://internal/config/meilisearch.go#L39-L50)
|
|
- [internal/config/redis.go:51-56](file://internal/config/redis.go#L51-L56)
|
|
- [internal/database/mysql.go:40-43](file://internal/database/mysql.go#L40-L43)
|
|
- [internal/search/meilisearch.go:27-29](file://internal/search/meilisearch.go#L27-L29)
|
|
- [internal/cache/redis.go:10-16](file://internal/cache/redis.go#L10-L16)
|
|
- [pkg/logger/logger.go:8-19](file://pkg/logger/logger.go#L8-L19)
|
|
|
|
## 结论
|
|
本项目通过“构造函数注入 + 手动工厂”的方式实现了清晰、可控的依赖管理。入口程序集中构建依赖,路由工厂统一注入,控制器职责单一且易于测试。该模式具备以下优势:
|
|
- 测试性增强:可在测试中轻松替换依赖(数据库、搜索、缓存)。
|
|
- 松耦合设计:控制器仅依赖接口抽象,具体实现通过构造函数注入。
|
|
- 配置灵活性:通过配置层集中管理环境变量与默认值,支持多环境部署。
|
|
|
|
## 附录
|
|
|
|
### 依赖生命周期管理
|
|
- 入口程序负责创建与销毁:日志、数据库、搜索与缓存均在入口处创建并在退出时关闭。
|
|
- 路由与控制器仅持有依赖引用,不负责释放底层资源。
|
|
|
|
章节来源
|
|
- [cmd/server/main.go:36-42](file://cmd/server/main.go#L36-L42)
|
|
- [cmd/server/main.go:56-57](file://cmd/server/main.go#L56-L57)
|
|
- [cmd/server/main.go:87-92](file://cmd/server/main.go#L87-L92)
|
|
|
|
### 循环依赖的避免策略
|
|
- 将共享依赖集中在入口程序,避免控制器之间相互创建对方所需的依赖。
|
|
- 使用接口抽象(例如数据库、搜索、缓存)降低耦合度,防止编译期循环导入。
|
|
- 将业务逻辑与基础设施解耦,控制器只持有必要的最小依赖集。
|
|
|
|
章节来源
|
|
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
|
|
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
|
|
- [internal/handler/model.go:19-24](file://internal/handler/model.go#L19-L24)
|
|
- [internal/handler/model_list.go:19-24](file://internal/handler/model_list.go#L19-L24)
|
|
- [internal/handler/device.go:19-24](file://internal/handler/device.go#L19-L24) |