3. 服务识别与划分:业务域分析、服务粒度控制、服务分层

好,咱们进入第三章。这一章我打算聊聊服务识别与划分。说实话,这是整个SOA架构设计里最考验经验的一环。你想想看,服务划得太大,就成了单体应用的变种;划得太碎,又变成分布式泥潭。我见过不少项目,就是因为这一步没走好,后面怎么调都别扭。

3.1 业务域分析:先别急着写代码

我个人习惯,拿到一个新能源项目,第一件事不是画架构图,而是拉上业务方一起做业务域分析。说白了,就是搞清楚「这辆车/这个充电桩/这个储能系统,到底要干哪些事」。

业务域分析的核心是限界上下文(Bounded Context)。这个概念来自DDD,但在SOA里同样好用。我举个例子:

在新能源整车架构里,我们通常可以划分出这几个业务域:

  • 动力域:电机控制、能量回收、扭矩分配
  • 底盘域:制动、转向、悬架
  • 车身域:门窗、灯光、座椅
  • 座舱域:仪表、中控、语音交互
  • 智驾域:感知、规划、控制

每个域内部,业务逻辑是高度内聚的。域与域之间,通过定义好的接口通信。嗯,这里要注意:不要跨域直接访问数据库。我在项目中遇到过,有人为了图方便,在座舱域里直接查动力域的表,结果后来动力域改了表结构,座舱域崩了一周。

核心原则:业务域是服务划分的第一道边界。每个域对应一个或多个服务,但服务不能跨域。

3.2 服务粒度控制:粗了不行,细了也不行

服务粒度这个问题,我估计每个架构师都纠结过。到底一个服务该多大?

我的经验是:服务粒度 = 业务变更频率 × 团队规模。什么意思呢?

  • 如果一个业务逻辑经常变,那就把它拆成独立服务。比如充电策略,不同车型、不同地区、不同季节,策略都不一样。拆出来单独迭代,不影响其他服务。
  • 如果一个团队只有3个人,却维护了20个微服务,那运维成本会压垮你。我建议一个服务对应一个团队,或者至少一个服务对应一个明确的职责。

我给大家一个参考粒度:

服务类型 粒度示例 说明
过粗 整车控制服务 包含所有控制逻辑,改一个功能要全量发布
适中 电池管理服务、电机控制服务 每个服务聚焦一个子系统,独立迭代
过细 单体电芯电压采集服务 服务太多,通信开销大于业务逻辑本身

我曾经在一个项目里,把「充电桩状态上报」拆成了「电压上报」「电流上报」「温度上报」三个服务。结果呢?每次充电桩上报一次数据,要调三个接口,网络延迟翻了三倍。后来我学乖了,把高频一起变化的数据,合并到一个服务里

避坑指南:我曾经把「车辆定位」和「路径规划」拆成两个服务。后来发现,每次定位更新都要触发路径重算,两个服务之间通信频繁到爆炸。最后合并成一个「导航服务」,反而更清爽。

3.3 服务分层:基础服务 / 业务服务 / 编排服务

服务分层,说白了就是让不同职责的服务各司其职。我习惯把服务分成三层:

3.3.1 基础服务

基础服务是底层能力,不包含任何业务逻辑。比如:

  • 日志服务
  • 配置管理服务
  • 认证鉴权服务
  • 消息推送服务

这些服务的特点是:通用、稳定、复用性高。你想想看,不管你是做充电桩还是做储能,日志总得记吧?认证总得做吧?所以基础服务一般由平台团队统一维护。

3.3.2 业务服务

业务服务是核心,承载了具体的业务逻辑。比如:

  • 电池健康评估服务
  • 充电策略调度服务
  • 车辆远程诊断服务

每个业务服务对应一个具体的业务能力。我建议一个业务服务只做一件事,并且把它做好。比如电池健康评估服务,它只负责根据电芯数据计算SOH(健康状态),不负责存储,也不负责展示。

3.3.3 编排服务

编排服务是「指挥家」。它不干具体活,而是协调多个业务服务完成一个复杂流程。比如:

  • 充电流程编排服务:调用「鉴权服务」→「充电策略服务」→「充电控制服务」→「计费服务」
  • OTA升级编排服务:调用「版本管理服务」→「下载服务」→「安装服务」→「回滚服务」

编排服务的存在,是为了避免业务服务之间直接耦合。你想想看,如果充电流程里,充电控制服务直接调用了计费服务,那以后计费逻辑变了,充电控制也得跟着改。有了编排层,每个业务服务只知道自己该干什么,不需要知道别人在干什么

分层的好处:
  • 基础服务稳定,很少改动
  • 业务服务聚焦,容易维护
  • 编排服务灵活,可以快速调整流程

3.4 实战案例:充电桩远程启动服务

光说理论太干,我拿一个实际场景来串一下。假设我们要设计一个「充电桩远程启动」服务。

第一步:业务域分析

这个功能属于「充电业务域」。充电业务域下还有「充电桩管理」「用户管理」「计费管理」等子域。远程启动属于「充电桩管理」子域。

第二步:服务粒度控制

我一开始想,要不要把「远程启动」「远程停止」「远程预约」都放在一个服务里?后来一想,这三个功能的变更频率不一样:

  • 远程启动:基本稳定
  • 远程停止:基本稳定
  • 远程预约:经常变(不同活动、不同优惠策略)

所以我把「远程预约」拆成了独立服务,而「启动」和「停止」放在一起。这样预约策略变了,不影响启动和停止的稳定性。

第三步:服务分层

  • 基础服务:认证鉴权服务(验证用户身份)、消息推送服务(推送启动结果到手机App)
  • 业务服务:充电桩控制服务(执行启动指令)、充电桩状态服务(查询当前状态)
  • 编排服务:远程启动编排服务(先鉴权→再查状态→再执行启动→最后推送结果)

你看,这样分层之后,每个服务职责清晰。如果以后要加一个「启动前检查车辆是否已插枪」的逻辑,只需要在编排服务里加一步,不需要改任何业务服务。

注意:编排服务不要包含业务逻辑。它只负责「调谁」和「按什么顺序调」,不负责「怎么调」。我曾经见过有人把业务判断写在编排层,结果编排服务越来越臃肿,最后变成了「超级编排」,反而成了单点故障。

3.5 小结

服务识别与划分,没有标准答案。但有几个原则,我建议你记住:

  1. 先做业务域分析,搞清楚边界再动手
  2. 服务粒度要适中,高频一起变化的数据放一起
  3. 服务分层要清晰,基础服务、业务服务、编排服务各司其职

嗯,这一章就到这里。下一章我会聊聊服务接口设计,包括RESTful和gRPC怎么选、接口版本怎么管理。到时候见。