模块怎么写
统一内部形状
业务模块通常长这样:
services/{module}/
domain/ # 领域对象与核心规则
application/ # 用例编排
adapter/http/ # HTTP 适配
adapter/repo/ # 仓储实现
app.go # 模块注册入口(或 runtime 接线)
cmd/{module}/ # 抽取服务时的边界入口(可能尚未接线)
各层职责
| 层 | 做什么 | 不要做什么 |
|---|---|---|
| domain | 不变量、实体、领域错误 | 依赖 HTTP、具体 SQL 驱动细节 |
| application | 用例、事务边界、调用仓储/合约 | 直接解析 HTTP 细节散落各处 |
| adapter/http | 路由、DTO 映射、状态码 | 堆核心业务规则 |
| adapter/repo | 持久化实现 | 被其他 service 直接 import |
Runtime 接线(推荐形态)
活跃维护的模块倾向于显式 Runtime:
type Runtime
type RuntimeDeps
func NewRuntimeWithDeps(deps RuntimeDeps) (Runtime, error)
func RegisterRuntime(app *server.HTTPServer, deps platform.Deps, runtime Runtime) error
说明:
- Deps 显式传入:缺依赖在启动期失败,而不是运行期空指针
- RegisterRuntime:把 HTTP 路由挂到 monolith 的 server 上
- 旧式
Register(app, deps)可能仍存在兼容;新代码优先 Runtime 形态
contracts:模块之间的「法律」
libs/contracts 放:
- 跨模块 DTO
- 接口(端口)
- 共享错误类型与事件形状
规则:
- 允许:
services/foo→libs/contracts、libs/platform - 禁止:
services/foo→services/bar/...内部包 - 需要别的模块能力时:通过 contracts 接口 + monolith 装配时注入实现
这是后续「拆服务不改业务语义」的前提。
路由注册注意
使用 pkg.gostartkit.com/web 时:
- 同一路径深度不要用不同参数名造成冲突
- 错误示例:
/devices/:gateway_id/subdevices与/devices/:device_id/gateway参数名不一致冲突 - 改路由时补测试:走真实注册入口或 monolith 全图,捕获 router panic
平台能力复用
优先复用已有平台能力,而不是每个模块再造一遍:
| 能力 | 倾向位置 |
|---|---|
| 事件 | libs/contracts/events + 共享 bus |
| 可重试后台任务 | services/jobs |
| 组织级配置 | services/configuration |
| 文件/附件 | services/files |
| 出站 webhook | services/integration |
| 用户侧通知编排 | services/notifications |
工程优先级(写代码时)
- 正确性、安全、租户隔离、数据完整
- 公共 API 与持久化兼容
- 可测量的热点路径性能
- 简单可维护
不要用正确性换性能;非平凡性能改动应有依据(基准、分配、DB 往返等)。
自检
- 新用例落在 application,而不是 handler 里摊大饼
- 无跨 service 内部 import
- 新路由参数命名无冲突
- 需要的能力是否已有模块可注入
下一步:本地开发。