You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

微服务架构优化咨询:解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 02:57:50