1.AI 协作文档体系重构(objections/worklines/contracts+matrix.md) 2.coord 仲裁文档(final-decisions/cross-review/final-rulings/orchestration) 3.各服务 01/02 文档补全 4.共享包初始化(shared-ts/shared-go/hooks/ui-components/ui-tokens) 5.Proto 契约补全 6.004 架构影响地图更新 7.端口分配表 8.设计规格文档
16 KiB
模块理解确认书 — classes
AI:ai07(TS / 教学组织 · 黄金模板) 阶段:阶段 1 交付物 日期:2026-07-10 关联:004 架构影响地图、AI 分配方案、pending-features P1、项目规则
本确认书定位:classes 是 Edu 微服务的 黄金模板(ai-allocation.md §3.4),是其余 8 个 TS 服务的对照基准。本确认书既是对 classes 现状的审计,也是为其他 AI 提供的"读这一份即可理解黄金模板全貌"的入口。
1. 我在架构中的位置
- 层级:L5 业务微服务层(004 §3.1 六层架构)
- 业务领域:D2 教学组织领域(004 §1.1b),承载班级(Class)聚合根
- 上游(谁调用我):
api-gateway(Go/Gin):HTTP 反向代理/api/v1/classes/*→http://classes:3001/classes/*(Gateway 已注册 classes 路由块,含精确路径 + 通配路径)teacher-bff(NestJS):P2 起通过 GraphQL 聚合调用 classes 班级列表(教师仪表盘)- 未来:
student-bff、parent-bff(同样走 BFF 聚合)
- 下游(我调用谁):
MySQL(独占库classes_db,连接串DATABASE_URL,mysql2 连接池 + Drizzle ORM)Redis(依赖已声明ioredis,env 已留REDIS_URL占位;当前未启用,P3 合并入 core-edu 后启用班级列表缓存)Kafka(依赖已声明kafkajs,当前未启用;P3 起 Outbox 事件发布edu.org.class.created等)
- 通信方式:
- 入口:HTTP/REST(当前实现,端口 3001)
- 出口:MySQL(Drizzle ORM)
- 演进:proto 已定义
ClassService(5 个 RPC),P3 起 core-edu 合并 classes 后启用 gRPC server 50053
- 不持有跨服务状态:无本地内存缓存,无 WebSocket 连接
1.1 黄金模板职责
classes 除了承载 D2 教学组织领域(班级 CRUD)业务外,首要职责是作为黄金模板:
| 黄金模板职责 | 说明 |
|---|---|
| 横切关注点基准 | 错误处理 / 可观测性 / 安全 / 契约 / 测试 / 文档 / 配置 / Dockerfile 全部落地,其他 TS 服务对齐 |
| 目录结构基准 | src/{main,app.module,config,middleware,<domain>,shared} 标准结构 |
| 对照 checklist 源 | 产出黄金模板对照 checklist(见 02 架构设计 §10)供 ai01-ai10 自检 |
| 模式回写 | 各阶段新模式实现后回写本模板(Outbox 模式 P3 / 长连接 P5 / CDC P4) |
2. 我的限界上下文
2.1 我负责的聚合 / 实体
| 聚合根 | 实体 / 值对象 | 当前表 | 职责 |
|---|---|---|---|
| Class(班级) | name、gradeId、headTeacherId、description | classes |
班级 CRUD、按年级过滤、班主任归属 |
演进:P3 合并入 core-edu 后,classes 表与
subjects(学科)、enrollment(选课)同属 D2 教学组织域,由 core-edu 统一承载(见 004 §1.3 CICD→Edu 映射)。
2.2 业务领域归属
- D2 教学组织领域(004 §1.1b):classes 独占此领域(当前),P3 起与 subjects、enrollment 共同组成 D2
- 上游依赖:无业务领域依赖(教学组织是基础数据,不依赖其他业务领域)
- 下游被依赖:
- core-edu D3 教学核心域(考试/作业按班级归属)
- data-ana D6 智能洞察域(班级维度统计聚合)
- msg D5 沟通通知域(班级群组广播)
2.3 我不负责什么(边界外)
- ❌ 学科(subjects)→ core-edu(P3 新增,D2 教学组织)
- ❌ 选课 / 学生分班(enrollment)→ core-edu(P3 新增)
- ❌ 考试 / 作业 / 成绩 → core-edu(D3 教学核心)
- ❌ 教材 / 知识点 / 题库 → content(D4 内容资源)
- ❌ 站内信 / 通知投递 → msg(D5 沟通通知)
- ❌ 学情分析 / 掌握度计算 → data-ana(D6 智能洞察)
- ❌ 用户 / 角色 / 权限管理 → iam(D1 身份认证)
- ❌ JWT 校验 → api-gateway(classes 仅读
x-user-id/x-user-roles头) - ❌ 年级(grade)数据 → 当前 classes 仅持有
gradeId外键引用,年级实体归属待 core-edu P3 定义(当前由前端/管理端维护)
3. 我与外部的契约
3.1 我消费的 proto message(从 shared-proto)
- 当前不消费任何外部 proto(classes 是基础数据源,不反向依赖业务服务)
- 未来 P3 起:core-edu 合并 classes 后,消费
iam.proto的BatchGetUsers(班主任信息批量查询)
3.2 我暴露的契约
当前 REST API(已实现,见 classes.controller.ts)
| Method | Path | 权限 | 请求体 / 参数 | 响应 | 说明 |
|---|---|---|---|---|---|
| POST | /classes |
CLASSES_CREATE |
{name, gradeId, ...} |
{success, data: ClassResponse} |
创建班级 |
| GET | /classes |
CLASSES_READ |
?gradeId=<uuid>(可选) |
{success, data: ClassResponse[]} |
列表,支持按年级过滤 |
| GET | /classes/:id |
CLASSES_READ |
path: id |
{success, data: ClassResponse} |
单条查询 |
| PUT | /classes/:id |
CLASSES_UPDATE |
path:id + {name?, ...} |
{success, data: ClassResponse} |
更新(空 body 抛 ValidationError) |
| DELETE | /classes/:id |
CLASSES_DELETE |
path: id |
{success: true} |
删除(先校验存在) |
响应信封:遵循 ActionState(004 §11.5),成功
{success:true, data:T},失败{success:false, error:{code,message,details?,traceId?}}。
健康检查(见 health.controller.ts)
| Method | Path | 鉴权 | 用途 |
|---|---|---|---|
| GET | /healthz |
无 | liveness,进程存活 |
| GET | /readyz |
无 | readiness,校验 DB SELECT 1,失败 503 |
| GET | /metrics |
无 | Prometheus 指标抓取(见 main.ts 第 23-26 行注册) |
Proto 契约(见 classes.proto)
package next_edu_cloud.classes.v1;
service ClassService {
rpc CreateClass(CreateClassRequest) returns (Class);
rpc GetClass(GetClassRequest) returns (Class);
rpc ListClasses(ListClassesRequest) returns (ListClassesResponse);
rpc UpdateClass(UpdateClassRequest) returns (Class);
rpc DeleteClass(DeleteClassRequest) returns (Empty);
}
proto 完备性:5 个 RPC 与 REST 端点一一对应,已包含分页字段(
page_size/page_token),REST 实现尚未落地分页。P3 合并后由 core-edu 启用 gRPC server 50053(004 §16.7.3 #1)。
我发布的领域事件(当前未实现,P3 由 core-edu 接管)
| 事件 | 触发时机 | Topic(遵循 004 §7.2) | 消费者 |
|---|---|---|---|
ClassCreated |
班级创建成功 | edu.org.class.created |
data-ana(建班级维度宽表行) |
ClassUpdated |
班级信息变更 | edu.org.class.updated |
data-ana、msg(班主任变更通知) |
ClassDeleted |
班级删除 | edu.org.class.deleted |
data-ana、core-edu(关联检查) |
ClassTransferred |
班主任变更 | edu.org.class.transferred |
msg(通知新/旧班主任)、data-ana |
事件 message 契约:
events.proto已定义ClassEvent(event_id / aggregate_id / event_type / occurred_at / class_id / name / action / metadata),见 events.proto 第 15-24 行。Topic 命名遵循 004 §7.2edu.<domain>.<aggregate>.<action>强制格式。
4. 我的技术栈
| 维度 | 选型 | 说明 |
|---|---|---|
| 语言 | TypeScript 5.6+ | ESM 模式("type": "module"),相对 import 用 .js 后缀 |
| 框架 | NestJS 10 | 装饰器 + DI,适合 DDD 分层 |
| ORM | Drizzle ORM 0.31 | 参数化查询,类型安全,替代 TypeORM |
| 数据库 | MySQL 8(mysql2 驱动) | 连接池 connectionLimit: 10 |
| 校验 | Zod 3.23 | 输入 DTO 校验,controller 层 schema.parse(body) |
| 日志 | pino 9 + pino-pretty(dev) | 结构化 JSON,base:{service,version} |
| 指标 | prom-client 15 | 自定义 Counter/Histogram + 默认进程指标,/metrics 端点 |
| 链路 | OpenTelemetry SDK + OTLP exporter | auto-instrumentations,serviceName=classes |
| 测试 | Vitest 2.1 + @vitest/coverage-v8 | 单元测试(mock repository),阈值 60%(待提升至 80%) |
| 容器 | node:20-alpine(多阶段构建) | builder + runtime 两阶段(项目规则要求 node:22-alpine,待对齐) |
| ID 生成 | uuid v4 | 当前实现;project_rules 要求 cuid2(黄金模板待迁移) |
| 预留依赖 | ioredis、kafkajs | 已声明但未使用,为 P3 Redis 缓存 + Kafka 事件预留 |
5. 我的阶段归属
- 阶段:P1 地基阶段(已实现,黄金模板)
- 当前阶段目标(pending-features P1):classes 域 CRUD 端到端跑通 + 全横切关注点落地 + 30 分钟可复制新服务
- 依赖上游阶段产出:无(P1 是地基,classes 是最早交付的服务)
- 未来演进路径:
- P3:合并入 core-edu(ai08 负责),classes 表 + subjects + enrollment 共同组成 D2 教学组织域;启用 gRPC server 50053
- P3:补齐 Outbox 事件发布能力(
shared/outbox/目录) - P3:启用 Redis 班级列表缓存(TTL 5 分钟,事件驱动失效,见 004 §6.3)
- P3:落地 DataScope 6 级 WHERE 注入(见 004 §5.3,当前 PermissionGuard 仅做角色级校验)
6. 我需要对齐的黄金模板项(对照 classes 自身——作为基准的自我审计)
classes 是黄金模板本身,本节为自我审计,确认各项已落地,供其他 TS 服务对照。
| 检查项 | 状态 | 证据 | 待整改 |
|---|---|---|---|
权限装饰器 @RequirePermission |
✅ | permission.guard.ts + controller 全部方法装饰 | — |
错误码前缀 CLASSES_ |
✅ | application-error.ts 7 类错误码均带前缀 | — |
| logger(pino) | ✅ | logger.ts | — |
| metrics(prom-client) | ✅ | metrics.ts + /metrics 端点 |
— |
| tracer(OTel) | ⚠️ | tracer.ts 第 20 行仍用 console.log,需改 logger.info |
P1 整改 |
/healthz |
✅ | health.controller.ts liveness | — |
/readyz |
✅ | readiness 校验 DB SELECT 1,失败 503 |
— |
| 优雅关闭(SIGTERM) | ✅ | main.ts 第 31-34 行 + lifecycle.service.ts | — |
| Zod 输入验证 | ✅ | classes.dto.ts + controller schema.parse(body) |
— |
| GlobalErrorFilter | ✅ | global-error.filter.ts 捕获 ZodError + ApplicationError | — |
| Dockerfile 多阶段 | ✅ | Dockerfile builder + runtime 两阶段 | node:20→22 |
| 测试覆盖率 | ⚠️ | vitest.config.ts 阈值 60%,需提升至 80% | P1 整改 |
shared/outbox/ 目录 |
❌ | 缺失,需补齐事件发布能力 | P3 补齐 |
typeorm 冗余依赖 |
❌ | package.json 第 33 行仍声明 typeorm,已用 Drizzle,需移除 | P1 整改 |
@Req() → @Query() |
❌ | classes.controller.ts 第 49-53 行 list 方法仍用 @Req() |
P1 整改 |
| ID 生成 cuid2 | ❌ | classes.service.ts 第 1 行用 uuid v4 | P3 迁移 |
整改项汇总:4 项 P1 立即整改(tracer console.log / 测试覆盖率 / typeorm 移除 / @Req 替换)+ 2 项 P3 演进(outbox / cuid2),详见 02 架构设计 §11 整改清单。
7. 待 coord 仲裁 / 提请事项
| # | 议题 | 我的倾向 | 影响 |
|---|---|---|---|
| 1 | classes 在 P3 合并入 core-edu 后,黄金模板源码是否保留独立目录 services/classes/? |
保留(作为只读对照基准,core-edu 复制其结构) | 影响 ai08 合并策略与黄金模板 checklist 归属 |
| 2 | node:20-alpine → node:22-alpine 升级是否在 P1 立即做? |
立即做(project_rules §15.8 镜像预拉清单已是 node:22-alpine) | 影响所有 TS 服务 Dockerfile 基准 |
| 3 | cuid2 迁移时机:P1 黄金模板立即迁移,还是 P3 合并时统一迁移? | P1 立即迁移(黄金模板应先行,避免 ai08 继承 uuid 遗留) | 影响 classes 表主键、proto message 字段类型 |