日別アーカイブ: 2019年6月19日

Project Helidon and OpenAPI

このエントリは以下のエントリをベースにしています。
This entry is based on the following written by Tim Quinn (Oracle).
https://medium.com/oracledevs/project-helidon-and-openapi-54a1fadc75b1

Helidon 1.1.1以後、SE/MPともOpenAPIをサポートしており、MPではMicroProfile OpenAPI仕様をサポートしています。

Helidon
https://helidon.io/
OpenAPI Specification
https://github.com/OAI/OpenAPI-Specification
MicroProfile
https://microprofile.io/
MicroProfile OpenAPI Specification
https://github.com/eclipse/microprofile-open-api

SE、MPの両方に対し、OpenAPI機能の利用方法を説明したドキュメントとサンプルコードを用意しています。

OpenAPI support in Helidon SE
https://helidon.io/docs/latest/index.html#/openapi/01_openapi
Helidon SE OpenAPI Example
https://github.com/oracle/helidon/tree/master/examples/openapi
OpenAPI support in Helidon MP
https://helidon.io/docs/latest/index.html#/microprofile/08_openapi
Helidon MP Basic OpenAPI Example
https://github.com/oracle/helidon/tree/master/examples/microprofile/openapi-basic

このエントリではOpenAPIのサポートをHelidonで作成したアプリケーションに追加する最もシンプルな方法をご紹介します。

変更点はMP、SEいずれを使っているかで変わりますので、それぞれ分けて説明します。アプリケーションのOpenAPIドキュメントにアクセスする方法は同じなので、それはひとまとめにして最後に記載します。

Helidon MPの場合

Update your pom.xml

OpenAPI関連のアノテーションのスキャン高速化のため、Jandexインデックスを作成します。セクションに以下を追加します。

<plugin>
    <groupId>org.jboss.jandex</groupId>
    <artifactId>jandex-maven-plugin</artifactId>
    <version>1.0.6</version>
    <executions>
      <execution>
          <id>make-index</id>
          <goals>
              <goal>jandex</goal>
          </goals>
      </execution>
    </executions>
</plugin>

依存関係を追加すると、OpenAPIアノテーションを利用できます。アプリケーションの実行時にHelidon OpenAPIランタイム(およびMicroProfile 2.2に対するHelidonのその他のサポート)が存在するようにします。

<dependency>
    <groupId>org.eclipse.microprofile.openapi</groupId>
    <artifactId>microprofile-openapi-api</artifactId>
    <version>1.1.2</version>
</dependency
<dependency>
    <groupId>io.helidon.microprofile.bundles</groupId>
    <artifactId>helidon-microprofile-2.2</artifactId>
    <version>1.1.2</version>
</dependency>

Annotate the endpoints

OpenAPIのアノテーションをアプリケーションのエンドポイントに追加します。以下の例はシンプルなGETエンドポイントにOpenAPIアノテーションを付加しています。

@GET
@Operation(
    summary = "Returns a generic greeting",
    description = "Greets the user generically")
@APIResponse(
    description = "Simple JSON containing the greeting",
    content = @Content(
        mediaType = "application/json",
        schema = @Schema(implementation = GreetingMessage.class)))
@Produces(MediaType.APPLICATION_JSON)
public JsonObject getDefaultMessage() {...}

@Operation@APIResponse は新規のアノテーションです。

Helidon SEの場合

SEにおけるOpenAPIのサポートは、基本的にMPと同じですが、SEはアノテーションの処理を含んでいません。そのため、OpenAPIはエンドポイントの情報を収集するためにアノテーションに依存できません。

その代わりに、SEで作成したアプリケーションにはアプリケーションのエンドポイントを記載したOpenAPIドキュメントの静的ファイルを含めることになるでしょう。

Update your pom.xml

依存関係を追加します。

<dependency>
     <groupId>io.helidon.openapi</groupId>
     <artifactId>helidon-openapi</artifactId>
     <version>1.1.2</version>
</dependency>

Register OpenAPISupport in your code

SEで作成したアプリケーションにはすでに以下のようなコードが含まれているはずです。OpenAPISupportを登録するために1行追加します。

Config config = Config.create();
...
return Routing.builder()
         .register(JsonSupport.create())
         .register(OpenAPISupport.create(config))
         .register(health)
         .register(metrics)
         .register("/greet", greetService)
         .build();

Add a static OpenAPI document file

Swaggerのようなツールを使って、アプリケーションのAPIを記述し、OpenAPIドキュメントファイルを生成しましょう。もしくは手作業でファイルを作成、編集することも可能です。ファイルができたら、プロジェクトのMETA-INF/openapi.ymlとして追加すれば、HelidonのOpenAPIサポート機能が自動検知してくれます(訳注:META-INF/openapi.yml以外には、META-INF/openapi.yamlもしくはMETA-INF/openapi.jsonとして追加できます)。

Access ths OpenAPI document

変更してアプリケーションをビルド後、実行してみましょう。自動的にサポートされる/openapiエンドポイントに対してGETリクエストを投げてください。上記のソースコードでエンドポイントにアノテーションで付加した情報を含む多数の情報を確認できます(訳注:OpenAPI 3ベースでした)。

paths:
  /greet:
    get:
      summary: Returns a generic greeting
      description: Greets the user generically
      responses:
        default:
          description: Simple JSON containing the greeting
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GreetingMessage'

What next?

これはほんの初歩的なものにすぎないので、例えばMPとSEのアプリケーションに、エンドポイントのOpenAPIモデルにプログラムで追加したり変更したり(つまりモデルのReaderとFilter)するコードを含めてみてもよいでしょう。

実のところ、HelidonのOpenAPIサポートでは、これらのソースすべてからのエンドポイント情報を組み合わせます。

  • 静的ファイル
  • アノテーション(MPのみ)
  • configurationの設定
  • model reader
  • model filter

Azure API Managementの外部キャッシュ (2)

このエントリは2019/06/19現在の情報に基づいています。将来の機能追加・廃止に伴い、記載内容との乖離が発生する可能性があります。また、外部キャッシュ機能は本日現在パブリック・プレビューです。

先日のエントリにおけるキャッシュ・ポリシーの設定情報を記載しておく。

Azure API Managementと外部キャッシュ (1)
https://logico-jp.io/2019/06/19/external-cache-for-azure-api-management-part1/

応答キャッシュ

応答キャッシュの場合、Query Parameterで指定した値はキーにできるが、Path Parameterで指定した値をキーにできない点に注意。以下の例では、inboundのcache-lookupでcodeというQuery Parameterをキーとして利用し、キャッシュに問い合わせしている。

キャッシュから取得
https://docs.microsoft.com/ja-jp/azure/api-management/api-management-caching-policies#GetFromCache
Get from cache
https://docs.microsoft.com/en-us/azure/api-management/api-management-caching-policies#GetFromCache

outboundのcache-storeでは、有効期限が3600秒、つまり1時間と指定しているだけで、値に対して何も指定をしていない。このことからも、応答キャッシュではレスポンスをそのままキャッシュに格納していることがわかる。

キャッシュに格納
https://docs.microsoft.com/ja-jp/azure/api-management/api-management-caching-policies#StoreToCache
Store to cache
https://docs.microsoft.com/en-us/azure/api-management/api-management-caching-policies#StoreToCache

<policies&gt;
    <inbound&gt;
        <base /&gt;
        <set-backend-service id="apim-generated-policy" backend-id="domestic" /&gt;
        <cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none" caching-type="external"&gt;
            <vary-by-query-parameter&gt;code</vary-by-query-parameter&gt;
        </cache-lookup&gt;
    </inbound&gt;
    <backend&gt;
        <base /&gt;
    </backend&gt;
    <outbound&gt;
        <base /&gt;
        <cache-store duration="3600" /&gt;
    </outbound&gt;
    <on-error&gt;
        <base /&gt;
    </on-error&gt;
</policies&gt;

値キャッシュ

先日のエントリでは、値キャッシュで応答キャッシュのような振る舞いをさせるために、以下のようなロジックを構成した。

  • inbound
    1. コードをPath Parameterから取得
    2. コードをキーとしてキャッシュに問い合わせる。
      1. 値がある場合、取得した値を使ってInboundパイプラインで応答を返す
      2. 値がない場合、バックエンドサービスを呼び出す
  • Outbound
    1. バックエンドの応答をキャッシュに文字列として格納。このとき、後続でもバックエンドサービスからの応答を利用できるよう、preserveContentフラグはONを指定
    2. 呼び出し側に応答を返す
<policies&gt;
    <inbound&gt;
        <base /&gt;
        <set-backend-service id="apim-generated-policy" backend-id="domestic" /&gt;
        <set-variable name="code" value="@(context.Request.MatchedParameters["code"])" /&gt;
        <cache-lookup-value key="@((string)(context.Variables["code"]))" default-value="" variable-name="response" caching-type="external" /&gt;
        <choose&gt;
            <when condition="@(context.Variables["response"] != "")"&gt;
                <return-response&gt;
                    <set-status code="200" /&gt;
                    <set-body&gt;@((string)context.Variables["response"])</set-body&gt;
                </return-response&gt;
            </when&gt;
        </choose&gt;
    </inbound&gt;
    <backend&gt;
        <base /&gt;
    </backend&gt;
    <outbound&gt;
        <base /&gt;
        <cache-store-value key="@((string)context.Variables["code"])" value="@(context.Response.Body.As<string&gt;(preserveContent: true))" caching-type="external" duration="3600" /&gt;
    </outbound&gt;
    <on-error&gt;
        <base /&gt;
    </on-error&gt;
</policies&gt;

Azure API Managementの外部キャッシュ (1)

このエントリは2019/06/19現在の情報に基づいています。将来の機能追加・廃止に伴い、記載内容との乖離が発生する可能性があります。また、外部キャッシュ機能は本日現在パブリック・プレビューです。

Azure API Managementのキャッシュ

Azure API Managementにはキャッシュ機構があり、応答した結果をキャッシュにためてレスポンスを向上させる仕組みが標準で備わっているが、外部キャッシュとして、Azure Cache for Redisを利用できる(Azureで提供しているものだけでなく、一般のRedisインスタンスも利用可能)。

Azure API Management で外部の Azure Cache for Redis を使用する
https://docs.microsoft.com/ja-jp/azure/api-management/api-management-howto-cache-external
Use an external Azure Cache for Redis in Azure API Management
https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-cache-external

外部キャッシュのメリット

以下のドキュメントに記載の通り、従量課金でAzure API Managementを利用すると、組み込みのキャッシュが利用できないが、外部キャッシュを使えばキャッシュ機構を利用できる。その他、キャッシュの構成をより細かく制御したり、利用中のAzure API Management の価格レベル以上のデータをキャッシュしたりでき、さらに、キャッシュ管理のライフサイクルをAPI Managementインスタンスのライフサイクルから切り離すことができる。そのため、メンテナンスやインスタンス移行時にもキャッシュを再作成しなくてすむ、といったメリットが考えられる。

Azure API Management レベルの機能に基づく比較
https://docs.microsoft.com/ja-jp/azure/api-management/api-management-features
Feature-based comparison of the Azure API Management tiers
https://docs.microsoft.com/en-us/azure/api-management/api-management-features

キャッシュ ポリシー

以下の2種類のポリシーがある。

  • 応答キャッシュポリシー (Response caching policies)
    • 有効期間(TTL)内の応答を保持、利用するしくみ
  • 値キャッシュポリシー (Value caching policies)
    • キーを使い値を格納、利用、削除できるしくみ
    • 特定のFragmentを格納することができる

API Management のキャッシュ ポリシー
https://docs.microsoft.com/ja-jp/azure/api-management/api-management-caching-policies
API Management caching policies
https://docs.microsoft.com/en-us/azure/api-management/api-management-caching-policies

構成

ドキュメント、というかチュートリアルの通り。以下には流れのみを記載。

  1. Azure Cache for Redisのインスタンスを作成
  2. (まだ作成していなければ)Azure API Managementインスタンスの作成
    • そこそこ時間がかかります…
  3. 1.で作成したCacheを2.で作成した(もしくは既存の)Azure API Managementインスタンスに関連付ける。
    • ドキュメントのスクリーンショットではあたかも2で作成したキャッシュインスタンスを選択するように見えるが、実際には「カスタム」しか選択できない。
    • 接続文字列は、Azure Cache for Redisの設定>アクセスキーにある接続文字列を指定(その他のインスタンスであれば該当する接続文字列を指定する)。
  4. 設定が終了したら保存をクリック

実際に使ってみる

簡単のため、国内の空港を3レターコードで取得するようなAPIを作成し、そのAPIを使うことにした。Redis Cacheへの格納を確認するため、Azure Portalから利用可能なRedisコンソールを使う。

応答キャッシュの場合

テスト前のキャッシュの状況は以下の通りで、キャッシュに何もない状態。

この段階で、FUK(福岡)を検索したところ、以下のようにキャッシュに追加されたことがわかる。

Redisコンソールで確認すると、データが格納されていることはわかるが、エンコードされていてよくわからない…。

簡単なRedisクライアントアプリケーションを作成して確認したところ、APIレスポンス自体をCacheしていた。

値キャッシュの場合

同じAPIでQuery Parameterをキーに、レスポンスのJSONを文字列としてキャッシュに格納することにした。最初はまだ何も入っていない状態。

コードとしてFUKを指定してAPIを呼ぶと、キャッシュにキーが追加されていることが確認できる。キーに対応する値が文字列として格納されていることも確認できる。

先ほどのRedisクライアントで内容を確認すると、レスポンス本体を文字列として保持していることを確認できた。

まとめ

外部キャッシュを簡単に利用可能であることを確認できた。また、応答キャッシュポリシーの場合、レスポンスがまるごと格納されていたが、値キャッシュの場合はほぼ指定した形式で格納されていたことがわかった(先頭にヘッダーと思しきものが付加されている)。

TTLはRedisの機能を使っているため、値キャッシュでその気になればTTLを-1にして永続化することも可能ではある。

値キャッシュは確かに開発の自由度が高いが、API Management外からのデータ更新は想定されていないことは留意すべきである。