Files
patbond-doc/docs/development/iterations/iteration-2/02-backend-technical-assessment.md
T
lixi 1891d9b7b4
CI / docs-build (push) Successful in 1m3s
docs: M2 开工前分析 10 份报告入档 + ADR-009~015 拍板决策
- iteration-2 报告 01-08(PM 拆解/后端/Flutter 评估/现实核查/UI 规范/埋点规划/证据基线/Git 规划),04、06 已由正式角色复核定稿
- mkdocs 挂「第二迭代」导航,build --strict 通过
- ADR-009 新建 patbond-pet 模块、ADR-010 照片剪出 M2、ADR-011 dev 主干/master 发布、ADR-012 北极星与 H1-H4、ADR-013 废弃 health_record_action、ADR-014 DEBT-1 随 M2、ADR-015 照护人邀请后置

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-07 13:54:35 +08:00

12 KiB
Raw Blame History

Patbond 第二迭代后端技术评估(Dev)

  • 日期:2026-09-07
  • 评估范围:patbond-api 承接 M2「宠物健康档案」的改动面、建模与 API 草案、迁移规划、遗留项耦合
  • 代码基线:patbond-api 0d81c382026-09-04,工作区干净)
  • 结论先行:当前基线 82 个测试全绿(50.6s);建议 M2 在 patbond-user 内以独立包实现 pet_health 域,建模跟随 patbond-doc 目标模型(体重/疫苗强结构子表 + health_events 单表),共 7 项待拍板。

1. 现状盘点(实际读码结论)

1.1 模块与代码结构

Maven 三模块:patbond-common(错误码/响应契约/内部 DTO)、patbond-auth8081,无库,Feign 调 user)、patbond-user8082,唯一持库服务,Flyway 归属方)。

与 M2 直接相关的既有设施,全部可复用:

  • 鉴权链路patbond-userBearerAuthFilterpatbond-user/src/main/java/com/patbond/patbond/user/security/BearerAuthFilter.java)拦截 /api/v1/*RS256 本地验签后把 userId 放进 request attribute patbond.authenticatedUserIdcontroller 用 @RequestAttribute 取。宠物接口直接挂在同一过滤器下,零新增鉴权代码。
  • 异常/错误码契约ErrorCode 枚举(common+ 每服务一个 GlobalExceptionHandler{code, message, data} 信封 + 正确 HTTP 状态。扩展 = 往枚举追加值(不重编号)。
  • 数据访问:无 JPA,统一 JdbcClient + 手写 SQL(见 UserRepository),约束下沉数据库(CHECK/部分唯一索引),updated_at 由触发器维护。pet 域照此风格即可。
  • 主键:应用侧生成 UUIDv7patbond-user/src/main/java/com/patbond/patbond/user/support/UuidV7.java)。
  • 埋点挂接点EventDictionary 已预置 health_record_actionprops 白名单 recordType/actionType),M2 后端无需改埋点代码,Flutter 侧触发即可。
  • 测试设施TestcontainersPostgreSQL 18+ TestcontainersConfiguration,集成测试模式成熟,pet 域测试直接套用。

1.2 数据库现状

Flyway 链在 patbond-userV1identity/media/platform 基线)+ V2platform.product_events)。pet_health schema 尚未创建V1 只建了 platform/identity/media 三个 schema)。开发种子在 db/dev/afterMigrate__dev_seed.sql,默认不执行。

patbond-doc 目标模型(patbond-doc/docs/database/patbond_postgresql.sql 333-560 行)已给出完整的 pet_health 设计,共 8 张表:breedspetspet_ownerspet_weight_recordsvaccine_catalogpet_vaccinationshealth_eventshealth_event_mediacare_reminders。该模型已经过评审,M2 建模应以它为正典裁剪,而不是另起炉灶。

1.3 缺口

  • media 上传流程未实现:仓库中没有任何 media 相关代码(无 controller/service),media.assets 只有表。目标模型中宠物头像、疫苗证书、健康事件附件全部 FK 到 media.assets——附件能力被 media 上传流程阻塞(见待拍板 P3)。
  • marketplace schema 未建:目标模型中 pet_vaccinations.provider_id/booking_idhealth_events.provider_id/booking_id 本就未设 FK(预留列),M2 保留可空列即可,无阻塞。

2. 改动面评估

改动面 内容 量级
Flyway V3 pet_health 结构基线(从目标模型裁剪)+ V4 字典种子(若拍板引入)
新代码 pet 域 controller/service/repository/DTO(约 5 组资源) 大(M2 主体)
common ErrorCode 追加 3~4 个值;若拍板新模块则需下沉 UuidV7/BearerAuthFilter
契约 openapi.yaml 冻结新增 pets 相关 path(实现前先冻结,本评估不动契约)
既有代码 零改动(鉴权过滤器、异常处理、埋点均直接复用)
依赖 无需新增任何依赖JdbcClient + Flyway + Testcontainers 足够,不引 JPA

3. 领域建模草案

3.1 模块归属【待拍板 P1】

  • 方案 A:新建 patbond-pet Maven 模块(独立服务)。符合开发计划 4.1「按迭代增加模块,边界与 schema 对齐」的字面方向。代价:新端口/compose 服务/CI 矩阵;BearerAuthFilterJwtVerifierGlobalExceptionHandlerUuidV7 需下沉 common 或复制;Flyway 单链归属要拆(共库单 flyway_schema_history,需为新模块配独立 history 表),部署与联调面翻倍。
  • 方案 B(推荐):在 patbond-user 内新增独立顶层包 com.patbond.patbond.user.pethealth。零基础设施成本,Flyway 链自然延续(V3+),鉴权/异常/UUIDv7 直接复用。约束:包内不 import user 域内部类(只经 service 接口),SQL 只碰 pet_health schema(读 identity.users 仅限权限校验 join),保证未来抽成独立模块时是「搬包 + 拆迁移」而非重写。
  • 推荐 B:MVP 单实例共库阶段,「模块边界与 schema 对齐」用包边界 + schema 读写纪律即可兑现,把工程成本留给业务代码。

3.2 表结构草案(Flyway V3,从目标模型裁剪)

按目标模型原样建(列、CHECK、部分唯一索引、set_updated_at 触发器全保留),仅做以下裁剪调整:

M2 处置 调整点
pets 主键去掉 DEFAULT gen_random_uuid(),应用侧 UUIDv7(与 users 做法对齐);avatar_asset_id 保留可空列(media 未实现,暂不写入)
pet_owners 目标模型原样;创建宠物时自动写入 (pet_id, creator, 'owner', is_primary=true)
pet_weight_records 原样
breeds + vaccine_catalog 建(P2 拍板) 若引入:结构进 V3、种子进 V4 正式迁移(字典是生产数据,不放 db/dev);若不引入:pets 全走 custom_breed_nameck_pets_breed 约束允许),疫苗表需把 vaccine_id 放宽为自由文本——偏离目标模型,后续迁移代价大
pet_vaccinations provider_id/booking_id/certificate_asset_id 保留可空预留列,M2 不写入
health_events event_type 枚举沿用目标模型 6 值(medical/feeding/deworming/grooming/measurement/note
health_event_media 不建,推迟 依赖 media 上传流程(P3);纯增量表,后续 V5+ 补零成本
care_reminders 建(P6 拍板) 纯 CRUD,无推送

与 user/auth 的关系:pet_owners.user_id -> identity.users(id)health_events.created_by_user_id -> identity.users(id) 两个跨 schema FK,共库阶段保留(与 V1 中 media.assets.owner_user_id 先例一致)。鉴权只用 JWT 里的 userId,不新增 auth 侧改动、不新增 /internal 接口。

3.3 健康记录类型建模:单表 + type vs 每类型子表【已由目标模型定调,确认即可】

  • 纯单表(所有记录一张表 + type + jsonb):查询简单,但体重/疫苗的强约束(剂次唯一、状态-日期一致性、数值范围)全丢给应用层。
  • 纯子表(每类型一张表):表爆炸,时间线聚合要 UNION 多表。
  • 推荐(= 目标模型的混合方案)pet_weight_recordspet_vaccinations 独立强结构子表(各自的 CHECK 与部分唯一索引是业务规则本体,如「同系列同剂次未取消唯一」);其余低结构记录统一进 health_events + event_type 枚举。时间线视图由 health_events 承载,体重/疫苗页各查各表。

4. API 资源设计草案(供契约冻结参考,本评估不改 openapi.yaml

路径与开发计划 6.2 对齐,全部挂 BearerAuthFilter 强制鉴权:

接口 说明
GET /api/v1/pets 当前用户可见宠物列表(经 pet_owners join);量小,建议一次性返回不分页(契约冻结时定)
POST /api/v1/pets 创建,创建者自动 primary owner,返回 201
GET /api/v1/pets/{petId} 详情(含调用者自己的 role
PATCH /api/v1/pets/{petId} 更新,请求体带 version 乐观锁,冲突返回 409/40902
DELETE /api/v1/pets/{petId} 软删(status=deleted),仅 owner;是否进 M2 契约冻结时定
GET/POST /api/v1/pets/{petId}/weights 体重记录(GET 按 measured_at 倒序,cursor 分页)
GET/POST /api/v1/pets/{petId}/vaccinationsPATCH .../vaccinations/{id} 疫苗记录(PATCH 带 version;状态迁移 scheduled→completed/cancelled
GET/POST /api/v1/pets/{petId}/health-events 健康时间线(cursor 分页:(occurred_at, id) 复合游标,与既有索引对齐)
GET/POST/PATCH /api/v1/pets/{petId}/reminders 提醒 CRUDP6
GET /api/v1/pets/{petId}/health-summary 服务端聚合:最新体重与趋势、疫苗进度、下次接种、当月花费(P5)
GET /api/v1/breedsGET /api/v1/vaccines 字典只读接口(若 P2 拍板引入;query 参数 species

权限规则:owner 全权;caregiver 可读写记录、不可改宠物档案与成员;viewer 只读。M2 只实现 owner 路径(P4),但 repository 层权限查询按三档写好。

错误码扩展(追加进 ErrorCode,延续现有编号段):

code HTTP 语义
40300 PET_ACCESS_DENIED 403 对可见宠物无相应操作权限(如 viewer 尝试写)
40401 PET_NOT_FOUND 404 宠物不存在或调用者不可见(防 ID 枚举,见 P7
40402 RECORD_NOT_FOUND 404 宠物下的记录不存在
40902 VERSION_CONFLICT 409 乐观锁版本冲突(对应验收标准「并发更新返回明确冲突」)

幂等:开发计划 6.1 的 Idempotency-Key 强制名单(帖子/生成任务/预约)不含 pets,M2 写接口不强制幂等键;客户端重试语义靠乐观锁 + 唯一约束兜底。

5. 待拍板清单

# 事项 选项 推荐
P1 模块归属 新建 patbond-pet 模块 vs patbond-user 内独立包 user 内独立包(3.1
P2 品种/疫苗字典 引入 breeds + vaccine_catalogV3 结构 + V4 种子)vs 自由文本 引入字典,种子最小集(犬猫核心疫苗),避免偏离目标模型
P3 附件/图片 进 M2(需先实现 media 上传流程)vs 推迟 推迟出 M2;media 上传是独立工作量,不该给健康档案当前置;表列已预留
P4 共同照护人 邀请/成员管理 API 进 M2 vs 只做 owner 自动归属 只做 owner,权限校验按三档 role 实现好,邀请 API 下迭代
P5 健康汇总聚合 服务端 health-summary 接口 vs 客户端自聚合 服务端聚合(计划 M2 验收提到「从事实表聚合生成」,且跨设备一致)
P6 提醒 care_reminders CRUD 进 M2(无推送)vs 推迟 进 M2 做纯 CRUD(计划 M2 范围明确包含),推送依赖通知基础设施、明确不做
P7 无权限读取语义 403 vs 404 不可见宠物一律 404/40401(防枚举);可见但越权操作 403/40300

6. 遗留中低优先项与 M2 的耦合评估

  • access token 黑名单:与 M2 弱耦合,建议不进本迭代。宠物权限每次请求实时查 pet_owners,撤销照护关系立即生效,不依赖 token 吊销;access token 15 分钟 TTLADR-003)对健康档案的敏感级别足够。黑名单需求真正的触发点是「改密/封号即时踢出」,属身份域主题,与 pet 域实现无交集。
  • /internal 改 mTLS:与 M2 无耦合,建议不进本迭代。M2 不新增任何 /internal 接口(按 P1 推荐方案,pet 域与 user 同进程,连内部调用都没有);即使 P1 拍板为独立模块,也应沿用现有静态 service token 方案,mTLS 留给微服务化阶段(与 ADR-002 的节奏一致)。

7. 构建与测试基线(2026-09-07 实测)

命令:JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw test(系统默认 JDK 26 不可用于构建,须显式指定)。

模块 测试数 结果
patbond-common 3 通过
patbond-user 48 通过(含 Testcontainers 集成测试)
patbond-auth 31 通过
合计 82 全绿,BUILD SUCCESS,总耗时 50.6s

与第一迭代收官基线(82 测试)一致,无回归。此为 M2 开工基线:M2 结束时测试数只增不减,且该命令保持一次通过。