Spring Boot服务层控制器层解耦设计模式最佳实践 聚合服务优化
持续向MyLastService注入更多依赖的实际问题
- 违反单一职责原则:这个类既要维护越来越多的依赖装配关系,又要承载订单创建的流程编排,后续每新增一个下游依赖,都要修改这个类,哪怕业务流程本身没有调整,也会额外增加无意义的代码改动量。
- 单元测试成本飙升:测试这个类的逻辑时,需要Mock所有注入的依赖,依赖数量超过5个之后,Mock配置代码的长度往往会超过核心业务逻辑的长度,后续修改测试用例的维护成本会越来越高。
- 代码耦合度失控:所有下游服务的接口调整都会直接改动这个类,比如UserService的查询方法参数变了,哪怕只是一个分支逻辑用到,也要动MyLastService的代码,类的稳定性会越来越差,改一个地方牵一发动全身。
- 核心逻辑被稀释:字段声明、重复的try-catch代码越堆越多,后续维护的人打开类之后,要翻几十行依赖声明、异常处理代码才能找到核心的业务流程,可读性直线下降。
新增分层的落地设计方法
- 先明确拆分边界:把原来MyLastService里的跨服务编排职责从基础领域服务中剥离,不要把所有调用逻辑都堆在一个实现类里。
- 三层职责划清楚:
- 原子领域服务层:就是当前的ProductService、OrderService、UserService这类,每个服务只负责自己领域内的原子操作,比如商品服务只管商品的增删改查,不注入其他领域的服务,不写跨域的流程逻辑。
- 应用编排层:这层的唯一作用就是对着具体的业务用例,调用下层的原子服务串起完整流程,不实现具体的领域规则,也不直接对接数据库或第三方接口。
- 控制器层:只负责接请求、做参数校验、调用编排层的接口返回结果,不写任何业务逻辑代码。
- 不要搞“大而全”的服务类:拆分时把每个独立业务用例对应成独立的编排类,不要把所有用例的调用都塞在同一个MyLastService里,比如“新增订单”对应CreateOrder类,“查询订单详情”对应QueryOrder类,每个类只注入自己需要的依赖。
适配场景的设计模式与SpringBoot最佳实践
- 核心适配的设计模式:
- 门面模式(Facade):把多个下游服务的调用、顺序、参数组装逻辑封装在统一的编排入口里,上层调用方(比如控制器)不需要知道内部要调哪几个服务、调用顺序是什么,只需要调用编排层的统一方法即可,完全匹配当前跨服务调用的场景。
- 用例拆分模式:每一个独立业务场景单独拆成一个类,每个类只负责当前场景的逻辑,只注入当前场景需要的依赖,从根源上避免单类依赖过多的问题。
- Spring Boot下的具体落地规则:
- 把原来的字段
@Autowired注入改成构造器注入,当类的依赖数量过多时,构造器的参数会变长,从编码层面就能直观感知到类已经膨胀了,需要拆分;而且构造器注入写单元测试的时候不需要用反射注入Mock对象,测试写起来更简单。
举个调整后的代码示例:@Service public class CreateOrderService { private static final Logger LOGGER = LoggerFactory.getLogger(CreateOrderService.class); // 只注入当前业务场景需要的服务 private final ProductService productService; private final UserService userService; private final ReferentialService referentialService; private final OrderService orderService; // 构造器注入,Spring自动完成依赖装配 public CreateOrderService(ProductService productService, UserService userService, ReferentialService referentialService, OrderService orderService) { this.productService = productService; this.userService = userService; this.referentialService = referentialService; this.orderService = orderService; } public void addNewOrder(MyRequest req) { try { Product p = productService.findProduct(req.getProductId()); User u = userService.findUserByUserName(req.getUserName()); City c = referentialService.findCity(req.getCity()); orderService.addOrder(p, u, c); } catch (Exception e) { LOGGER.error("error : {}", e.getMessage()); } } } - 服务层和控制器层解耦的通用规则:
- 控制器不要直接调用原子领域服务,所有跨服务的业务逻辑都要经过编排层,避免控制器层堆业务代码,后续逻辑调整时还要改动控制器。
- 编排层不要直接操作数据库、不要直接调用第三方接口,所有资源访问逻辑都下沉到最底层的原子服务中,保证编排层只做流程判断、参数组装,不承载具体实现细节。
- 重复的try-catch逻辑不要散落在每个业务方法里,用Spring的
@RestControllerAdvice做全局异常拦截,把通用的异常处理逻辑统一处理,减少业务方法里的冗余代码。
- 把原来的字段
内容的提问来源于stack exchange,提问作者A.khalifa
相关产品推荐
相关产品推荐

