本連載では、以下に示すようなマイクロサービスアーキテクチャのアプリケーション環境を構築しています。

構成図

構成図

前回は、X-Rayデーモンへトレースデータを送信するためのフロントエンドのWebアプリケーションの設定を個別に実装しました。今回は、バックエンドのマイクロサービスにおけるX-Rayの設定実装について解説していきます。

なお、本連載で実際に作成するアプリケーションはGitHub上にコミットしています。以降に記載するソースコードでは、import文など本質的でない記述を省略している部分があるので、実行コードを作成する際は、必要に応じて適宜GitHubにあるソースコードも参照してください。

サンプリングルールの設定

前回と同様、共通のX-Rayの設定クラスではサンプリングルールを設定した「sampling-rules.json」を読み込みます。ここでも、全てのリクエストが計測対象となるよう、レートを「1」に設定しておきます。

{
  "version": 2,
  "rules": [
    {
      "description": "Sample description.",
      "host": "*",
      "http_method": "*",
      "url_path": "/api/*",
      "fixed_target": 1,
      "rate": 1
    }
  ],
  "default": {
     "fixed_target": 1,
     "rate": 0.1
  }
}

バックエンドのマイクロサービスにおけるX-Rayの設定

フロントエンドのWebアプリケーションと同様に、Controller、Service、Repositoryごとに実行時間を計測します。前回設定したXRayConfigクラスのAOPポイントカット定義に基づき、各コンポーネントの実装クラスに@XRayEnabledアノテーションを付与します。以下は、Controllerにアノテーションを付与する例です(backend-user-service/src/main/java/org/debugroom/mynavi/sample/aws/microservice/backend/user/app/web/BackendUserController.java)。

package org.debugroom.mynavi.sample.aws.microservice.backend.user.app.web;

// omit

import com.amazonaws.xray.spring.aop.XRayEnabled;

// omit

@XRayEnabled
@RestController
@RequestMapping("api/v1")
public class BackendUserController {
    // omit
}

ただし、Repositoryのクラスについては、org.springframework.data.repository.Repositoryインタフェースを継承しているので、特にアノテーションを設定する必要はありません。これは、XRayConfigの継承クラスcom.amazonaws.xray.spring.aop.AbstractXRayInterceptorが、既にそのインタフェースをポイントカット定義しているためです。

代わりにというわけではありませんが、バックエンドのマイクロサービスではインメモリデータベース「HSQL」を使ってユーザー情報を取得しているので、以下のようにDataSourceをcom.amazonaws.xray.sql.TracingDataSourceでラップして定義しておきます。

package org.debugroom.mynavi.sample.aws.microservice.backend.user.config;

// omit

import com.amazonaws.xray.sql.TracingDataSource;

// omit

@Profile("dev")
@Configuration
public class DevConfig {

    // omit

    @Bean
    public DataSource dataSource(){
        return new TracingDataSource(new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:schema-hsql.sql")
                .addScript("classpath:data-hsql.sql")
                .build());
    }

}

Controllerの処理が完了した後、TraceIDやユーザーID、実行時間をDynamoDBに保存するよう、HandlerInterceptorAdapterを継承したコンポーネントを生成します。TraceIDは前回の実装と同様、Segmentオブジェクトをcom.amazonaws.xray.AWSXRayから取得してもよいのですが、リクエストヘッダにもTraceIDは設定されているため、そこから取得するように実装してみましょう。最終的には、前回と同じくLogクラスに設定してDynamoDBへ保存します(backend-user-service/src/main/java/org/debugroom/mynavi/sample/aws/microservice/backend/user/app/web/interceptor/AuditLoggingInterceptor.java)。

package org.debugroom.mynavi.sample.aws.microservice.backend.user.app.web.interceptor;

// omit

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import org.debugroom.mynavi.sample.aws.microservice.common.apinfra.cloud.aws.log.dynamodb.model.Log;
import org.debugroom.mynavi.sample.aws.microservice.common.apinfra.cloud.aws.log.dynamodb.repository.LogRepository;
import org.debugroom.mynavi.sample.aws.microservice.common.apinfra.util.DateStringUtil;

@Component
public class AuditLoggingInterceptor extends HandlerInterceptorAdapter {

    private static final String HEADER_KEY = "X-Amzn-Trace-Id";

    @Autowired(required = false)
    LogRepository logRepository;

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler, ModelAndView modelAndView) throws Exception {
        Log log = Log.builder()
                .userId("1")
                .traceId(getTraceId(request))
                .createdAt(DateStringUtil.now())
                .build();

        logRepository.save(log);
    }

    private String getTraceId(HttpServletRequest request){
        String header = request.getHeader(HEADER_KEY);
        String[] headerElements = StringUtils.split(header, ";");
        String rootElements = Arrays.stream(headerElements)
                .filter(headerElement -> headerElement.startsWith("Root="))
                .findFirst().get();
        String[] traceIdElement = StringUtils.split(rootElements, "=");
        return traceIdElement[1];
    }

}

なお、Webアプリケーションから送信されたリクエストに設定されているTraceIDは、com.amazonaws.xray.javax.servlet.AWSXRayServletFilterで取り出されて、バックエンドマイクロサービスのセグメントに設定されます。

実装が完了したら、双方のアプリケーションを起動してログイン処理を行ってみましょう。以下の通り、アプリケーション起動処理と、Webアプリケーションからマイクロサービスへの呼び出しが以下のように可視化されるはずです。

可視化

Webアプリケーションがバックエンドマイクロサービスにアクセスしてデータを取得し、認証処理をトレースして可視化すると以下のようになります。

可視化

各コンポーネントの処理でどれくらい時間がかかっているのか、ビジネスロジックにほぼ手を加えずに可視化できたことを実感いただけると思います。

DynamoDBに保存したログは以下の通りです。ユーザーIDやTraceIDなどで検索しやすくなるので、必要に応じて保存する項目をカスタマイズするとよいでしょう。

可視化

* * *

以上、今回はバックエンドのマイクロサービスにX-Rayを設定する実装を行い、フロントエンドのWebアプリケーションから呼び出して処理するまでの一連の流れを可視化しました。実装の初期から各処理を可視化しておけば、性能上ボトルネックになりそうな箇所を早めに特定できます。問題のある部分は随時改善し、製造過程で品質を作り込んでいくことを心がけましょう。 次回以降は「AWS Cognito」を使い、認証/認可処理の組み込みやバックエンドのマイクロサービスへの認可制御の組み込みを実装していきます。

著者紹介


川畑 光平(KAWABATA Kohei) - NTTデータ

金融機関システム業務アプリケーション開発・システム基盤担当、ソフトウェア開発自動化関連の研究開発を経て、デジタル技術関連の研究開発・推進に従事。

Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professional等の資格を持ち、アプリケーション基盤・クラウドなど様々な開発プロジェクト支援にも携わる。AWS Top Engineers & Ambassadors選出。

本連載の内容に対するご意見・ご質問は Facebook まで。