Files
conti-docs/backend/05-integration-layer.md
T
Guangfei.Zhao 1e0cbb86a2 feat: Add comprehensive documentation for integration layer, API design, config governance, observability, build/deploy, and testing strategies
- Introduced integration layer design with Resilience4j for external vendor calls.
- Established API design standards with unified response structures and global exception handling.
- Defined configuration and service governance using Kubernetes native solutions.
- Implemented observability practices including trace ID propagation and structured logging.
- Outlined build and multi-environment deployment strategies using Gradle and GitLab CI/CD.
- Specified testing strategies across different layers, utilizing JUnit, MockK, Testcontainers, and WireMock.
2026-08-12 18:23:11 +08:00

6.5 KiB
Raw Blame History

05. 集成层设计(F6 / Mini 域)

决策

供应商(F6)和历史 Mini 域的调用统一收口在 f6-integration / mini-clients 模块,业务 domain 不直接持有 WebClient 或 HTTP 客户端;用 Resilience4j 统一管理超时、重试、熔断。

结构约定

platform-integration/
  WebClientConfig             # 统一封装 WebClient(连接池、超时基线配置)
  Resilience4jDefaults          # 超时/重试/熔断的公共默认配置

domains/f6-integration/
  负责:换票、供应商访问上下文准备、超时/重试/熔断策略、异常转换为内部标准错误码

domains/mini-clients/
  对 O2O/Warranty/Retail Store/ROOS 的只读客户端封装,供 workbench / bff-orchestration 调用

WebClient + Resilience4j 配置示例

# application.yml
resilience4j:
  timelimiter:
    instances:
      f6-api:
        timeout-duration: 2s
      mini-o2o:
        timeout-duration: 1s
  retry:
    instances:
      f6-api:
        max-attempts: 2
        wait-duration: 200ms
        retry-exceptions:
          - java.net.SocketTimeoutException
          - org.springframework.web.reactive.function.client.WebClientRequestException
  circuitbreaker:
    instances:
      f6-api:
        sliding-window-size: 20
        failure-rate-threshold: 50
        wait-duration-in-open-state: 10s
        permitted-number-of-calls-in-half-open-state: 5
// domains/f6-integration/.../F6ApiClient.kt
@Component
class F6ApiClient(
    private val webClient: WebClient, // 来自 platform-integration 的统一封装
) {
    @CircuitBreaker(name = "f6-api", fallbackMethod = "fallbackProcurementList")
    @Retry(name = "f6-api")
    @TimeLimiter(name = "f6-api")
    fun fetchProcurementList(storeId: Long): Mono<ProcurementListResponse> =
        webClient.get()
            .uri("/f6/procurement/list?storeId={storeId}", storeId)
            .retrieve()
            .onStatus({ it.isError }) { resp ->
                resp.bodyToMono(String::class.java)
                    .map { body -> F6IntegrationException("F6 采购列表调用失败: ${resp.statusCode()} $body") }
            }
            .bodyToMono(ProcurementListResponse::class.java)

    // Resilience4j 约定:fallback 方法签名 = 原方法参数 + Throwable,返回类型一致
    fun fallbackProcurementList(storeId: Long, ex: Throwable): Mono<ProcurementListResponse> =
        Mono.just(ProcurementListResponse.degraded())
}
// 统一异常转换:F6IntegrationException -> 内部标准错误码,业务层不感知供应商原始协议
class F6IntegrationException(message: String) : RuntimeException(message)

@RestControllerAdvice
class F6ExceptionHandler {
    @ExceptionHandler(F6IntegrationException::class)
    fun handle(ex: F6IntegrationException): ResponseEntity<ApiResult<Nothing>> =
        ResponseEntity.status(HttpStatus.BAD_GATEWAY)
            .body(ApiResult.error(code = "F6_UNAVAILABLE", message = "供应商服务暂不可用,请稍后重试"))
}

Mini 域客户端示例(内部系统,策略更宽松)

// domains/mini-clients/.../O2OClient.kt
@Component
class O2OClient(private val webClient: WebClient) {

    @TimeLimiter(name = "mini-o2o") // 只兜底超时,不需要熔断(内部系统,稳定性相对可控)
    fun fetchOrderSummary(storeId: Long): Mono<OrderSummary> =
        webClient.get()
            .uri("/o2o/orders/summary?storeId={storeId}", storeId)
            .retrieve()
            .bodyToMono(OrderSummary::class.java)
            .onErrorResume { Mono.just(OrderSummary.empty()) } // 局部降级,见 workbench 聚合规则
}

关键规则

  • F6 是外部供应商域,稳定性不可控,必须配置超时 + 重试 + 熔断,且熔断后要有降级返回(fallbackXxx 方法),不能让异常直接穿透到 APP。
  • 异常统一转换:F6 / Mini 域返回的异常或非标准错误,在 f6-integration / mini-clients 内部转换成内部标准错误码(如 F6_UNAVAILABLE),业务 domain 和最终 API 响应都不暴露供应商侧的原始协议细节。
  • Mini 域调用相对可控(内部系统),熔断策略可以比 F6 宽松(示例里只加超时兜底),但仍需要超时兜底,避免慢查询拖垮 workbench 聚合——对应架构图 Flow 3 "首页失败按 tile 降级"的要求。
  • 业务 domain(如 workbench)只依赖 mini-clients / f6-integration 暴露的接口,不自己 new WebClient 发请求。

附录:超时、重试、熔断分别解决什么问题

三者经常被一起提,但作用点不同,配置的时候容易搞混:

  • 超时(Timeout:解决"对方一直不回应,我方请求线程/连接被一直占着"的问题。没有超时,一个慢下游能拖垮整个调用方的线程池。这是三者里最基础、必须有的一道防线。
  • 重试(Retry:解决"这次失败大概率是偶发的(网络抖动、瞬时过载)"的问题。重试的前提是幂等——fetchProcurementList 这种 GET 查询可以放心重试,但如果是"扣库存""创建订单"这类有副作用的调用,重试前要先确认接口本身幂等(比如带幂等 key),否则重试可能造成重复下单这类更严重的问题。
  • 熔断(Circuit Breaker:解决"对方已经持续故障,继续重试只是在浪费资源、拖慢自己"的问题。熔断器统计一个滑动窗口内的失败率,超过阈值后直接短路请求(进入 OPEN 状态,一段时间内不再真的发请求,直接走 fallback),过一段时间放几个探测请求(HALF_OPEN)判断对方是否恢复。

三者组合的顺序也有讲究:一次调用先看熔断器状态(OPEN 直接 fallback,不发请求)→ 没熔断就真的发请求 → 超时控制这次请求最多等多久 → 超时或失败了再看要不要重试。上面 Resilience4j 的注解顺序(@CircuitBreaker 在最外层,@Retry@TimeLimiter在内层)就是按这个语义叠加的。

待补充

  • 具体超时/重试参数需要结合 F6 实际 SLA 压测后调整,示例里的数值是起点,不是最终值。
  • 熔断后降级返回的数据结构约定(degraded() 具体字段)。
  • F6 换票具体协议细节(对接 webview-ticket 的会话失效联动)。

参考链接