网站策划资料方案用电脑做兼职的网站比较好
网站策划资料方案,用电脑做兼职的网站比较好,策划案,网业升级坊问前言昨天在Code Review时#xff0c;我发现阿城在Service层直接返回了Result对象。指出这个问题后#xff0c;阿城有些不解#xff0c;反问我为什么不能这样写。于是我们展开了一场技术讨论#xff08;battle #x1f923;#xff09;。讨论过程中#xff0c;我发现这个…前言昨天在Code Review时我发现阿城在Service层直接返回了Result对象。指出这个问题后阿城有些不解反问我为什么不能这样写。于是我们展开了一场技术讨论battle 。讨论过程中我发现这个看似简单的设计问题背后其实涉及分层架构、职责划分、代码复用等多个重要概念。与其让这次讨论的内容随风而去不如整理成文帮助更多遇到同样困惑的朋友理解原因。知其然更知其所以然。耐心看完你一定有所收获。职责分离原则在传统的MVC架构中Service层和Controller层各自承担着不同的职责。Service层负责业务逻辑的处理而Controller层负责HTTP请求的处理和响应格式的封装。当我们将数据包装成 Result 对象的任务交给 Service 层时意味着 Service 层不再单纯地处理业务逻辑而是牵涉到了数据处理和响应的部分。这样会导致业务逻辑与表现逻辑的耦合降低了代码的清晰度和可维护性。看一个不推荐的写法:Service publicclassUserService{ public ResultUser getUserById(Long id){ User user userMapper.selectById(id); if (user null) { return Result.error(404, 用户不存在); } return Result.success(user); } } RestController publicclassUserController{ Autowired private UserService userService; GetMapping(/user/{id}) public ResultUser getUser(PathVariable Long id){ return userService.getUserById(id); } }上面代码中Service 层不仅负责从数据库获取用户信息还直接处理了返回的结果。如果我们需要改变返回的格式或者进行错误信息的标准化所有 Service 层的方法都需要修改。这样会导致代码的高耦合。相比之下以下做法将展示逻辑留给 Controller 层保证了业务逻辑的纯粹性Service publicclassUserService{ public User getUserById(Long id){ User user userMapper.selectById(id); if (user null) { thrownew BusinessException(用户不存在); } return user; } } RestController publicclassUserController{ Autowired private UserService userService; GetMapping(/user/{id}) public ResultUser getUser(PathVariable Long id){ User user userService.getUserById(id); return Result.success(user); } }让每一层都专注于自己的职责。可复用性问题当Service层返回Result时会严重影响方法的可复用性。假设我们有一个订单服务需要调用用户服务:Service publicclassOrderService{ Autowired private UserService userService; publicvoidcreateOrder(Long userId, OrderDTO orderDTO){ // 不推荐的方式:需要解包Result ResultUser userResult userService.getUserById(userId); if (!userResult.isSuccess()) { thrownew BusinessException(userResult.getMessage()); } User user userResult.getData(); // 后续业务逻辑 validateUserStatus(user); // ... } }这种写法有个很明显的问题。OrderService 作为另一个业务服务业务之间的调用本来应该简单直接但使用 Result 带来了两个问题不知道 Result 里到底包含什么还得去查看代码里面的实现写起来麻烦。还需要额外判断 Result 的状态增加了不必要的复杂度。如果是调用第三方外部服务需要这种包装还能理解但在自己业务之间互相调用时完全没必要这样做。如果Service返回纯业务对象:Service publicclassOrderService{ Autowired private UserService userService; publicvoidcreateOrder(Long userId, OrderDTO orderDTO){ // 推荐的方式:直接获取业务对象 User user userService.getUserById(userId); // 后续业务逻辑 validateUserStatus(user); // ... } }代码变得简洁且符合直觉。业务层之间直接传递业务对象保持简单和清晰。异常处理机制有些 Service 层在业务判断失败后会直接返回Result.fail(xxx)这样的代码例如public ResultVoid createOrder(Long userId, OrderDTO orderDTO){ if (userId null) { return Result.fail(用户ID不能为空); } // 后续业务逻辑 return Result.success(); }这种做法有几个问题重复的错误处理每个方法都得写一大堆类似的错误判断代码增加了代码量。错误分散错误处理分散在每个方法里如果需要改进错误逻辑要在多个地方修改麻烦且容易出错。而如果我们通过抛出异常并结合全局异常处理来统一处理错误例如publicvoidcreateOrder(Long userId, OrderDTO orderDTO){ if (userId null) { thrownew BusinessException(用户ID不能为空); } // 后续业务逻辑 }再通过全局异常捕获来转换为 ResultRestControllerAdvice publicclassGlobalExceptionHandler{ ExceptionHandler(BusinessException.class) publicResultVoid handleBusinessException(BusinessExceptione) { return Result.error(400, e.getMessage()); } ExceptionHandler(Exception.class) publicResultVoid handleException(Exceptione) { log.error(系统异常, e); // 这里可以查看堆栈信息 return Result.error(500, 系统繁忙); } }这样做的好处是减少重复代码业务方法不再需要写重复的错误判断代码更简洁。集中错误处理错误处理集中在一个地方修改时只需修改全局异常处理器不用改动每个 Service 层方法。业务与错误分离业务逻辑专注处理核心功能错误处理交给统一的机制代码更加清晰易懂。而且异常可以携带更丰富的上下文信息如果业务侧需要时可以带上堆栈信息便于一些问题的定位。测试便利性Service层返回业务对象而不是Result时能够大大提升单元测试的便利性:SpringBootTest publicclassUserServiceTest{ Autowired private UserService userService; Test publicvoidtestGetUserById(){ // 推荐的方式:直接断言业务对象 User user userService.getUserById(1L); assertNotNull(user); assertEquals(张三, user.getName()); } Test publicvoidtestGetUserById_NotFound(){ // 推荐的方式:断言抛出异常 assertThrows(BusinessException.class, () - { userService.getUserById(999L); }); } }如果Service返回Result测试代码则需要写得更复杂:Test publicvoidtestGetUserById(){ // 不推荐的方式:需要解包Result ResultUser result userService.getUserById(1L); assertTrue(result.isSuccess()); assertNotNull(result.getData()); assertEquals(张三, result.getData().getName()); }测试代码变得莫名冗长还得去关注响应结构这并不是Service层测试的关注点。Service 层本应专注于业务逻辑测试也应该直接验证业务数据。领域驱动设计角度再换个角度。从领域驱动设计DDD的角度来看Service 层属于应用层或领域层应该使用领域语言来表达业务逻辑。而 Result 是基础设施层的概念代表 HTTP 响应格式不应该污染领域层。例如考虑转账业务Service publicclassTransferService{ public TransferResult transfer(Long fromAccountId, Long toAccountId, BigDecimal amount){ Account fromAccount accountRepository.findById(fromAccountId); Account toAccount accountRepository.findById(toAccountId); fromAccount.deduct(amount); toAccount.deposit(amount); accountRepository.save(fromAccount); accountRepository.save(toAccount); returnnew TransferResult(fromAccount, toAccount, amount); } }在这个例子中TransferResult 是一个领域对象代表了转账的结果包含了与业务相关的意义而不是一个通用的 HTTP 响应封装 Result。这种做法更符合领域模型的表达体现了领域层的职责——处理业务逻辑而不是涉及 HTTP 响应格式的细节。接口适配的灵活性当 Service 层返回纯粹的业务对象时Controller 层可以根据不同的接口需求灵活封装响应RestController RequestMapping(/api) publicclassUserController{ Autowired private UserService userService; // REST接口返回Result GetMapping(/user/{id}) public ResultUser getUser(PathVariable Long id){ User user userService.getUserById(id); return Result.success(user); } // GraphQL接口直接返回对象 QueryMapping public User user(Argument Long id){ return userService.getUserById(id); } // RPC接口返回自定义格式 DubboService publicclassUserRpcServiceImplimplementsUserRpcService{ public UserDTO getUserById(Long id){ User user userService.getUserById(id); return convertToDTO(user); } } }同一个Service方法可以被不同类型的接口复用每个接口根据自己的协议要求封装响应。强行使用 Result 会导致接口的适配性变差无法根据不同协议的需求灵活定制响应格式。灵活性反而丢失了。事务边界清晰Service 层通常是事务边界所在当 Service 返回业务对象时事务的语义更加清晰Service publicclassOrderService{ Transactional public Order createOrder(OrderDTO orderDTO){ Order order new Order(); // 设置订单属性 orderMapper.insert(order); // 扣减库存 inventoryService.deduct(orderDTO.getProductId(), orderDTO.getQuantity()); return order; } }在这个例子中事务是围绕 Service 层的方法展开的Transactional注解确保在业务逻辑执行失败时事务会回滚。因为方法正常返回时事务会提交如果抛出异常事务会回滚事务的边界非常明确。如果 Service 返回的是 Result很难界定事务是否应该回滚。比如public ResultOrder createOrder(OrderDTO orderDTO){ Order order new Order(); // 设置订单属性 orderMapper.insert(order); // 扣减库存 ResultVoid inventoryResult inventoryService.deduct(orderDTO.getProductId(), orderDTO.getQuantity()); if (!inventoryResult.isSuccess()) { return Result.fail(库存不足); } return Result.success(order); }在这种情况下如果库存不足虽然 Result 返回失败信息但事务并不会回滚可能会导致数据不一致反而还得额外去抛出异常。而通过抛出异常的方式事务的回滚语义非常清晰异常抛出则回滚方法正常返回则提交这种设计确保了事务的边界更加明确避免了潜在的数据一致性问题。