Files
lislgosms/docs/sendchain-service-decomposition-plan-20260828.md

12 KiB
Raw Permalink Blame History

SendChain Service 职责拆分专项方案

  • 方案日期:2026-08-28
  • 对应整改项:C8 SendChain Service 职责拆分
  • 当前范围:只制定方案,不在本轮实施发送链重构
  • 适用环境:本地和测试环境 100.93.204.60
  • 禁止范围:未经新授权不得部署预生产;不得用生产短信验证重构

1. 当前状态与问题

当前发送链已经存在一层门面 send-submission.service.ts,但两个实现类仍然过大:

文件 当前规模 集中职责
send-inbound-entry.service.ts 约1852行、84.2 KiB CMPP认证、入站持久化、长短信、Inbox claim、企业微批、配额、模板/引流匹配、风控、指标
send-gateway-submit.service.ts 约1159行、52.2 KiB BullMQ Worker、批处理、路由、签名报备、限速、Submit命令、Outbox、任务进度、资源回收
send-submission.service.ts 约280行 对外门面、跨服务回调与编排

主要风险不是文件长度本身,而是以下行为边界在同一个类中耦合:

  • PostgreSQL事务与 advisory lock 顺序。
  • Inbox claim、租约、重试与恢复。
  • 长短信分片幂等和完整消息恢复。
  • 计费冻结、扣费、释放与失败补偿。
  • 路由、签名报备、通道限速和主备策略。
  • Submit Outbox 的租约、发布、确认和重复消费。
  • 消息状态与批量任务进度的单飞/尾随刷新。

因此专项必须以“行为锁定后机械提取”为主,不允许借拆分类的机会同时重写SQL、事务或状态机。

2. 拆分目标

  1. SendSubmissionService 只保留稳定门面和跨子域编排。
  2. 每个子服务只有一个主要变化原因,建议控制在500行以内;复杂持久化服务可放宽至700行,但不得继续混合Worker生命周期、业务校验和发布协议。
  3. 保持现有公开方法签名、队列名、Stream字段、幂等键、数据库状态和错误码不变。
  4. 拆分过程中不改变吞吐参数、路由权重、计费口径和定时器周期。
  5. 每一步均可独立回退,不要求一次性完成全部拆分。

3. 目标结构

建议新增以下内部服务,目录仍保留在 api/src/send-chain/,暂不建立更深目录,避免一次性路径迁移过大。

3.1 入站侧

目标服务 从现有文件提取的职责 明确不负责
inbound-authentication.service.ts authenticateInboundApplication、连接认证结果记录、IP白名单和接口状态检查 消息持久化、计费
inbound-long-message.service.ts 分片收集、完成判定、超时清理、已完成结果恢复 普通单条业务创建
inbound-inbox-repository.service.ts Inbox持久化、请求哈希、claim、租约续期/完成/失败 风控和模板匹配
inbound-workflow-worker.service.ts pump、企业公平分组、微批调度、停止与指标刷新 SQL细节、消息业务规则
inbound-message-preparation.service.ts 应用快照、模板/签名/引流匹配、号码频控与风控输入组装 事务写入和队列发布
inbound-message-persistence.service.ts 单条/微批消息、任务、记录、配额预留和幂等持久化 Worker生命周期、Gateway Submit

3.2 出站侧

目标服务 从现有文件提取的职责 明确不负责
send-worker-lifecycle.service.ts BullMQ Worker启动/停止、并发槽位、微批flush、队列指标 路由、计费和命令构造
message-send-processor.service.ts 单条/批量消息处理、状态迁移、失败补偿编排 Worker定时器和Outbox发布器生命周期
channel-routing.service.ts 运营商/省份识别、应用路由、候选通道、签名报备检查、主备选择 发布命令和数据库Outbox
channel-rate-limit.service.ts 通道TPS等待和相关低基数指标 路由选择
gateway-submit-command.service.ts 命令构造、开放会话ID复用、直接Stream发布契约 Outbox claim和业务状态变更
submit-outbox-publisher.service.ts Outbox claim、租约、批量发布、确认、重试和恢复 消息计费和路由
task-progress-refresher.service.ts 真实总量/状态聚合、单飞和尾随刷新 Submit或回执业务处理

3.3 共享边界

  • send-chain.contracts.ts:只保留稳定输入输出契约,不放实现函数。
  • send-chain.helpers.ts:只保留无副作用纯函数;需要数据库、Redis、时间器或日志的逻辑必须进入Service。
  • 计费继续通过现有 BillingService,不得复制金额算法。
  • 所有数据库访问保持现有 Prisma 条件和事务客户端传递方式。
  • 所有跨服务调用通过构造注入,不通过全局单例或循环引用。

4. 拆分前行为锁定

在移动代码前补齐特征测试,测试的是当前语义,不是期望中的新设计。

4.1 入站测试

  • 认证成功、未知账号、禁用应用、未认证企业、IP不在白名单。
  • 同一请求键并发两次只产生一份任务/消息/计费预留。
  • 请求键相同但payload不同返回冲突。
  • 长短信乱序、重复分片、缺片、超时、重启恢复。
  • Inbox claim不重复、租约超时可恢复、失败次数和最终状态一致。
  • 企业微批公平性、单企业失败不阻塞其他企业。
  • 模板、签名、引流匹配和人工审核关联不改变。
  • 配额预留不足时不产生半成品消息或费用。

4.2 出站测试

  • 普通批量与CMPP内部任务入队语义保持不同且正确。
  • Worker微批开启/关闭结果一致。
  • 路由优先级、weighted分流、主备切换和运营商过滤不变。
  • 签名未报备时的拒绝状态和费用释放一致。
  • 通道TPS限制不会跨通道串扰。
  • Submit命令字段、Sequence_Id、Msg_Id、Src_Id和幂等键保持一致。
  • Outbox重复claim、租约恢复、发布成功/失败、Stream重复消费结果一致。
  • 计费冻结、扣费、退款和释放只发生一次。
  • 任务进度刷新保持单飞,并在运行中出现新状态时执行一次尾随刷新。

4.3 契约快照

拆分前固定以下非敏感契约样本:

  • gateway.submit.commands
  • gateway.submit.results
  • gateway.protocol.logs
  • BullMQ send-message job data
  • Submit Outbox payload

契约测试只固定字段、类型、必填关系和幂等语义,不固定时间戳、随机ID或日志文本。

5. 分阶段实施顺序

阶段 S0:基线冻结

  • 记录Git commit、两个文件行数/哈希、95项migration状态。
  • 运行现有SendChain专项、API全量、Gateway全量、队列契约和性能基线。
  • 在隔离数据库和Redis记录测试前快照。

出口:所有既有测试通过,所有临时数据带独立前缀且可清理。

阶段 S1:提取纯路由与命令构造

先提取 channel-routing.service.tsgateway-submit-command.service.ts。这两部分事务耦合相对较低,最适合验证构造注入和门面调用方式。

规则:只移动代码;输入输出、查询条件、排序和错误文本保持不变。

阶段 S2:提取任务进度刷新与限速

提取 task-progress-refresher.service.tschannel-rate-limit.service.ts,保留单飞Map、dirty尾随集合及Redis限速键格式。

出口:并发刷新和跨通道限速特征测试通过。

阶段 S3:提取Submit Outbox

提取 submit-outbox-publisher.service.ts,将定时器生命周期和claim/publish/ack逻辑封装在同一服务;不得改变租约所有者、批量大小、轮询周期或重试条件。

出口:故障注入后无丢失、无双发,Stream最终 pending=0/lag=0

阶段 S4:提取Worker生命周期和消息处理

将BullMQ启动/停止/微批flush移入 send-worker-lifecycle.service.ts,业务处理移入 message-send-processor.service.ts

出口:Worker关闭会等待当前flush;并发槽位与处理指标不变;吞吐基线不下降超过5%。

阶段 S5:提取入站认证和长短信

先移动认证,再移动长短信。两者均有明确入口和状态表,避免直接触碰Inbox微批主流程。

出口:认证审计字段、长短信幂等和超时恢复全部一致。

阶段 S6:提取Inbox Repository和Worker

先封装原SQL与事务为Repository,再将pump/企业分组/claim循环移入Worker。Repository方法必须接受现有事务客户端,不允许内部偷偷开启嵌套事务。

出口:claim锁顺序、租约、重试、企业公平性和停机等待不变。

阶段 S7:提取消息准备与持久化

这是入站侧风险最高的阶段,最后执行。先把纯准备结果定义为显式结构,再机械移动持久化;计费操作继续调用现有BillingService。

出口:消息、任务、计费、审核任务和Inbox状态在成功与每个失败点均与基线一致。

阶段 S8:清理门面

删除仅为旧类内部跳转存在的回调,SendSubmissionService仅保留公开API编排。清理必须单独提交,不能与任何业务优化合并。

6. 每阶段验证矩阵

层级 必须执行
静态 API TypeScript、ESLint、Prettier、结构门禁、git diff --check
单元 新子服务定向测试、SendChain既有全套特征测试
API 全量Jest及全源/增量覆盖率
PostgreSQL Prisma validate、95项migration、真实事务/锁/幂等契约
Redis BullMQ、限速键、Stream、Outbox幂等和最终pending/lag
Gateway go test ./... -count=1go vet ./...、五份队列契约
故障注入 Redis短暂不可用、Gateway响应超时、DB事务失败、Worker中断恢复
性能 与S0同数据和同参数,入口延迟、完整Submit吞吐、DB池等待、队列积压

任何阶段出现以下情况立即停止并回退该阶段:

  • 重复计费、重复Submit或消息丢失。
  • 状态机出现无法收敛的新状态。
  • advisory lock顺序或事务边界变化。
  • 相同负载完整Submit吞吐下降超过5%,且无法由环境波动解释。
  • Stream pending/lag、Inbox processing或Outbox leased无法清零。

7. 提交策略

每个阶段至少拆成两个提交:

  1. test(send-chain): lock <responsibility> behavior
  2. refactor(send-chain): extract <responsibility> service

禁止在重构提交中混入:

  • SQL优化或索引变更。
  • 状态、错误码、队列字段变更。
  • TPS、批量大小、超时或重试参数调整。
  • 依赖大版本升级和全仓格式化。

8. 测试环境发布与回退

每个可部署阶段发布前都必须重新建立并校验:PostgreSQL custom dump、运行目录、环境/systemd/Nginx、Redis RDB与Stream状态、MinIO清单、部署commit和migration状态。

建议S1~S3合并为第一个测试版本,S4为第二个,S5~S6为第三个,S7~S8为最终版本。每个版本均使用独立恢复点,禁止复用旧备份冒充当前基线。

回退只允许回退本阶段部署包与对应恢复资产;如已发生业务写入,应优先代码前滚修复,不得盲目恢复数据库覆盖测试期间其他数据。

9. 工作量与决策点

阶段 预计工作量 风险
S0 行为锁定 23人日
S1S3 出站低/中耦合拆分 24人日
S4 Worker拆分 12人日 中高
S5S6 入站认证/Inbox拆分 24人日
S7S8 持久化与门面收口 24人日

总计建议按9~17人日安排,并分至少4个测试版本。开始实施前需要用户再次明确授权C8专项和测试环境发布窗口。

10. 最终关闭标准

  • 两个超大实现类被删除或只保留不超过300行的兼容转发层。
  • 每个目标服务职责单一,构造依赖和公开方法有明确边界。
  • 所有行为锁定、故障注入、真实PostgreSQL/Redis和Gateway契约通过。
  • 任务、消息、Submit、Outbox、计费和回执语义与S0一致。
  • 同口径性能基线下降不超过5%,无新增DB池等待或队列积压。
  • 测试环境发布后服务健康、错误日志为0、Stream最终 pending=0/lag=0
  • 浏览器只需验证受影响的任务/消息状态展示;不得用页面显示替代后端全链证据。