电商后台系统做微服务改造,第一步不是选框架,也不是搭注册中心,而是先把“边界”想清楚。我过去几年都在跟电商供应链和营销结算这两条线打交道,也就是常说的SPS(供应商服务系统)和CPS(按成交计费的推广结算系统),看着它们从单体Java应用一步步拆成十几个微服务,中间踩了很多坑,也总结了不少能直接落地的经验。今天就把微服务拆分过程中关于边界界定和实操技巧的部分沉淀下来,希望能帮到正在做同类改造的团队。
先说一个核心观点:微服务拆分的难点从来不在技术,而在业务边界怎么画。 画错了,后面所有的分布式事务、接口调用、数据一致性都会变成噩梦,而且越到后期越难纠正。所以这篇文章的重点会放在“怎么识别边界”和“怎么落地拆分”这两件事上,结合SPS和CPS系统的具体业务场景来拆解,顺带聊一聊拆分过程中那些文档里不会写的坑。
1. 先从业务看:SPS/CPS到底在解决什么问题
很多团队一上来就画系统架构图、定微服务清单,结果拆出来的服务跟业务是脱节的。原因很简单:没有先把业务吃透。在讨论边界之前,得先弄清楚SPS和CPS这两套系统各自的业务本质。
1.1 SPS系统的核心痛点
SPS在电商体系里一般指供应商服务系统,解决的是“货怎么进来、怎么变成可售商品、怎么跟平台同步库存价格”的问题。单体阶段这个系统通常包揽了供应商入驻、资质审核、商品资料维护、价格库存同步、采购订单管理一大堆职责。
这里有个很容易被忽略的特点:SPS系统其实是一个典型的流程管理系统,状态机特别复杂。一个供应商从申请入驻到正式供货,中间要经历资料提交、资质审核、合同签署、商品上架、供货状态变更等多个阶段。每个阶段涉及不同角色、不同数据表、不同的审批规则,而且这些流程彼此还有依赖关系。
单体应用下这种复杂度还能靠事务硬扛,但问题在于:SPS系统的一部分功能跟主站的商品中心、订单中心耦合在一起,比如供应商改了个价格,要同步到商品中心生效;采购单审核通过之后,要驱动后面的供货单创建。这些跨系统的调用在单体里是直接方法调用,拆开之后就变成了服务间通信问题,边界一旦划错,轻则同步延迟,重则数据错乱。
1.2 CPS系统的核心痛点
CPS系统解决的是“怎么把货卖出去、怎么分钱”的问题。它的业务链路是:平台招募推广渠道(达人、站长、导购平台),推广渠道通过专属链接或二维码引导用户下单,订单成交后系统根据归因规则判定这个订单归属于哪个渠道,然后计算佣金、发起结算。
CPS系统有两个鲜明的业务特征,直接决定了它的拆分方式和SPS完全不一样:
一是数据量有明显的峰值特征。大促期间订单量可能是平时的几十倍,而每一笔订单都要经过归因判断、佣金计算、数据回写这几个步骤。如果佣金计算和订单主流程耦合在一起,大促时要么把订单链路拖垮,要么把结算链路拖垮,很难两全。
二是资金敏感度极高。佣金算错一分钱,渠道那边就会来投诉,而且涉及真金白银,是没法用“最终一致性”四个字蒙混过关的。这意味着CPS系统里的资金账户、佣金明细、结算单这些模块,要有比对账、比审计更严格的数据保障机制。
1.3 单体阶段积攒的“债务”
回到真实场景,绝大多数需要拆分的SPS/CPS系统,都是经历了几年迭代的“大泥球”:
- 一个订单服务里面塞了佣金计算逻辑,一个商品服务里面塞了供应商价格同步逻辑;
- 公用一个数据库,一个表几百个字段,一半字段只有某个业务在写,其他都是“历史遗留”;
- 新人接手根本不敢动代码,因为改一个方法可能影响好几条业务线;
- 发布一次要全量回归,动辄几个小时,上线窗口只能安排在凌晨。
这些问题表面上是技术债,本质上全是边界混乱造成的。所以拆分的起点不是写代码,而是把“每块业务到底归谁管”这件事掰扯清楚。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 边界界定的方法论:别从技术出发,从业务找边界
很多团队拆微服务最喜欢按“层”来拆,比如拆出一个“接口层服务”、拆出一个“数据层服务”,或者按技术组件来拆,比如拆出“Redis服务”、“MQ消费服务”。这些都是典型的错误示范,拆出来的服务没有任何业务含义,只会让系统更碎、更难维护。
正确做法是从业务能力出发,找到那些“内部高内聚、彼此低耦合”的领域边界。
2.1 用限界上下文框定服务范围
落地方法上,DDD(领域驱动设计)里的“限界上下文”是我用过最好用的工具,但别把它想得太玄。限界上下文翻译成大白话就是:同一个业务概念,在不同场景下有不同的含义和规则,把这些不同含义和规则分开,就是限界上下文。
举个SPS系统里的经典例子。“商品”这个概念,在供应商眼里和在平台运营眼里完全是两回事。
供应商在SPS系统里维护的“商品”,本质是“供货商品”,关心的是成本价、供货状态、起订量;平台商品中心里的“商品”,本质是“销售商品”,关心的是售价、上下架状态、类目属性。单体系统里这两个概念经常混在一张表里,拆服务的时候就必须把它们拆开:SPS服务只维护“供货商品”,通过数据同步或事件通知把数据推给商品中心,商品中心再维护自己的“销售商品”。
用限界上下文来框定服务边界,核心产出是画出一张上下文映射图,标明哪个服务依赖哪个服务、通过什么方式依赖(同步接口还是异步事件)、数据归属在哪边。这张图画清楚以后,微服务清单其实已经出来了,剩下的只是把代码和表按图搬迁。
2.2 识别聚合根与数据归属
限界上下文解决的是“哪些业务放一起”的问题,聚合根解决的是“表怎么分”的问题。在关系型数据库里,表之间的外键关联是业务耦合最直接的体现。拆服务的时候有一个很实用的判断标准:如果两张表需要强事务一致,那它们必须属于同一个服务;如果只靠最终一致性就能搞定,那它们可以分属不同的服务。
具体操作时,我会带着团队做三件事:
第一,梳理出核心业务对象。比如CPS系统里,渠道、推广位、订单流水、佣金明细、结算单,这些就是核心对象。
第二,分析每个对象的一致性要求。订单流水和佣金明细之间是什么关系?某笔订单归因错误重算时,佣金明细是不是要同步更正?如果答案是需要强一致,那就说明“订单归因”和“佣金计算”这两个逻辑必须放在同一个服务内,哪怕从代码复用角度看它们“看起来”应该分开。
第三,确定数据归属。数据归谁,逻辑就归谁。比如“结算单”这个对象,只有结算服务能写,其他服务要读只能走接口或者订阅事件,绝对不能直接操作结算服务的数据库表。
2.3 判断事务边界和一致性等级
这是边界界定里最容易被低估的一环。很多拆分失败的案例,不是因为边界画错了,而是因为用了一套统一的事务方案去处理所有跨服务交互,结果处处受制,性能一塌糊涂。
我的经验是:在拆分之前,先把所有跨边界的数据交互分成三类:
- 强一致:比如资金扣减、库存扣减、佣金明细记录,这些操作失败一个就不能接受部分成功;
- 最终一致:比如商品价格同步,允许有几十秒到几分钟的延迟,但不允许永远不一致;
- 纯异步/可延迟:比如发送通知消息、生成报表数据,晚一点完全没关系。
这个分类做完之后,你会发现真正需要强一致的地方其实很少,大部分场景都可以用最终一致性来解。而那些需要强一致的场景,要么通过服务内事务解决,要么必须引入可靠的消息机制来补偿,而不是一上来就上分布式事务框架。
注意:分布式事务框架(如Seata)不是不能用,但它带来的复杂度是实打实的,而且很多场景下并不需要。先想办法把强一致的范围缩小到单个服务内,是拆分设计里性价比最高的优化。
3. 拆分落地的实操流程:从梳理到灰度迁移
边界讲完了,接下来是操作层面的东西。这里我给出一套我自己验证过相对靠谱的拆分流程,分为六个步骤,每一步都有明确的输入和产出。
3.1 第一步:梳理现状,画依赖图谱
别急着写代码,先花一到两周时间做现状梳理。具体包括:
- 把单体应用的Java代码按模块导出来,统计每个Controller、Service类的调用关系;
- 把数据库表的结构梳理一遍,标记出每张表被哪些模块读写;
- 把所有定时任务、MQ消费者、外部接口回调全部列出来,标注它们操作了哪些表、调用了哪些服务。
最终产出一张“代码模块-数据表-对外接口”的三维依赖矩阵。这张矩阵能让你非常清晰地看到:哪些模块其实是“寄生”在其他模块的数据上的,哪些模块之间的依赖是不合理的,哪些表看起来在一个库里但实际上业务关联极少。
这个阶段我强烈建议让团队所有核心开发都参与进来,因为很多隐性依赖只在老开发的大脑里,不写文档就永远沉淀不下来。当年我们梳理SPS系统时,发现一个“平时没人碰”的定时任务竟然直接改了16张表的数据,涉及商品、库存、结算三条链路,这种雷不提前排掉,拆分中后期一定会爆。
3.2 第二步:定拆分优先级,别搞大爆炸
拆分最忌讳“一步到位”。一次性把所有服务都拆出来,光是要保证一次上线所有功能正常,复杂度就足以让团队崩溃。稳妥的做法是按风险从低到高、按依赖从底到顶逐步拆分。
我常用的拆分顺序参考如下:
| 优先级 | 拆分目标 | 理由 |
|---|---|---|
| 第一批 | 纯基础类服务(如渠道管理、供应商档案) | 业务相对独立,代码量小,风险低 |
| 第二批 | 核心业务原子服务(如商品基础数据、订单流水) | 依赖方较多,需要先稳定底座 |
| 第三批 | 复杂流程服务(如佣金计算、结算) | 依赖前一阶段的服务,可基于稳定底座开发 |
| 最后 | 报表、后台管理类服务 | 只读场景多,迁移不影响核心链路 |
每拆一个服务,都要经历“代码复制、接口切流、数据双写、验证观察、老代码下线”这个过程。节奏上建议每个服务拆分周期控制在1到2周,拆完一个稳定一个,再碰下一个。
3.3 第三步:数据库拆分策略
服务拆了,数据库不拆等于白拆。数据库拆分有两种常见路线,我分别说下适用场景:
一种是先拆库、后拆服务。适合那种服务边界已经比较明确、但代码耦合较深的情况。先通过数据库中间件(如ShardingSphere)把不同业务的表分到不同物理库,代码层面还是同一个服务,等到数据层面稳定后,再搬代码、拆服务。优点是拆分风险被分散了,缺点是会有很长一段时间的“中间态”,代码里可能出现跨库事务。
另一种是先拆服务、后拆库。适合那种团队代码能力比较强、可以使用独立数据库的场景。先把服务拆开,服务间通过接口调用代替原来的直接表访问,观察一段时间,再把表按归属迁移到独立库。优点是服务逻辑先行验证,缺点是一旦出现跨库调用,排查起来会很痛苦。
我更推荐的是第二种,但前提是拆分初期要先做一个“数据访问收口”的动作:把所有跨模块的表访问先改成通过接口调用,哪怕接口内部暂时还是走同一个库,也要先隔离开。这样后续拆库只是物理上的搬运,逻辑上早就独立了。
3.4 第四步:接口设计与事件驱动改造
服务拆开后,原来的方法调用变成了网络调用,接口设计的重要性立刻凸显出来。这里我要强调两个原则:
第一,接口语义要面向业务,不要暴露内部表结构。CPS系统最容易犯的错是直接暴露“按订单号查询佣金明细”这类数据查询接口,结果就是上游服务把下游服务当成数据库来用,边界形同虚设。正确的做法是设计“计算佣金”“确认结算单”这类有业务含义的接口,把数据操作封装在服务内部。
第二,能异步就别同步。SPS系统里的价格同步是一个典型场景:供应商改价后,真的要立刻同步到商品中心吗?大多数情况不用,发一个“价格变更事件”,让商品中心自己去消费,哪怕延迟几秒用户根本感知不到。异步化之后,不仅服务间的耦合度降低了,核心链路的性能也会明显提升。
这里顺便提一个实操经验:事件驱动设计时,事件定义不要跟某个服务的内部数据结构绑定。否则一旦发送方改了字段,所有消费方都要跟着改,事件反而成了新的耦合点。事件应该定义为“业务事实”的抽象描述,比如“商品价格调整通知”,包含业务ID和必要字段,而不是“SpsProductTableUpdateEvent”这种一听就绑定表结构的命名。
4. 电商场景下拆分的三个硬骨头
下面进入真正的实战环节。SPS和CPS系统在拆分过程中,有三个场景是我认为最难处理的,分别对应“商品同步”、“佣金归因”、“结算对账”。这三个场景处理好了,系统的拆分就算成功了七成。
4.1 硬骨头一:SPS与商品中心的商品同步边界
前面提到,SPS维护“供货商品”,商品中心维护“销售商品”。两者不是简单的数据复制关系,而是有业务逻辑的转换关系:供应商填一个供货价,平台要按毛利率规则计算销售价;供应商传一个商品类目,平台要校验是否符合经营许可范围。
这个场景的边界怎么定?我建议这样划:
- SPS服务只负责采集和校验供应商提交的原始数据,产出一个“供应商商品草稿”;
- 商品中心对外提供“审核发布商品”的接口,接收SPS推送的草稿数据,执行平台侧的转换和校验规则,生成最终的销售商品;
- 两边通过MQ事件解耦,SPS发布“商品资料已提交”事件,商品中心消费后触发审核流程。
操作细节上有一个易踩的坑:商品属性字段特别多,SPS和商品中心对“同一个字段”的取值规则可能完全不同。比如“颜色”在SPS里是自由文本,在商品中心是枚举值。如果事件里直接传原始文本,商品中心就要写一套很重的转换逻辑,最后又变成边界不清。我的做法是,在SPS内部先把“字段级校验和格式转换”做完,事件里只传双方约定好的“标准格式数据”。
4.2 硬骨头二:CPS佣金归因与订单中心的边界
CPS系统怎么拿到“一笔订单是否属于某个推广渠道”?这是整个CPS系统最核心的业务逻辑,也是拆分时最容易扯皮的地方。
我见过的最糟糕的方案是把归因逻辑放在订单中心。理由听起来很合理:“订单是你们产生的,顺带算一下归属不很正常吗?”但这么做会导致订单中心被迫关心推广渠道、Cookie、设备指纹这些跟订单履约毫无关系的业务,订单链路变长,大促时直接影响下单成功率。
比较合理的拆分方案是:订单中心在订单创建成功后,把订单基础信息以事件或消息的方式推给CPS系统,CPS自己维护一套“订单归因上下文”。 具体来说,用户在点击推广链接的那一刻,CPS系统已经把“用户标识-渠道标识-归属关系”记录在自己的存储里了;当订单事件到达后,CPS拿用户标识去匹配自己的存储,完成归因。
这个方案的优点是订单中心完全不感知CPS业务,缺点是CPS需要维护一份用户点击关系数据。但从边界和性能两个角度看,这个“成本”是值得的。大促时可以单独扩容CPS的服务和存储,而不需要拖着订单中心一起横向扩展,这正是微服务拆分的核心收益之一。
4.3 硬骨头三:结算对账与资金链路的边界
CPS系统的资金链路比普通电商系统更敏感。一笔订单成交后,要经历“订单归因→佣金计算→佣金明细生成→结算单确认→财务打款”这一整条链路。中间任何一步出错,都可能引发渠道投诉。
拆服务时资金链路的边界要怎么划?我的建议是把“佣金计算”和“结算单管理”拆成两个服务。为什么?因为它们的一致性要求不同:
- 佣金计算是高频的、逐笔的,和订单事件一一对应,允许通过重算来修正;
- 结算单管理是按周期汇总的、批量的,一旦确认就不能随意改动,需要严格的审计和审批。
两者中间通过一条“佣金明细确认”的异步任务来衔接。每天定时跑一次对账任务,把当天的佣金明细和订单流水做全量比对,发现差异自动告警并触发重算流程。这个对账任务本身也可以独立成一个服务,定期扫描两边数据,确保最终一致。
资金链路的实操心得:不要迷信“消息不丢失”之类的中间件承诺,它只能保证数据“不丢”,不能保证数据“不错”。要保证资金数据不错,最可靠的还是定时对账兜底。我们上线初期有一次因为上游订单事件重复推送,导致佣金多算了一倍,靠的就是每日对账任务发现并纠正的。
5. 常见问题与排查技巧实录
拆完之后,真正的“好戏”才开始。运维阶段的很多问题,是单体应用时代根本不会遇到的。这里把我和团队踩过的坑做个系统性的汇总,按问题类型给出排查思路。
5.1 分布式事务:能不用就不用,但真要用必须选对方案
前面说过,拆分设计的目标是把强一致场景尽量收敛到单个服务内。但总有一些跨服务强一致的场景绕不开,比如“结算单确认”和“渠道账户余额变更”之间,就必须保证要么都成功,要么都失败。
这里我按经验给出几个可用方案的决策参考(我们最终用的是本地消息表加定时补偿的方案):
| 方案 | 适用场景 | 优缺点 | 实际体验 |
|---|---|---|---|
| 本地消息表 | 两个服务间的事务性消息 | 实现可控,依赖MQ可靠性 | 我们CPS系统最终选了这个,稳定可排查 |
| 事务消息(RocketMQ) | 消息发送方业务和发消息需要同事务 | 无本地表,需要中间件支持 | 适合发送方简单、消费方能幂等的场景 |
| Seata AT模式 | 跨服务数据库强一致 | 使用简单,但有性能损耗 | 小流量场景可用,大促慎用 |
| 手动补偿代码 | 必须精确控制每个步骤 | 最灵活,但开发量大 | 适合那些“每一步都要记录操作日志”的业务 |
不管选哪种方案,有几个通用原则必须坚持:消费者必须幂等、补偿任务必须可重入、每一步操作都要有操作日志和追踪ID。
5.2 数据不一致怎么兜底:对账系统是最后一道防线
微服务架构下,数据不一致是常态,你只能靠“及时发现”来降低影响。所以我在拆分SPS/CPS系统的同时,一定会同步搭建一个独立对账系统。
对账的维度不用太复杂,先把最核心的几组数据对起来:
- SPS的供应商商品数和商品中心的商品数是否一致;
- CPS的归因订单数和订单中心推送的订单事件数是否一致;
- CPS的佣金明细金额和结算单总额是否一致;
- 渠道账户余额和银行打款记录是否一致。
对账任务建议每天运行,发现差异就写入“差异工单”,由业务人员在管理后台人工确认处理。有了这层兜底,即使某个环节的最终一致性出了问题,也能在24小时内发现并纠正,不会拖到月底结算时变成无法收拾的大事故。
5.3 服务间调用的雪崩与限流
服务拆分之后,调用链变长,任何一个下游服务的抖动都可能被放大成整个系统的雪崩。CPS系统大促时尤其明显:订单量激增,佣金计算服务处理不过来,同步调用的上游服务全部阻塞,线程池耗尽,最后整个系统都卡死。
针对这个问题,有几个有效的治理手段:
- 同步改异步:能异步的调用尽量异步,减少同步阻塞点;
- 线程池隔离:给不同调用方配置独立的线程池,避免“一颗老鼠屎坏一锅汤”;
- Sentinel限流降级:对核心接口配置QPS阈值,超出后直接降级返回,保护下游;
- 缓存兜底:佣金计算涉及的渠道分成比例、商品佣金率这类配置数据,全部放缓存,防止高并发时打到数据库。
这里给一个Sentinel限流规则的简单配置示例,针对佣金计算接口,按调用方区分不同阈值:
yaml复制flow-rules:
- resource: "POST /api/cps/commission/calculate"
grade: 1 # 0: 线程数, 1: QPS
count: 2000 # QPS阈值
strategy: 0 # 0: 直接限流
controlBehavior: 0 # 0: 快速失败, 1: Warm Up, 2: 排队等待
limitApp: "order-service" # 针对order-service这个调用方单独限流
看起来很简单,但实际配置时有一个经验:限流阈值不能拍脑袋定,必须基于压测结果来定。 我们曾经把阈值设得偏高,结果大促时下游还是被打挂了;后来先对下游做了全链路压测,拿到真实容量数据再来配置,才真正起到保护作用。
5.4 从“排查困难”到“可观测”:日志与链路追踪
拆分之后,一次完整的业务请求可能要经过四五个服务。如果没有链路追踪,出了问题根本不知道从哪里查起。这个成本很多人没算过——每次线上事故排查多花一小时,一年下来积累的时间损失非常惊人。
我们的做法是在所有服务里统一接入全链路追踪组件,核心要求有两条:
第一,所有日志必须包含traceId且要打印在固定位置。排查问题的时候,只要拿到业务ID,就能在整个链路里把所有相关日志捞出来,按时间线重放。
第二,每个服务的关键业务操作要输出结构化业务日志。比如佣金计算服务里,“收到订单事件”“归因命中”“计算佣金”“写入明细”这四个关键节点都打一条日志,包含订单号、渠道ID、计算金额等核心业务字段。这样排查问题时,不需要看链路追踪平台,就能快速定位到具体环节。
实操中还有一个容易被忽视的点:日志级别要区分开,业务日志用INFO,调试细节用DEBUG,千万别把DEBUG级别的日志打到生产环境。 曾经有次大促,因为某位同事把一个循环里打印对象转化结果的日志打到了INFO级别,直接导致应用日志文件暴涨,磁盘写满,服务全部只读。排查了半天才发现是日志闯的祸。
6. 拆分之后的“收尾”工作:这步不做等于白拆
很多人觉得服务拆完、上线跑通就算大功告成,其实这只是开始。拆分之后有一段“过渡期”,如果收尾工作做不好,系统会以肉眼可见的速度重新长回去,之前的努力全部白费。
6.1 消灭“虚拟服务”:物理拆分和逻辑拆分必须同步
这里说的“虚拟服务”是指代码上已经标成独立服务,但还在共享数据库、共享缓存、共享定时任务的那些服务。这是团队为了赶进度最常走的“捷径”:把包名改了、注册中心里注册了新服务名,但实际上还在操作同一个库的同一张表。
为什么这条捷径不能走?因为共享数据库是服务间最大的隐性耦合。两个服务都能改同一张表,意味着它们之间没有任何边界可言。今天A服务加一个字段,B服务毫无感知,等到B服务上线才发现数据被A服务写坏了。而且这种问题靠测试很难发现,基本都是线上事故级别。
收尾工作的第一优先级,就是检查所有服务是否做到了“库表独享”。如果有服务还在跨库读表,必须限期整改。刚开始会很痛,但这一步不落实,后面的演进全是空中楼阁。
6.2 接口契约管理:先有契约,后有实现
服务拆开以后,消费方和提供方节奏不一致是常态。消费方等提供方开发完才能联调,整体效率会很差。改善这个问题的办法是先定义接口契约(如OpenAPI规范),提供方和消费方并行开发,最后统一联调。
契约管理还有一个额外的好处:它会强制团队把接口设计想得更清楚。写文档的时候你会考虑“这个字段真的需要暴露吗?”“这个状态值定义得够清晰吗?”而不是等代码写完再补文档。我们团队现在的要求是:拆分任何一个新服务,必须先把接口文档评审通过,再开始写服务端代码。省下来的返工时间远远超过写文档的时间。
6.3 灰度切换与回滚预案:任何时刻都要能回得去
最后一条铁律:拆分切换必须支持灰度,而且必须准备回滚预案。 不管你对新服务多有信心,一定要留退路。特别是涉及资金链路的服务,一次切换出错带来的风险远大于多花几天做灰度验证的成本。
灰度策略可以参考这个模式:先切1%的流量到新服务,观察核心指标(报错率、响应时间、对账差异数)没有异常后,逐步放大到10%、30%、50%、100%。每个阶段至少观察一个业务周期(比如一个完整的佣金计算周期),确保所有场景都覆盖到了再继续。
回滚预案至少要包含两个层面的准备:一是代码层面,老版本服务要保留到新版本彻底稳定后才能下线;二是数据层面,切换期间的双写或改造数据要设计好回滚映射,确保老服务能用旧数据跑起来。这不是小题大做——我们拆分结算服务时,就因为没设计好数据回滚映射,切换到新服务后发现问题却回退不了,硬着头皮排查了整整一个通宵。
最后再分享一点体会
回到最开头那句话:微服务拆分最大的成本是业务边界梳理的成本,而不是技术实现的成本。技术框架选型再花哨,团队能力再强,如果业务边界是糊的,拆出来的服务就是“分布式单体”,问题只会从一个形态换成另一个形态,并不会真的消失。
在我个人经验里,SPS和CPS这两个系统因为业务特性不同(一个重流程、一个重数据),拆分的侧重点也完全不同。SPS要花更多精力去梳理状态机和审批流,CPS则要花更多精力去梳理资金链路和数据一致性。但两者的共同点都是一样的:一边拆,一边强化契约、强化监控、强化对账,用机制保证边界不重新烂掉。
如果你所在团队也正在做类似的系统改造,我建议先把这篇文章里提到的“梳理依赖图谱”和“事务等级分类”这两件事做完,再谈怎么拆。这两件事做扎实了,后面的拆分过程会比想象中顺利得多。等到拆完两三个服务、走完一个完整的拆分周期,你自然会对边界有更敏锐的判断力,那时候再回头看最初纠结的那些问题,会发现大半都已经不是问题了。
