跨服务保存如何避免半配置:一次告警规则页的前端补偿设计
导读
一个表单页面同时编辑“监控任务”和“告警规则”,看起来只需要一个保存按钮,后端却分属两个服务,无法共享本地事务。真正危险的不是接口报错,而是第一步成功、第二步失败后留下一个已经启用却没有专属规则的任务。
这篇记录最终落地的保存协议:前端预生成业务 ID,先以禁用状态创建任务,再幂等保存规则,最后才启用任务。它没有引入分布式事务框架,但把失败窗口收敛成了可重试、不会误触发的安全状态。
一张页面,两个事务边界
页面上的规则名称、告警源和布防时间属于任务服务;告警等级、阈值、弹窗和消息接收人属于规则服务。用户看到的是一张表单,保存时实际要提交两份数据:
| 数据 | 所属服务 | 失败后的影响 |
|---|---|---|
| 监控任务 | 任务服务 | 任务可能已经参与调度 |
| 告警规则 | 规则服务 | 规则可能缺失或仍是旧版本 |
两个接口各自有事务,却没有跨服务原子性。只要顺序调用,就必然存在“前一步成功、后一步失败”的窗口。
这类问题的关键不是假装两个请求原子提交,而是先回答:哪个中间状态最安全?
在这个场景里:
- “任务已启用、规则不存在”很危险,运行时可能回退到共享规则,把告警发给错误的人;
- “任务已创建但保持禁用”可以接受,它不会进入正常运行链路;
- “规则存在、任务尚未启用”也可以接受,规则只是暂时没有消费者。
所以保存协议要让所有可见的中间状态都落在后两种情况里。
最终协议:先建立安全态,再开放运行态
最终链路如下:
1 | flowchart TD |
这个顺序有三个支点。
1. ID 由前端预生成
任务创建接口的成功响应没有返回新 ID,但服务端允许调用方传入非空 ID。因此前端使用浏览器的密码学随机数 API 生成 32 位十六进制 ID,并在任务包和规则包中复用。
这样第二个请求不需要等待第一个接口“吐出”主键,也不需要用名称反查刚创建的记录。
1 | function createId() { |
2. 规则保存必须幂等
规则接口按任务 ID 执行 upsert:不存在就新增,已经存在就覆盖。关联配置采用明确的三态语义:
null:本次没有涉及,不修改旧数据;[]:用户明确清空;- 非空数组:全量替换。
因此网络超时后,前端不必猜第一次请求到底有没有成功,使用同一个任务 ID 重试即可收敛到相同结果。
3. 启用是最后一道状态迁移
任务创建时固定写成禁用。只有规则保存成功后,才根据用户选择执行启用更新。
这里还有一个实际踩坑:启用接口不能只提交 id + status。后端更新过程中还会使用布防时间等字段计算运行配置,缺字段会导致启用失败。因此最后一步提交的是完整任务快照,而不是一个看似轻量的状态补丁。
读取链路也要按同一业务键聚合
写入拆成两包,编辑回显同样需要聚合两边数据:
- 用任务 ID 查询任务详情;
- 用同一个任务 ID 分页查询专属规则;
- 查到规则后,再读取包含联动配置的完整详情;
- 将两份响应映射回一张表单。
如果规则查询漏传任务 ID,就可能读到历史共享规则。页面看似回显成功,下一次保存却会把别的配置覆盖掉。对这种“同屏跨服务”的页面,业务键不仅用于写入关联,也必须贯穿读取链路。
前端需要显式处理的失败分支
最终实现没有依赖模糊的“保存失败,请稍后重试”,而是为每一步定义了确定行为:
| 失败位置 | 系统状态 | 用户下一步 |
|---|---|---|
| 创建任务失败 | 两边都没有新数据 | 直接重试 |
| 保存规则失败 | 任务存在但禁用 | 保留表单和 ID,直接重试 |
| 最终启用失败 | 任务与规则都已保存,任务禁用 | 提示配置已保存但启用失败 |
保存按钮在请求期间进入 loading,并阻止重复提交。错误发生后不重新生成 ID,否则一次重试会变成另一组业务数据。
E2E 改写了纸面方案
最初的纸面方案倾向于“先存规则,再建任务”,因为孤儿规则不会触发业务,看上去也安全。真正接入页面后发现,任务创建、电子围栏和上游策略之间还有既有约束,单纯调换两个请求并不能覆盖最终启用语义。
浏览器 E2E 最终验证了:
- 新建和编辑使用同一个任务 ID;
- 任务、规则和关联配置能够完整保存与回显;
- 关闭通知时空数组能够清理旧配置;
- 保存期间不会重复提交;
- 任务详情、规则分页和规则详情都命中真实接口;
- ESLint、差异检查和生产构建通过。
这次最大的收获不是某个调用顺序,而是:跨服务流程的正确性必须用中间状态定义,再用真实 E2E 验证;只看最终成功页面,会漏掉最危险的失败窗口。
仍然存在的边界
当前方案解决的是应用侧任务与规则的一致性,不等于完整分布式事务。上游 AI 策略是否同步启用,仍取决于后端是否真正调用对应外部接口;本地状态变成“启用”不能自动证明上游也已生效。
如果以后流程扩展到更多服务,下一步不应继续在前端无限追加补偿请求,而应考虑由后端编排状态机、事务消息或 Saga。现阶段只有两个写服务,且每个中间状态都可安全重试,前端协议是成本更低、边界也足够清楚的方案。