Spring Boot Fat Service重构:业务逻辑存放方案选型咨询
结论
两种方案不是非此即彼的对立关系,硬套任意一种单走都会留坑,正确的做法是先按业务边界拆分细粒度服务承接跨域编排逻辑,再把纯内聚无外部依赖的逻辑下沉到实体,完全适配你现有的controller > service > repository分层,不需要大改架构。
为什么不建议全量把逻辑下沉到实体
你现在的InventoryService本身已经跨了Product、Tax、Customer三类独立实体,甚至存在直接调用其他服务Repository的跨域逻辑,这类逻辑本质是流程编排类逻辑,涉及外部IO访问、多实体状态联动,根本不属于单个实体的职责范畴:
- 硬把这类逻辑塞给某个实体,会直接打破你现有纯POJO实体的简洁性,最后实体不得不注入Repository、其他Service等托管Bean,变成依赖一堆外部资源的“上帝类”,比原来的臃肿服务还难测、难维护。
- 实体的核心职责只有两个:维护自身状态的一致性、完成仅依赖自身字段的计算/校验,不该感知任何外部资源。
落地步骤(都是生产环境踩坑攒的可直接复用的实践)
第一步:先拆分细粒度服务,划清业务边界
先把200个方法全拉出来做归类,不要硬按实体名套服务名,按业务职责拆分,拆分时同步修掉跨服务直连Repository的坏味道:
- 先挑出纯库存域自身的原子操作(比如锁库存、扣减库存、释放库存、库存流水记录),留在精简后的
InventoryService里,最终这个类的方法数尽量控制在20个以内,只做库存核心能力。 - 涉及库存和商品规则联动的逻辑(比如商品上下架同步库存、商品库存阈值校验),拆到
ProductInventoryService,这个服务只允许注入自身域的Repository、核心InventoryService,不许直接访问Tax、Customer相关的表或服务。 - 涉及库存计税的逻辑(比如含库存成本的税费核算、库存报损的税费抵扣计算),拆到
TaxCalculationService,所有跨域数据都通过对应域的服务接口获取,不许直连其他域的Repository。 - 涉及客户维度的库存逻辑(比如客户专属库存、客户信用额度对应的可锁库存额度计算),拆到
CustomerInventoryService。 - 拆分后强制约束:所有服务只能直接操作自身域对应的数据库表,需要其他域的数据时,一律调用对应域的服务接口获取,从根上避免再次出现跨表乱调用的问题。
第二步:按需下沉逻辑到实体,绝不强行凑“充血模型”
只把符合以下特征的逻辑下沉到实体,其他逻辑一律留在服务层:
- 逻辑执行完全不需要访问数据库、调用外部服务
- 逻辑仅依赖当前实体自身的字段,入参都是基本类型/不可变值,不依赖其他实体的状态
举几个适合下沉的常见例子: - Product实体里的
isStockManaged():判断商品是否开启库存管理,原来服务里写的一堆判断商品状态、商品类型的if逻辑,直接挪到实体里 - Tax实体里的
getApplicableRate(BigDecimal amount):根据传入的金额匹配实体自身存储的阶梯税率,不需要查外部配置的话直接放实体 - Customer实体里的
getExclusiveStockQuota():如果客户专属库存额度是直接存在Customer表的字段,不需要关联其他表计算,直接放实体
踩过的实坑:之前有团队硬套充血模型概念,给Order实体注入了OrderRepository、PaymentService,最后一个实体类上千行,new一个Order实例都要传一堆依赖,单元测试必须启动Spring容器,维护成本比原来的臃肿服务还高。
适配现有架构的注意事项
你现在用的经典三层架构不需要做任何颠覆性调整:
- 各层之间依然用Entity做数据传递,Controller层转DTO返回的逻辑完全不用动
- 拆出来的细粒度服务依然归在service层,不需要额外引入DDD的聚合根、值对象等复杂概念,怎么简单怎么来
- 实体始终保持纯POJO,不要加任何Spring容器相关的注解,不需要被托管,new就能用
实际拆分效果参考
之前拆过一个320个方法的臃肿OrderService,场景和你完全一致:跨商品、用户、支付三个域,到处直连其他域的Repository。按上述方法拆完后:
- 核心OrderService只保留18个订单生命周期相关的原子方法
- 拆出OrderItemService、OrderDiscountService、OrderDeliveryService三个细粒度服务,各管一块业务逻辑
- 实体层总共下沉了47个零散的校验、计算方法,服务层总代码量下降62%,后续排查问题不需要在几百个方法里翻,直接按业务域找对应服务即可。
最后给个不用记复杂概念的判断标准:
- 逻辑要查库、调外部接口、联动多个实体做流程编排 → 放对应域的服务层
- 逻辑只用到当前实体自身字段,无任何外部依赖 → 下沉到实体
内容的提问来源于stack exchange,提问作者TomerMiz
相关产品推荐
相关产品推荐

