Spring Cloud Eurekaを利用したマイクロサービス間連携:クライアント側のサービス呼び出し実装

マイクロサービスアーキテクチャにおけるサービス間通信は、REST形式のHTTPリクエストが一般的ですが、リモートプロシージャコール(RPC)も基盤となる重要な手法の一つです。

RPCとHTTP通信の技術的差異

RPC(Remote Procedure Call)は、ネットワークを介して離れたホスト上のプログラムを、ローカルのメソッド呼び出しのように透明に実行するためのプロトコル群です。RPCを実装するには、以下の2点が必須となります。

  • ネットワークレベルのデータ送受信: 要求元と要求先の間でデータをやり取りするには、TCPなどのトランスポート層プロトコルを採用し、双方が共通して理解できるシリアライゼーションフォーマットを定義する必要があります。
  • 呼び出しプロセスの抽象化: 単なるリモート通信ではなく、開発者がソケット通信やHTTPステータスコードなどの低レイヤーの詳細を意識しないように、API呼び出しをローカルメソッド実行のようにラップする仕組みが不可欠です。

対照的に、HTTPはアプリケーション層の規格であり、リソースの識別方法(URI)や操作タイプ(GET/POSTなど)を明確に規定しています。RPCのようなAPIレベルの呼び出し隠蔽機能は標準では提供されません。この違いにより、RPCは開発時の透明性が高く、HTTPは言語非依存で柔軟性に優れますが、クライアント側でリクエストの構築やレスポンスパースを自前で行う必要があります。

Eurekaクライアント側の準備と実装

Spring CloudエコシステムでEurekaを利用し、他のサービスからデータを取得するコンシューマーを構築するには、クライアント側の依存関係の追加と、サービスディスカバリー機能を有効化する設定が必要です。

1. Maven依存関係の定義

サービスを検知したり呼び出したりする役割を担うモジュールには、Eurekaクライアントのライブラリを必ず包含させます。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>

2. ブートストラップ設定とHTTPクライアントの準備

Spring Bootのメインクラスにディスカバリー機能を有効化するアノテーションを付与し、同期通信に利用する`RestTemplate`をSpringの管理対象にします。

package jp.tech.sample.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
public class ServiceConsumerBootstrap {

    @Bean
    public RestTemplate webRequestClient() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ServiceConsumerBootstrap.class, args);
    }
}

3. DiscoveryClientを活用した動的呼び出しロジック

サービス名から実際のネットワークアドレスを解決し、直接HTTPリクエストを投げる処理は、コントローラーまたはサービスレイヤーで実装します。`DiscoveryClient`を通じて登録ノードの情報を取得し、実行時にエンドポイントを構築します。

package jp.tech.sample.consumer.controller;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/client-proxy")
public class RemoteDataFetcher {

    private final DiscoveryClient registryClient;
    private final RestTemplate httpCaller;

    public RemoteDataFetcher(DiscoveryClient registryClient, RestTemplate httpCaller) {
        this.registryClient = registryClient;
        this.httpCaller = httpCaller;
    }

    @GetMapping("/fetch/{targetId}")
    public UserProfile retrieveProfile(@PathVariable Long targetId) {
        // 登録されているサービスインスタンスのリストを取得
        List<ServiceInstance> registeredNodes = registryClient.getInstances("profile-provider-service");
        if (registeredNodes == null || registeredNodes.isEmpty()) {
            throw new IllegalStateException("呼び出し先サービスが見つかりません");
        }

        // 簡易的な実装として最初のノードを選択(本番環境ではLoadBalancerなどを併用します)
        ServiceInstance targetNode = registeredNodes.get(0);

        // Eureka設定(eureka.instance.prefer-ip-addressなど)に応じて取得されるホスト情報に基づきエンドポイントを構築
        String endpointUri = String.format("http://%s:%s/profile/%d",
                targetNode.getHost(),
                targetNode.getPort(),
                targetId);

        return httpCaller.getForObject(endpointUri, UserProfile.class);
    }
}

Eurekaサーバー上で確認できるアドレス形式は、`application.yml`内の`eureka.instance.prefer-ip-address`や`ip-address`の設定値によって変化します。コード内で`getHost()`と`getPort()`が返す値は、常にその設定を反映した実際の通信先情報となります。

タグ: SpringCloud Eureka RestTemplate DiscoveryClient Microservices

6月5日 16:00 投稿