81 lines
3.1 KiB
Markdown
81 lines
3.1 KiB
Markdown
|
|
---
|
|||
|
|
tags: []
|
|||
|
|
aliases:
|
|||
|
|
- 🏗️ 推荐的中间件目录演进策略
|
|||
|
|
date created: 星期三, 十二月 10日 2025, 11:08:17 上午
|
|||
|
|
date modified: 星期三, 十二月 10日 2025, 11:08:58 上午
|
|||
|
|
---
|
|||
|
|
这是一个非常有前瞻性的问题。你现在的顾虑完全符合 **“高内聚、低耦合”** 的架构直觉。
|
|||
|
|
|
|||
|
|
在企业级 Go 项目中,如果把所有中间件(Auth, CORS, RateLimit, Logger, Recovery, Trace…)都堆在 `internal/middleware/` 根目录下,确实会变成一个“大杂烩”,导致文件结构不明晰。
|
|||
|
|
|
|||
|
|
基于 **Modular Clean Architecture** 和 **Go 最佳实践**,我推荐采用 **“按复杂度分层 (Complexity-Based Layering)”** 的混合策略。
|
|||
|
|
|
|||
|
|
# 🏗️ 推荐的中间件目录演进策略
|
|||
|
|
|
|||
|
|
我们不需要一开始就过度设计(Over-engineering),而是制定一条**规则**:
|
|||
|
|
|
|||
|
|
1. **通用基础设施 (Universal Infra):** 逻辑简单、无状态、单文件即可解决的,直接放在根目录。
|
|||
|
|
2. **复杂业务逻辑 (Complex Logic):** 需要定义配置结构体、辅助函数、或依赖外部库较重的,**建立子目录**。
|
|||
|
|
|
|||
|
|
## 修正后的目录蓝图
|
|||
|
|
|
|||
|
|
```Plaintext
|
|||
|
|
internal/
|
|||
|
|
└── middleware/
|
|||
|
|
│
|
|||
|
|
│ # --- Level 1: 通用基础设施 (直接放根目录) ---
|
|||
|
|
├── recovery.go # [现有] 及其简单,就一个函数
|
|||
|
|
├── not_found.go # [现有] 就一个 Handler
|
|||
|
|
├── cors.go # [未来] CORS 配置通常单文件搞定
|
|||
|
|
├── trace.go # [未来] 注入 TraceID
|
|||
|
|
│
|
|||
|
|
│ # --- Level 2: 复杂中间件 (独立子包) ---
|
|||
|
|
├── auth/ # [未来] 认证模块很复杂
|
|||
|
|
│ ├── jwt.go # JWT 解析逻辑
|
|||
|
|
│ └── context.go # 将 UserID 注入 Context 的辅助函数
|
|||
|
|
│
|
|||
|
|
├── ratelimit/ # [未来] 限流可能涉及 Redis 脚本
|
|||
|
|
│ ├── ip_limiter.go
|
|||
|
|
│ └── lua_script.go
|
|||
|
|
│
|
|||
|
|
└── log/ # [未来] 访问日志
|
|||
|
|
└── access_logger.go
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 💡 为什么要这样设计?(Rationale)
|
|||
|
|
|
|||
|
|
## 1. Go 的包导入体验 (Import DX)
|
|||
|
|
|
|||
|
|
- 对于通用中间件:
|
|||
|
|
|
|||
|
|
我们在 main.go 里的调用通常希望长这样:
|
|||
|
|
|
|||
|
|
```Go
|
|||
|
|
// 读起来很通顺:使用中间件 -> 恢复、日志、CORS
|
|||
|
|
r.Use(middleware.Recovery())
|
|||
|
|
r.Use(middleware.Logger())
|
|||
|
|
r.Use(middleware.Cors())
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
如果强行把 `recovery.go` 放到 `middleware/recovery/recovery.go`,调用就会变成 `recovery.New()`,这在语义上丢失了“这是一个中间件”的上下文,而且引入了太多的包名。
|
|||
|
|
|
|||
|
|
- 对于复杂中间件:
|
|||
|
|
|
|||
|
|
认证通常需要配置:
|
|||
|
|
|
|||
|
|
```Go
|
|||
|
|
// 读起来清楚:这是 Auth 相关的中间件
|
|||
|
|
r.Use(auth.Middleware(auth.Config{…}))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这时候引入 `auth` 子包是合理的。
|
|||
|
|
|
|||
|
|
## 2. 避免循环依赖
|
|||
|
|
|
|||
|
|
`recovery.go` 和 `not_found.go` 属于**基础设施的底座**,它们几乎不依赖其他业务代码(除了我们刚才定义的 `app` 包)。将它们放在根目录,可以方便地被其他子包引用(虽然中间件之间很少互相引用)。
|
|||
|
|
|
|||
|
|
---
|