微服务架构优化咨询:解决Provider与Consumer代码重复问题
架构优化方案:解决Provider与Consumer的代码重复问题
问题背景
我需要设计一个中间件服务,流程为从外部API获取数据、执行业务逻辑后返回给前端。初始架构分为三个独立服务:请求先抵达Gateway,再转发至负责业务逻辑的Provider;若需外部数据,Provider会调用Consumer获取响应。目前发现Provider(对应Service A)和Consumer(对应Service B)存在大量代码重复,请问该如何优化此架构?

现有重复代码示例
Service A(Provider)
@GetMapping("/users/{userId}") fun getUser(@PathVariable userId: String): ResponseEntity<UserResponse> { val userResponse = userService.getUser(userId) return ResponseEntity<UserResponse>(userResponse, HttpStatus.OK) } // Service层逻辑 fun getUser(userId: String) : UserResponse { return try { clientService.getUser(userId) // 这里是Provider的业务逻辑 } catch (ex: FeignException) { throw FeignException } } // Feign客户端定义 @FeignClient(...) interface UserClient { @GetMapping("/users/{userId}") fun getUser(@PathVariable userId: String): UserResponse }
Service B(Consumer)
@GetMapping("/users/{userId}") fun getUser(@PathVariable userId: String): ResponseEntity<UserResponse> { val userResponse = userService.getUser(userId) return ResponseEntity<UserResponse>(userResponse, HttpStatus.OK) } // Service层逻辑 fun getUser(userId: String) : UserResponse { return try { clientService.getUser(userId) } catch (ex: FeignException) { throw FeignException } } // Feign客户端定义 @FeignClient(url = "http://external.com") interface ExternalUserClient { @GetMapping("/users/{userId}") fun getUser(@PathVariable userId: String): UserResponse }
优化方案
1. 抽取公共基础模块(Common Library)
把重复代码抽成独立的公共Jar包,让两个服务依赖复用:
- Controller层公共逻辑:封装
BaseController,提供统一的响应封装、参数校验方法,两个服务的Controller直接继承该基类,避免重复编写响应返回逻辑。 - Service层公共逻辑:封装
BaseService,把Feign调用的try-catch异常处理、通用调用模板抽成公共方法,业务Service只需关注自身特有的逻辑。 - Feign客户端公共配置:统一配置超时、重试、拦截器规则,放在公共模块,两个服务的Feign客户端直接复用该配置。
示例:公共模块的BaseService
open class BaseService { protected fun <T> callFeignClient(action: () -> T): T { return try { action() } catch (ex: FeignException) { // 统一封装成自定义业务异常抛出,避免重复处理逻辑 throw BusinessException("外部服务调用失败", ex) } } }
Service A、B的Service层继承后复用:
// Service A的Service class UserService : BaseService() { fun getUser(userId: String): UserResponse { val externalUser = callFeignClient { userClient.getUser(userId) } // 执行Provider特有的业务逻辑 return convertToBusinessResponse(externalUser) } } // Service B的Service class ExternalUserService : BaseService() { fun getUser(userId: String): UserResponse { return callFeignClient { externalUserClient.getUser(userId) } } }
2. 调整架构职责,精简冗余服务
从现有代码看,Consumer(Service B)仅做外部API的转发,无额外业务逻辑,可考虑:
- 直接让Provider调用外部API,去掉独立的Consumer服务,把外部API调用逻辑封装成客户端模块(而非独立服务),让Provider依赖该模块即可,减少服务拆分的冗余。
- 如果后续Consumer需要扩展多外部API聚合逻辑,再保留服务,但复用公共模块的Controller、Service基础逻辑,只保留自身特有的聚合逻辑。
3. 复用API与DTO定义
把公共的API接口注解、请求响应DTO(如UserResponse)抽入公共模块的API包,两个服务的Controller直接引用这些定义,避免重复编写相同的接口声明和数据模型。
4. 统一全局异常处理
在公共模块中实现@ControllerAdvice全局异常处理器,统一处理Feign调用异常、参数校验异常等,两个服务依赖公共模块后,无需各自编写异常处理代码。
总结
优先通过抽取公共模块解决代码重复问题,同时根据业务规划评估Consumer的必要性:若仅做简单转发,合并到Provider的客户端模块更精简;若需承担多外部API聚合职责,保留服务但复用公共逻辑即可。
内容的提问来源于stack exchange,提问作者Wiki
相关产品推荐
相关产品推荐

