Spring Cloud Feignによる声明型HTTPクライアントの実装

  1. Feignの概要

1.1 基本概念 FeignはNetflixが開発した、宣言型のREST APIクライアントです。Spring Cloudでは、リバウンディング(Ribbon)とフォールバック(Hystrix)を統合し、微サービス間通信を簡素化しています。従来の手動設定や重複コードの削減により、アプリケーション開発の負担を大幅に軽減します。この仕組みは、Spring Bootが従来のSpringフレームワークを簡略化したのと同様に、開発者の生産性向上を目的としています。

1.2 FeignとRibbonの関係 Ribbonは、クライアント側でロードバランシングを行うためのツールであり、HTTP/TCPベースのリクエスト処理をサポートします。一方、Feignはその上位レイヤーとして、インターフェース定義とアノテーションによってリモートサービスの呼び出しを宣言的に実現します。これにより、RestTemplateなどでの手動リクエスト構築が不要となり、まるでローカルメソッド呼び出しのように自然なコード記述が可能になります。

1.3 ロードバランシングの自動適用 Feignは内部的にRibbonを統合しており、追加の依存関係やRestTemplateの登録は不要です。サービスディスカバリ(Eurekaなど)との連携も自動で処理されるため、開発者はシンプルな設定のみで分散環境での通信が実現できます。

  1. 主な機能 Feignは、微サービス間の通信を宣言型で記述できるWebサービスクライアントです。コントローラからサービスを呼び出すように、他のサービスのエンドポイントを簡単に呼び出せます。Spring Cloudでは、Eurekaと連携して自動的なロードバランシングを提供します。

  2. 動作確認手順

3.1 サービスレジストリの構築 依存関係の追加:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

application.yml設定:

server:
  port: 8080

spring:
  application:
    name: eureka-server

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

起動クラス:

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

ブラウザで http://localhost:8080 にアクセスすると、レジストリ管理画面が表示されます。

3.2 サービスプロバイダの作成 依存関係:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

application.yml:

server:
  port: ${port:8010}

spring:
  application:
    name: provider

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8080/eureka/

APIコントローラ:

@RestController
public class HelloController {

    @Value("${server.port}")
    private String port;

    @GetMapping("/hi")
    public String hi() {
        return "hi~ my port === " + port;
    }

    @GetMapping("/hiWithTimeOut")
    public String hiWithTimeOut() throws InterruptedException {
        Thread.sleep(10000);
        return "hi~ my port === " + port;
    }
}

2つの異なるポート(8010, 8011)でインスタンスを起動することで、ロードバランシングの動作確認が可能です。

3.3 サービスコンシューマの実装 Maven依存関係:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

application.yml:

server:
  port: 8082

spring:
  application:
    name: consumer

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8080/eureka/

feign:
  hystrix:
    enabled: true
  client:
    config:
      provider:
        loggerLevel: FULL
        connectTimeout: 2000
        readTimeout: 5000
      default:
        connectTimeout: 2000
        readTimeout: 3000

logging:
  level:
    com.ldx.consumer.service.HelloService: debug

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
      isolation:
        thread:
          timeoutInMilliseconds: 50000

起動クラス:

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

Feignインタフェース:

@FeignClient(name = "provider", fallback = HelloFallbackImpl.class)
public interface HelloService {
    @GetMapping("/hi")
    String hi();

    @GetMapping("/hiWithTimeOut")
    String hiWithTimeOut();
}

フォールバック実装:

@Component
public class HelloFallbackImpl implements HelloService {
    @Override
    public String hi() {
        return "リモートサービスが利用不可です。後でもう一度お試しください。";
    }

    @Override
    public String hiWithTimeOut() {
        return "リクエストがタイムアウトしました。";
    }
}

コントローラ:

@RestController
public class HelloController {
    @Resource
    private HelloService helloService;

    @GetMapping("/hi")
    public String hi() {
        return helloService.hi();
    }

    @GetMapping("/hiWithTimeOut")
    public String hiWithTimeOut() {
        return helloService.hiWithTimeOut();
    }
}

サービスプロバイダを停止した状態でリクエストを送信すると、フォールバック処理が動作し、期待通りの応答が返却されます。

  1. Hystrixとの統合 Feignは、Hystrixによるフォールバック機能を標準で提供します。@FeignClientfallback属性に実装クラスを指定することで、ネットワーク障害時のデフォルト応答を定義できます。また、fallbackFactoryを使用すれば、例外の詳細情報を取得でき、より精密なエラーハンドリングが可能です。

※ Spring Cloud Hoxton以降のバージョンでは、Hystrixのサポートが非推奨となっています。代替としてResilience4Jが推奨されています。

  1. カスタムエラーデコーダー ErrorDecoderインターフェースを実装することで、レスポンスステータスコードに応じたカスタムエラー処理を実現できます。例えば、4xxや5xxステータスに対して特定のメッセージを返すような処理が可能です。
@Configuration
public class FeginErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        ServiceException ex = new ServiceException();
        ex.setMethod(methodKey);
        if (response.status() >= 400 && response.status() <= 499) {
            ex.setCode(response.status());
            ex.setMessage("パラメータまたはページが無効です");
        } else if (response.status() >= 500 && response.status() <= 599) {
            ex.setCode(response.status());
            ex.setMessage("サーバーエラーが発生しました");
        }
        return ex;
    }
}
  1. 拡張機能

6.1 HTTPクライアントの切り替え FeignのデフォルトのURLConnectionではなく、Apache HttpClientを導入することで、接続プールやタイムアウト設定の柔軟な制御が可能になります。

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>11.0</version>
</dependency>
feign:
  httpclient:
    enabled: true

6.2 リクエスト前処理 RequestInterceptorを実装することで、リクエストヘッダーの付加やボディの変更が行えます。

@Component
public class TokenRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        System.out.println("呼び出しメソッド: " + template.method() + ", URL: " + template.url());
    }
}

6.3 GZIP圧縮の有効化 リクエスト・レスポンスの圧縮により通信コストを削減できます。

feign:
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true

6.4 ログレベルの設定 ログ出力には、Logger.Levelの設定が必要です。

logging:
  level:
    com.ldx.consumer.service.HelloService: DEBUG
@Bean
public Logger.Level feignLoggerLevel() {
    return Logger.Level.FULL;
}
  1. @FeignClientの主な設定項目 | 属性名 | デフォルト値 | 説明 | 備考 | |---|---|---|---| | name | 空文字 | 呼び出し対象サービス名 | valueと同一 | | url | 空文字 | 直接アクセスするフルパス | Ribbonを使わない場合に使用 | | fallback | void.class | 失敗時のフォールバック実装 | Hystrix依存 | | fallbackFactory | void.class | 例外情報付きのフォールバック | Throwableを引数に受け取れる | | contextId | 空文字 | Bean名の識別子(複数クライアント時必須) | 衝突回避に使用 | | qualifier | なし | 注入時の識別子 | primary=false時に必須 |

7.1 contextIdの役割 同じサービス名を持つ複数の@FeignClientがある場合、contextIdを明示的に設定することで、ビーン名の衝突を回避できます。例:

@FeignClient(name = "user", contextId = "userClient1")
public interface UserClient1 { ... }

7.2 qualifierの使い方 primary=falseの場合、@Qualifierを使って特定のビーンを注入します。

@Autowired
@Qualifier("userClient")
private UserClient userClient;

タグ: Spring Cloud Feign OpenFeign Hystrix Ribbon

8月19日 23:12 投稿