Feign Reactive:访问REST API的首选


这是关于如何为第三方 API 集成实现 Feign Reactive 的分步指南。
使用Feign Reactive而不是WebClient 作为 REST API 消费客户端。

Spring WebClient 是一个用于发出 HTTP 请求的非阻塞响应式客户端。OpenFeign是一个流行的框架,它可以帮助我们轻松地创建带有注释的声明式 REST 客户端。在微服务架构中调用外部服务时,它提供了更好的抽象。
Feign Reactive是 Feign 在 Spring WebClient 上的实现。它为我们带来了两全其美:Feign 的简洁语法在 Spring WebClient 的快速、异步和非阻塞 HTTP 客户端上编写客户端 API。
为了演示使用 Feign Reactive 使用 REST API,我们将构建一个 Spring Boot 应用程序客户服务客户端来使用另一个 Spring Boot 应用程序客户服务提供的 REST API,该服务对客户执行 CRUD 操作。
在 customer-service-client 中实现 Feign Reactive 的详细步骤概述如下:

第一步:在 pom.xml 中添加依赖:

<dependency>
    <groupId>com.playtika.reactivefeign</groupId>
    <artifactId>feign-reactor-spring-cloud-starter</artifactId>
    <version>3.2.6</version>
    <type>pom</type>
</dependency>

第 2 步:客户端声明
Feign 的声明性本质体现在下面CustomerServiceClient,一个 Feign 响应式客户端到客户服务 API。

@ReactiveFeignClient(
        name = "customer-service",
        url =
"${customer-service.urls.base-url}",
        configuration = CustomerClientConfig.class
)
public interface CustomerServiceClient {

    @PostMapping(value =
"${customer-service.urls.create-customer-url}", headers = {"Content-Type=application/json"})
    Mono<CustomerVO> createCustomer(@RequestBody CustomerVO customerVO);

    @PutMapping(value =
"${customer-service.urls.update-customer-url}", headers = {"Content-Type=application/json"})
    Mono<CustomerVO> updateCustomer(@PathVariable(
"customerId") String customerId,
                                    @RequestBody CustomerVO customerVO);

    @GetMapping(value =
"${customer-service.urls.get-customer-url}")
    Mono<CustomerVO> getCustomer(@PathVariable(
"customerId") String customerId);

    @DeleteMapping(value =
"${customer-service.urls.delete-customer-url}")
    Mono<Void> deleteCustomer(@PathVariable(
"customerId") String customerId);
}

上面第1行的注解@ReactiveFeignClient

Spring Boot主类CustomerServiceClientApplication中下面第2行的注解@EnableReactiveFeignClients。
是使这个接口成为Feign Reactive客户端
这两个注解都使Feign Reactive在我们的customer-service-client 中具有活力。

@EnableReactiveFeignClients
@SpringBootApplication
public class CustomerServiceClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(CustomerServiceClientApplication.class, args);
    }
}


第三步:application.yml
Feign 的声明性也体现在我们的application.yml中,我们为客户服务 API 集成定义了一组配置细节(示例配置如下):

customer-service:
  http-client:
    read-timeout: 3000
    write-timeout: 3000
    connect-timeout: 3000
    response-timeout: 3000
  retry:
    max-retry: 3
    retry-interval: 3000
  urls:
    base-url: http://customer-service-url:8500/
    create-customer-url: /customers
    update-customer-url: /customers/{customerId}
    get-customer-url: /customers/{customerId}
    delete-customer-url: /customers/{customerId}
    
logging:
  level:
    com.github.wenqiglantz.service.customerserviceclient.customerclient: TRACE


第 4 步:Feign 响应式配置
结合application.yml上面定义的内容,我们可以在类中配置 API 与客户服务集成的一些横切关注点CustomerClientConfig。请参阅下面的详细信息。我们正在配置以下区域:

  1. 日志记录(下面的第 26-29 行,以及application.yml上面的第 17-19 行)
  2. 重试策略(下面的第 31-34 行)
  3. 错误处理(下面的第 36-41 行)
  4. 超时(下面的第 49-57 行)

@Configuration
@ConfigurationProperties
@Indexed
@Data
@Slf4j
public class CustomerClientConfig {

    @Value("${customer-service.http-client.read-timeout}")
    private int readTimeout;

    @Value(
"${customer-service.http-client.write-timeout}")
    private int writeTimeout;

    @Value(
"${customer-service.http-client.connect-timeout}")
    private int connectTimeout;

    @Value(
"${customer-service.http-client.response-timeout}")
    private int responseTimeout;

    @Value(
"${customer-service.retry.max-retry}")
    private int maxRetry;

    @Value(
"${customer-service.retry.retry-interval}")
    private int retryInterval;

    @Bean
    public ReactiveLoggerListener loggerListener() {
        return new DefaultReactiveLogger(Clock.systemUTC(), LoggerFactory.getLogger(CustomerClient.class.getName()));
    }

    @Bean
    public ReactiveRetryPolicy reactiveRetryPolicy() {
        return BasicReactiveRetryPolicy.retryWithBackoff(maxRetry, retryInterval);
    }

    @Bean
    public ReactiveStatusHandler reactiveStatusHandler() {
        return ReactiveStatusHandlers.throwOnStatus(
                (status) -> (status == 500),
                errorFunction());
    }

    private BiFunction<String, ReactiveHttpResponse, Throwable> errorFunction() {
        return (methodKey, response) -> {
            return new RetryableException(response.status(),
"", null, Date.from(Instant.EPOCH), null);
        };
    }

    @Bean
    public ReactiveOptions reactiveOptions() {
        return new WebReactiveOptions.Builder()
                .setReadTimeoutMillis(readTimeout)
                .setWriteTimeoutMillis(writeTimeout)
                .setResponseTimeoutMillis(responseTimeout)
                .setConnectTimeoutMillis(connectTimeout)
                .build();
    }
}

使用 WireMock 进行测试
WireMock是一个用于存根和模拟 Web 服务的库。它构建了一个我们可以连接到的 HTTP 服务器,就像我们连接到一个实际的 Web 服务一样。当 WireMock 服务器运行时,我们可以设置预期、调用服务并验证其行为。


概括
我们在这个故事中探索了 Feign Reactive。Feign 的简洁语法结合了两个世界的优点,在 Spring WebClient 的快速、异步和非阻塞 HTTP 客户端上编写客户端 API,Feign Reactive,为任何 REST API 消费提供了完美的解决方案。我们深入探讨了如何在 REST API 客户端应用程序中实现 Feign Reactive。
我们还探讨了如何使用 WireMock 测试 Feign Reactive 实现。我希望这个故事对你有所帮助,我鼓励你在下一个 REST API 客户端项目中尝试 Feign Reactive。

源代码可以在我的GitHub 存储库中找到。