diff --git a/hzh/GO/工程模块化.md b/hzh/GO/工程模块化.md
new file mode 100644
index 0000000..516d7f0
--- /dev/null
+++ b/hzh/GO/工程模块化.md
@@ -0,0 +1,305 @@
+---
+tags: [go, engineering, modularization, architecture, project-structure]
+create time: 2026-04-29 15:30
+---
+
+# Go 工程模块化
+
+## 概述
+
+系统梳理 Go 项目的模块化和目录结构设计,从项目级别的架构模式到包粒度的拆分策略,涵盖工程化实践中最常见的结构选型和陷阱。
+
+## 一、Go 模块与包的基础概念
+
+> [!question] 思考:Module 和 Package 有什么区别?
+>
+> - **Module(模块)**:`go.mod` 定义的最小版本管理单元,发布、依赖管理的边界。一个 Module 可以包含多个 Packages。
+> - **Package(包)**:代码组织和编译的单位,同一目录下所有文件必须属于同一个 Package。import 的路径粒度是 Package 级别。
+
+核心关系:**Module → 多个 Package → 多个 .go 文件**
+
+```go
+module github.com/user/my-project // go.mod — 模块标识
+
+// import 路径
+github.com/user/my-project/pkg/user // Package: pkg/user
+github.com/user/my-project/internal/handler // Package: internal/handler
+```
+
+> [!tip] 关键规则
+>
+> - `internal/` 下的包只能被其父目录及以内的代码导入,外部 module 无法引用。这是 Go 语言层面强制的可见性约束,比 `private` 关键字更可靠。
+> - 不要在 `internal/` 里放第三方库或公共工具——它的语义就是"内部使用"。
+
+## 二、项目级结构选型
+
+### 2.1 标准 Go 项目布局(Standard Go Project Layout)
+
+这是社区最广泛认可的项目结构规范,由 [golang-standards/project-layout](https://github.com/golang-standards/project-layout) 提出并维护:
+
+```
+my-project/
+├── cmd/ # 入口点,每个子目录是一个可执行命令
+│ └── myapp/
+│ └── main.go # 入口极薄,只做初始化调用
+├── internal/ # 私有代码,外部无法 import
+│ ├── handler/ # HTTP/gRPC 处理器
+│ ├── service/ # 业务逻辑层
+│ ├── repository/ # 数据访问层
+│ ├── model/ # 领域模型
+│ └── config/ # 配置加载
+├── pkg/ # 可对外发布的公共包
+│ ├── middleware/
+│ └── util/
+├── api/ # API 定义(proto/openapi/grpc-gateway)
+├── web/ # 前端静态资源
+├── configs/ # 配置文件示例
+├── scripts/ # 构建/部署脚本
+├── docs/ # 文档
+├── tests/ # 外部集成测试
+├── go.mod
+├── go.sum
+└── README.md
+```
+
+> [!success] 适用场景
+>
+> - 需要开源或向其他团队分发 `pkg/` 中的代码
+> - 项目会持续增长,需要明确的组织规范
+> - 团队协作规模较大(5+ 人)
+
+### 2.2 整洁架构(Clean Architecture / Onion Architecture)
+
+以**业务核心**为中心,外层依赖内层,所有方向指向内:
+
+```
+my-project/
+├── cmd/app/main.go
+├── internal/
+│ ├── domain/ # 【最内层】实体、值对象、领域接口
+│ │ ├── entity/ # User, Order 等核心实体
+│ │ ├── valueobject/ # Money, EmailAddress 等值对象
+│ │ └── repository/ # 领域接口定义(契约)
+│ ├── application/ # 用例/应用服务
+│ │ └── usecase/ # CreateUserOrder 等业务编排
+│ ├── infrastructure/ # 【外一层】实现细节
+│ │ ├── persistence/ # 数据库驱动实现
+│ │ ├── externalapi/ # 第三方支付、邮件等
+│ │ └── auth/ # JWT/OAuth 实现
+│ └── interfaces/ # 【最外层】接入方式
+│ ├── http/ # HTTP handlers + router 注册
+│ ├── grpc/ # gRPC server
+│ └── cli/ # CLI 命令行
+└── go.mod
+```
+
+依赖方向图示:
+
+```mermaid
+graph LR
+ A["interfaces (HTTP/gRPC)"] --> B["application (usecases)"]
+ B --> C["domain (entities)"]
+ D["infrastructure (DB/API)"] --> C
+ D -.->|implements| B
+ style C fill:#f9f,stroke:#333,stroke-width:3px
+ style B fill:#bbf,stroke:#333
+ style D fill:#bfb,stroke:#333
+ style A fill:#fbf,stroke:#333
+```
+
+> [!info] Clean Architecture 核心原则
+>
+> 1. **依赖倒置**:内层定义接口,外层提供实现。这样换数据库、换框架不需要改动业务逻辑。
+> 2. **域独立**:`domain/entity` 不依赖任何框架或外部库,就是纯 struct。
+> 3. **切换成本低**:MySQL → PostgreSQL 只改 `infrastructure/persistence` 一个目录。
+
+> [!warning] 何时不用
+>
+> 简单 CRUD 项目引入 Clean Architecture 会带来过度抽象。按以下标准判断:
+>
+> | 条件 | 推荐方案 |
+> |------|----------|
+> | 单体、单一数据库、快速迭代 | 分层简单结构(见下方) |
+> | 多团队、多数据源、长周期 | Clean Architecture |
+> | 领域复杂(金融、电商交易) | Clean Architecture 或 DDD |
+> | MVP / PoC | 越简单越好 |
+
+### 2.3 轻量分层结构(适合大多数中小型项目)
+
+如果 Clean Architecture 太笨重,三层是最小可用划分:
+
+```
+my-project/
+├── cmd/app/main.go
+├── internal/
+│ ├── handler/ # 接收请求、参数校验、调 service
+│ ├── service/ # 业务逻辑、流程编排
+│ └── repo/ # SQL 查询、缓存读写
+├── pkg/ # 可复用组件
+└── go.mod
+```
+
+**黄金法则**:请求流向是单向的 `handler → service → repo`,禁止反向调用。
+
+## 三、包粒度与拆分策略
+
+### 3.1 拆到什么粒度合适?
+
+> [!question] 包的合理大小是多少?
+>
+> 没有一个精确的数字,但有以下参考基准:
+
+| 维度 | 建议范围 | 说明 |
+|------|----------|------|
+| 文件数 | 3 ~ 15 个 | 少于 3 可能该合并;超过 15 需检查是否职责过多 |
+| LOC | 200 ~ 1500 行 | 纯接口/类型声明可适当放宽 |
+| 职责 | 围绕一个概念 | `user`、`order`、`payment`——用领域名词命名 |
+
+> [!failure] 常见反模式
+>
+> - **utils 包地狱**:把所有工具函数扔进 `pkg/utils`,包越来越大、越来越难找东西。
+> - **解法**:按功能域拆分——`stringutil`、`datetimeutil`、`httputil`。或者直接在使用的地方新建包。
+> - **一个包对应一个文件**:如果文件之间强相关,应该放在同一个包里,避免跨包循环引用。
+> - **过深的包路径**:`internal/a/b/c/d/e` 超过 4 层通常意味着分类有问题。
+
+### 3.2 按什么维度拆包?
+
+三种主流维度,实际项目中常组合使用:
+
+```mermaid
+flowchart TD
+ A["包拆分维度"] --> B["按职责分层
handler/service/repo"]
+ A --> C["按业务域分组
user/order/payment"]
+ A --> D["混合模式"]
+
+ D --> E["internal/user/handler
internal/user/service
internal/user/repo"]
+
+ style E fill:#f9f,stroke:#333,stroke-width:3px
+```
+
+| 模式 | 结构示意 | 适合 |
+|------|----------|------|
+| **分层式** | `handler/`, `service/`, `repo/` | 单一聚合根、领域不太复杂 |
+| **域驱动式** | `user/`, `order/`, `payment/` | 多领域边界清晰、大团队 |
+| **混合式** | `user/handler`, `user/service` | **多数项目的最佳平衡点** |
+
+### 3.3 共享包 vs internal 包的决策树
+
+```
+某个包要不要暴露出去?
+├── 其他 module 会需要吗?
+│ ├── 会 → pkg/xxx
+│ └── 不会 → internal/xxx
+└── 整个项目都不需要 import 呢?
+ └── 直接放在所属业务包下,不要单独建包
+```
+
+> [!tip] `pkg/` 的使用建议
+>
+> - 放进 `pkg/` 等于承诺这是一个稳定的公开 API——其他人会依赖它。
+> - 常见的放 `pkg/` 的内容:日志封装、数据库连接池初始化、通用中间件、配置结构体。
+> - 如果不打算开源且没有外部消费者,**优先放 `internal/`**。
+
+## 四、避免循环依赖
+
+Go 编译器在编译期就会拒绝循环 import,所以不会像 Java 那样出现运行时问题。但在编码阶段频繁遇到,以下是高频场景和解法:
+
+### 4.1 常见循环依赖路径
+
+```
+handler → service → repository → model → handler ❌
+```
+
+> [!failure] 根因:model 定义了需要在 handler 中序列化的字段(如 `LastLoginTime`),repository 也用到 model。
+
+### 4.2 解法矩阵
+
+```mermaid
+quadrantChart
+ title 循环依赖解法选择
+ x-axis "低复杂度" --> "高复杂度"
+ y-axis "侵入性小" --> "侵入性大"
+ "提取接口": [0.2, 0.15]
+ "下沉共享包": [0.4, 0.35]
+ "引入 DTO": [0.5, 0.55]
+ "依赖注入": [0.7, 0.75]
+```
+
+| 解法 | 做法 | 适用场景 |
+|------|------|----------|
+| **接口下沉** | 把接口移到更通用的包(如 `internal/repository/contract`) | service 和 repo 之间的依赖混乱 |
+| **共享模型包** | 创建 `internal/model` 存放双方共用的类型 | model 确实被多层共用 |
+| **DTO 隔离** | handler 和 domain 用不同的类型,通过转换函数对齐 | 序列化格式和领域模型不一致 |
+| **依赖注入** | 用构造器传入依赖而非全局变量或直接 import(详见 [[依赖注入]]) | 大型项目、测试友好 |
+
+## 五、Go 项目的目录设计 Checklist
+
+初始化新项目时按此顺序思考:
+
+```mermaid
+flowchart LR
+ A["确定项目性质"] --> B{"需要对外发布包?"}
+ B -->|是| C["使用标准布局 + pkg/"]
+ B -->|否| D["精简布局,全放 internal/"]
+ D --> E{"领域复杂度?"}
+ E -->|简单 CRUD| F["轻量三层结构"]
+ E -->|复杂业务| G["Clean Architecture"]
+ F --> H["按单一路径拆分: handler/service/repo"]
+ G --> I["按域分组 + 分层"]
+ C --> J["开始编码"]
+ H --> J
+ I --> J
+```
+
+**逐项检查清单**:
+
+- [ ] `go.mod` 的 module name 已定好(建议用完整 import path,如 `github.com/org/repo`)
+- [ ] `cmd/` 下有 `main.go`,且逻辑不超过 20 行(只做依赖组装和应用启动)
+- [ ] `internal/` 包含了所有一级私有包
+- [ ] 每个包的 `package` 名和目录名一致(Go 强制要求,但容易在移动目录时遗忘)
+- [ ] 没有 `utils`、`common`、`tools` 这类万金油包名
+- [ ] 跨包引用是单向的,无环(可用 `goimports -l` 辅助检查)
+- [ ] 测试文件跟源文件放在一起,同包测试用 `_test.go`,白盒外部测试用 `x_test.go`(`package xxx_test`)
+
+## 六、模块化管理实战要点
+
+### 6.1 单 Module vs 多 Module
+
+```mermaid
+graph LR
+ A["项目依赖关系"] --> B{"各部分版本号需要独立控制?"}
+ B -->|是| C["Monorepo + 多 go.mod"]
+ B -->|否| D["单个 go.mod"]
+
+ C --> E["各自独立版本发布"]
+ D --> F["统一版本,最简单"]
+
+ style D fill:#bfb,stroke:#333,stroke-width:3px
+ style C fill:#fbf,stroke:#333
+```
+
+> [!summary] 决策建议
+>
+> - **绝大多数项目**:一个 `go.mod` 就够了。Go 的包系统已经足够做模块化解耦。
+> - **需要多 Module 的场景**:多个团队独立发布、不同生命周期(一个高频发版一个低频)、或作为独立 library 提供给他人。
+> - Monorepo 多 Module 时,用 `replace` 指令在本地开发时指向 sibling module,CI 时移除。
+
+### 6.2 Import 路径风格约定
+
+```
+✅ 推荐
+internal/user/service
+internal/user/service/user.go // 文件名不必与包名重复
+
+❌ 应避免
+internal/user-service/service // 连字符不符合 Go 命名惯例
+internal/user_service/ // 下划线也不常用
+pkg/util/helper.go // 嵌套过深,helper 本身没有信息量
+```
+
+**基本原则**:import 路径应该是可读的英文短语,用 `/` 分隔单词,全小写。
+
+## 关联笔记
+
+- [[测试目录结构]]
+- [[依赖注入]]
diff --git a/hzh/GO/工程模块化/依赖注入.md b/hzh/GO/工程模块化/依赖注入.md
new file mode 100644
index 0000000..081724f
--- /dev/null
+++ b/hzh/GO/工程模块化/依赖注入.md
@@ -0,0 +1,377 @@
+---
+tags: [go, dependency-injection, di, architecture, testing, refactoring]
+create time: 2026-04-29 15:30
+---
+
+# Go 依赖注入(Dependency Injection)
+
+## 概述
+
+系统梳理 Go 语言中依赖注入的实践模式、选型决策和常见陷阱。Go 的 DI 哲学与 Java Spring 等框架截然不同——它推崇显式构造器注入,不依赖反射、IoC 容器或运行时魔法。
+
+## 一、为什么 Go 不需要 IoC 容器?
+
+> [!question] 思考:Spring 的 BeanFactory 在 Go 里是什么?
+>
+> 在 Go 中,**构造函数就是 IoC 容器**。调用者一眼就能看到类型需要哪些依赖,不需要查看注册表、配置文件或注解。这就是 Go **"显式优于隐式"** 的设计哲学。
+
+Java/Spring 的典型风格 vs Go 风格对比:
+
+```java
+// ❌ Java Spring —— 隐式绑定,运行时查找
+@Service
+public class OrderService {
+ @Autowired
+ private OrderRepository repository; // 谁也不知道从哪里注入来的
+}
+```
+
+```go
+// ✅ Go —— 显式构造,编译期检查
+type OrderService struct {
+ repo OrderRepository // 结构体字段只是声明
+}
+
+func NewOrderService(repo OrderRepository) *OrderService {
+ return &OrderService{repo: repo} // 依赖关系写得一清二楚
+}
+```
+
+| 维度 | Spring IoC 容器 | Go 手动注入 |
+|------|----------------|------------|
+| **依赖可见性** | 通过注解 + 容器配置 | 构造函数签名直接暴露 |
+| **编译时检查** | 部分(缺 Bean 可能运行时才报错) | 全部(少传参数直接编译失败) |
+| **调试难度** | 启动失败时 stack trace 深不见底 | 普通函数调用,panic 信息直观 |
+| **性能开销** | 启动时大量反射初始化 | 零额外开销 |
+| **测试友好度** | 需 MockBean/@SpringBootTest 配合 | 直接传 mock 构造即可 |
+
+## 二、构造器注入:DI 的最基本形式
+
+核心原则:**结构体不自己创建依赖,而是通过构造函数接收**。
+
+### 2.1 反模式 vs 推荐模式
+
+```go
+// ❌ 反模式:服务内部自行创建依赖(紧耦合,不可替换)
+type OrderService struct {
+ db *sql.DB // 硬编码的具体类型
+}
+
+func NewOrderService() *OrderService {
+ conn, _ := sql.Open("postgres", dsn) // 隐藏了副作用和错误处理
+ return &OrderService{db: conn}
+}
+```
+
+问题:
+1. `dsn` 从哪来?对调用方来说是隐形依赖。
+2. 单元测试必须启动真实数据库,无法 mock。
+3. 想换成 Redis 做缓存?需要改源码。
+
+```go
+// ✅ 推荐:外部注入依赖,依赖抽象化
+type OrderService struct {
+ repo OrderRepository
+ logger *slog.Logger
+ cache Cache
+}
+
+func NewOrderService(
+ repo OrderRepository,
+ logger *slog.Logger,
+ cache Cache,
+) *OrderService {
+ return &OrderService{repo: repo, logger: logger, cache: cache}
+}
+```
+
+三个好处:
+- **可测试**:传入 mock Repo,无需启动 DB。
+- **可见**:构造函数签名 = 完整的依赖清单,IDE 自动补全提示。
+- **灵活**:同一 service 可按场景注入不同组合。
+
+### 2.2 为什么用接口而非具体类型?
+
+```go
+// Service 依赖的是接口,不是具体实现
+type OrderRepository interface {
+ ListByUserID(ctx context.Context, userID int64) ([]*Order, error)
+ Create(ctx context.Context, o *Order) error
+}
+```
+
+这实现了 **[依赖倒置原则](https://en.wikipedia.org/wiki/Dependency_inversion_principle)**:
+- 高层模块(service)不依赖低层模块(repository 的具体实现)。
+- 两者都依赖于**抽象**(interface)。
+- 切换实现只需改 Composition Root 一行代码,业务逻辑零改动。
+
+> [!tip] YAGNI 接口原则
+>
+> 不要一开始就给所有 struct 加接口。**只在必要时定义接口**:
+>
+> | 情况 | 是否定义接口 |
+> |------|-------------|
+> | 需要跨包引用 | ✅ 必须 |
+> | 需要用 mock 做单元测试 | ✅ 必须 |
+> | 只在一个包内使用,无独立测试 | ❌ 不需要 |
+
+## 三、可选依赖:Options 模式
+
+当依赖增多到 4~5 个以上时,构造函数参数变得臃肿。Go 社区的标准解法是**函数选项模式**:
+
+```go
+type UserService struct {
+ repo UserRepository
+ logger *slog.Logger
+ cache Cache
+ enableLog bool
+ maxRetries int
+}
+
+// Option 是修改 UserService 字段的函数
+type Option func(*UserService)
+
+func WithLogger(logger *slog.Logger) Option {
+ return func(s *UserService) { s.logger = logger }
+}
+
+func WithCache(cache Cache) Option {
+ return func(s *UserService) { s.cache = cache }
+}
+
+func WithMaxRetries(n int) Option {
+ return func(s *UserService) { s.maxRetries = n }
+}
+
+// 必选 + 可选分离
+func NewUserService(repo UserRepository, opts ...Option) *UserService {
+ s := &UserService{
+ repo: repo,
+ enableLog: true, // 合理默认值
+ maxRetries: 3,
+ }
+ for _, opt := range opts {
+ opt(s)
+ }
+ return s
+}
+
+// 调用点清晰可读
+svc := NewUserService(repo,
+ WithLogger(logger),
+ WithCache(newRedisCache()),
+)
+```
+
+> [!info] Options 模式的权衡
+>
+> - **优点**:向后兼容(新增选项不影响旧调用方),调用点像声明式 DSL。
+> - **缺点**:每个选项多写一个函数;超过 6~8 个选项时需考虑拆分 service。
+
+## 四、Composition Root:在哪里组装依赖?
+
+**Composition Root**(组合根目录)是 DI 的核心概念——在整个应用入口处将依赖组装在一起。Go 项目中它就是 `cmd/main.go`:
+
+```go
+package main
+
+func main() {
+ cfg := loadConfig()
+
+ // ── 基础设施层 ──
+ db := initPostgres(cfg.DSN)
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
+ redisClient := initRedis(cfg.RedisURL)
+
+ // ── 数据访问层(接口 ←→ 实现对接)──
+ userRepo := repository.NewPostgresUserRepo(db)
+ orderRepo := repository.NewPostgresOrderRepo(db)
+
+ // ── 业务逻辑层 ──
+ userSvc := service.NewUserService(userRepo,
+ service.WithLogger(logger),
+ )
+ orderSvc := service.NewOrderService(orderRepo, userRepo,
+ service.WithLogger(logger),
+ service.WithCache(redisClient),
+ )
+
+ // ── 表现层 ──
+ router := handler.NewRouter(userSvc, orderSvc)
+
+ log.Printf("server starting on :%s", cfg.Port)
+ http.ListenAndServe(":"+cfg.Port, router)
+}
+```
+
+> [!warning] 常见陷阱
+>
+> - **不要在 service 内部 new 依赖**:一旦某个地方绕过了构造器,整个 DI 链条就断了。
+> - **不要全局变量存依赖**:`var db *sql.DB` 会导致包级状态污染,并发问题和测试隔离问题接踵而来。
+> - **不要在 service 之间循环调用**:orderSvc 注入了 userRepo,那 userSvc 就不应该再注入 orderSvc。
+> - **不要为了 DI 而 DI**:工具函数包不需要接口+注入——过度设计也是反模式。
+
+## 五、Wire:手动 DI vs 代码生成
+
+Go 生态中有 fx(Uber)、wire 等 DI 工具,但**社区普遍推荐小中型项目手动组装**。
+
+| 维度 | 手动组装 | wire(编译时代码生成) | fx(运行时间单解析) |
+|------|---------|----------------------|-------------------|
+| 学习成本 | 零 | 中等(理解 build tags + 注释标注) | 高(App 生命周期 + 回调规则) |
+| 调试难度 | 低 | 低(生成的代码即普通 Go 代码) | 中高(运行时 panic,堆栈深) |
+| 启动速度 | 无开销 | 无开销 | 有反射开销 |
+| 适用规模 | < 50 个组件 | 50+ 组件,频繁变更依赖图 | 较少推荐,社区认可度低于 wire |
+
+### 5.1 Wire 的工作原理
+
+Wire 不是运行时框架,它在**编译期生成初始化代码**,生成的文件和手写的没有任何区别:
+
+```bash
+# 安装
+go install github.com/google/wire/cmd/wire@latest
+```
+
+编写 injector(注意 `//go:build wireinject` build tag):
+
+```go
+// go:build wireinject
+package main
+
+// InitializeApp 组装完整的 App 依赖图
+func InitializeApp(cfg Config) (*App, error) {
+ wire.Build(
+ initPostgres,
+ initRedis,
+ slog.New,
+
+ repository.NewPostgresUserRepo,
+ repository.NewPostgresOrderRepo,
+
+ service.NewUserService,
+ service.NewOrderService,
+
+ handler.NewRouter,
+
+ App.New,
+ )
+ return nil, nil
+}
+```
+
+运行 wire:
+
+```bash
+$ wire ./cmd/app
+# → 生成 cmd/app/inject.wire_gen.go
+```
+
+生成的文件大致是:
+
+```go
+// Code generated by wire; DO NOT EDIT.
+package main
+
+func InitializeApp(cfg Config) (*App, error) {
+ db := initPostgres(cfg.DSN)
+ redisClient := initRedis(cfg.RedisURL)
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
+ userRepo := repository.NewPostgresUserRepo(db)
+ orderRepo := repository.NewPostgresOrderRepo(db)
+ userSvc := service.NewUserService(userRepo, nil /* no logger option */)
+ orderSvc := service.NewOrderService(orderRepo, userRepo, nil, nil)
+ router := handler.NewRouter(userSvc, orderSvc)
+ app := NewApp(router)
+ return app, nil
+}
+```
+
+> [!summary] Wire 的使用时机
+>
+> - 依赖图 **< 20 个组件** → 手写,不需要 wire。
+> - 依赖图 **20 ~ 50 个组件** → 手写 + options 模式完全够用。
+> - 依赖图 **> 50 个组件** 且团队多人协作 → 考虑 wire 自动生成。
+> - 不想引入任何额外工具链 → 手写,永远是最安全的选择。
+
+## 六、DI 在测试中的价值
+
+DI 的最大受益者是**单元测试**——mock 替代真实依赖,测试变成快速、确定性的纯内存操作:
+
+```go
+// ── Mock 实现 ──
+type mockUserRepo struct {
+ mu sync.Mutex
+ users map[int64]*User
+}
+
+func (m *mockUserRepo) ByID(ctx context.Context, id int64) (*User, error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ u, ok := m.users[id]
+ if !ok {
+ return nil, ErrNotFound
+ }
+ // 返回副本,防止测试间相互篡改
+ cp := *u
+ return &cp, nil
+}
+
+func (m *mockUserRepo) Save(ctx context.Context, u *User) error {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ m.users[u.ID] = u
+ return nil
+}
+
+// ── 单元测试:毫秒级执行,不依赖外部环境 ──
+func TestCreateOrder(t *testing.T) {
+ repo := &mockUserRepo{
+ users: map[int64]*User{1: {ID: 1, Name: "alice"}},
+ }
+ svc := NewOrderService(repo)
+
+ order, err := svc.CreateOrder(context.Background(), 1, "widget", 2)
+ require.NoError(t, err)
+ assert.Equal(t, "alice", order.OwnerName)
+ assert.Equal(t, 2, order.Quantity)
+}
+```
+
+> [!success] 最佳实践
+>
+> - **单元测试**:全部用 mock,确保快速(ms 级)、确定性、CI 稳定。
+> - **集成测试**:用真实 DB + [`testcontainers-go`](https://github.com/testcontainers/testcontainers-go),覆盖端到端链路。
+> - **不要混用**:同一个包的测试要么全 mock,要么全真实——混合会带来难以复现的 flaky test。
+
+## 七、DI 在避免循环依赖中的作用
+
+依赖注入不仅是装配技巧,还是解决**循环依赖**的核心手段(详见 [[Go 工程模块化]] 第四节):
+
+```
+handler → service → repository → model → handler ❌
+```
+
+传统写法中,`model` 被多层共用导致循环 import。通过 DI 可以打破:
+
+```go
+// 1. domain 层只定义接口
+type UserRepository interface {
+ Save(ctx context.Context, u *User) error
+}
+
+// 2. service 依赖接口而非具体 repo
+type UserService struct {
+ repo UserRepository
+}
+
+// 3. infrastructure 层实现接口
+type PostgresUserRepo struct { db *sqlx.DB }
+
+// 4. main.go 中桥接接口与实现
+// (没有任何一层产生循环 import)
+userRepo := &PostgresUserRepo{db: db}
+userSvc := NewUserService(userRepo)
+```
+
+## 关联笔记
+
+- [[Go 工程模块化]]
\ No newline at end of file
diff --git a/hzh/GO/工程模块化/测试目录结构.md b/hzh/GO/工程模块化/测试目录结构.md
new file mode 100644
index 0000000..c9b16e4
--- /dev/null
+++ b/hzh/GO/工程模块化/测试目录结构.md
@@ -0,0 +1,143 @@
+---
+tags: [go, testing, integration-test, project-structure, engineering]
+create time: 2026-04-29 15:45
+---
+
+# 集成测试目录结构
+
+## 概述
+
+梳理 Go 项目中测试目录的组织方式,聚焦集成测试的定位、目录划分和最佳实践。明确单测与集成测试的边界,帮助团队在工程规模增长时保持测试可维护性。
+
+## 为什么需要独立测试目录?
+
+> [!question] 思考:单测不够吗?为什么还要单独建 tests/?
+
+Go 的社区惯例是**单测和源码放一起**(`xxx_test.go`),这已经覆盖了绝大多数场景。但当测试需要真实基础设施(数据库、Redis、HTTP Server)时,就需要引入独立的集成测试目录。
+
+| 维度 | 包内单测 | `tests/` 集成测试 |
+|------|---------|-------------------|
+| **视角** | 白盒——能看到 unexported | 黑盒——只调公开 API |
+| **速度** | 快(纯内存操作) | 慢(需启动依赖) |
+| **职责** | 验证函数行为 | 验证组件协作 |
+| **CI 触发** | 每次 commit 必跑 | 可单独控制,按需跑 |
+
+## tests/ 的典型结构
+
+```
+tests/
+├── integration/ # 集成测试:组件之间正确协作
+│ ├── user_test.go # 用户注册全流程(handler → service → repo → DB)
+│ └── order_test.go # 下单流程(含事务、并发安全校验)
+├── e2e/ # 端到端测试:整个服务对外可见的行为
+│ └── api_smoke_test.go # 启动 server,发 HTTP 请求做冒烟测试
+├── fixtures/ # 测试数据fixtures
+│ ├── users.json # 预置数据
+│ └── orders.json
+└── testutil/ # 集成测试专用辅助包
+ └── db.go # TestDB() 封装:创建测试库 + 自动清理
+```
+
+### 各子目录的职责
+
+```mermaid
+flowchart TD
+ A["tests/"] --> B["integration/ — 集成测试
组件协作 + 真实依赖"]
+ A --> C["e2e/ — 端到端测试
完整服务 + HTTP 调用"]
+ A --> D["fixtures/ — 测试数据"]
+ A --> E["testutil/ — 基础设施辅助"]
+
+ style B fill:#bfb,stroke:#333,stroke-width:3px
+ style C fill:#fbf,stroke:#333
+ style D fill:#fff,stroke:#333
+ style E fill:#fff,stroke:#333
+```
+
+> [!tip] 何时需要哪个?
+>
+> - **只有 unit test**:项目初期、CRUD 为主 → 不需要 `tests/`,全放包内
+> - **加了外部依赖后**:有数据库/缓存/消息队列 → 引入 `tests/integration/`
+> - **上线前质量保障**:微服务、对外 API → 增加 `tests/e2e/`
+
+## 集成测试的最佳实践
+
+### 1. 测试数据隔离
+
+每个测试用例应使用独立的数据命名空间,避免相互污染:
+
+```go
+func TestCreateOrder(t *testing.T) {
+ // 为当前测试准备干净的数据库状态
+ db := testutil.NewTestDB(t)
+ defer db.Cleanup() // 事务回滚或 truncate
+
+ svc := service.NewOrderService(db.Conn())
+ res, err := svc.Create(ctx, payload)
+ assert.NoError(t, err)
+
+ // 断言基于干净状态的预期结果
+}
+```
+
+> [!info] 两种隔离策略
+
+| 策略 | 做法 | 适用场景 |
+|------|------|----------|
+| **事务回滚** | 每件事务包裹在 BEGIN/ROLLBACK 中 | PostgreSQL/MySQL,速度快 |
+| **临时 Schema** | 每个 test 创建独立 schema | 需要测试迁移、DDL 等场景 |
+
+### 2. 避免硬编码端口
+
+集成测试需要启动服务,端口冲突是 CI 最常见的失败原因:
+
+```go
+func TestAPIIntegration(t *testing.T) {
+ // ✅ 动态分配可用端口
+ l, err := net.Listen("tcp", "127.0.0.1:0")
+ addr := l.Addr().String()
+ l.Close()
+
+ srv := &http.Server{Addr: addr, Handler: router}
+ // ...
+}
+```
+
+> [!warning] 常见坑
+>
+> - 不要写死 `8080`——本地开发可能已经在跑别的服务
+> - `net.Listener` 用 `"127.0.0.1:0"` 让内核分配空闲端口是最稳妥的方式
+
+### 3. CI 中的分级执行
+
+```yaml
+# .github/workflows/test.yml 示例思路
+- run: go test ./... # 所有单测(每次 commit)
+- run: go test ./tests/integration/... # 集成测试(push to main 或手动触发)
+- run: go test ./tests/e2e/... # E2E(release 前)
+```
+
+> [!summary] 分级执行的理由
+>
+> 集成测试和 E2E 跑起来可能几分钟甚至更久,混在常规 CI 里会拖慢开发节奏。按阶段分级,既能保证质量又不牺牲效率。
+
+## 与内部包的配合
+
+集成测试虽然放在项目根级的 `tests/` 下,但它访问的是你 `internal/` 里的代码。这里有个重要细节:
+
+```
+my-project/
+├── internal/user/service/
+│ └── service.go # package service — 对外不可见
+├── tests/integration/
+│ └── user_test.go # import "my-project/internal/user/service" — 合法!
+```
+
+> [!tip] 关键点
+>
+> - `internal/` 的限制是**对其它 module 而言**的,同一 module 内的任何位置都可以 import
+> - `tests/` 和 `internal/` 属于同一个 module,所以可以毫无阻碍地访问私有包
+> - 这意味着集成测试其实是**半白盒**的——你能看到 internal 但看不到第三方 module 的代码
+
+## 关联笔记
+
+- [[Go 工程模块化]]