Morning Girl

Web API, Windows, C#, .NET, Dynamics 365/CRM etc..

Java クライント開発における Web API の実装アプローチ:その3 Swagger(OpenAPI)Code Generate 編

f:id:sugimomoto:20190121012241p:plain

前回は シンプルな REST API 実装をお送りしました。

今回は、前回見えてきた課題点も踏まえながら、Swagger(OpenAPI)を利用した Web API実装アプローチを見ていきます。

最初の記事はこちらから。

bit.ly

Swagger(OpenAPI)って何?

swagger.io

その前に少しだけ、Swagger(OpenAPI)に触れておきます。Gtihub の「The OpenAPI Specification」では、以下のように記載があります。

OpenAPI Specification(OAS)は、ソースコードへのアクセス、追加ドキュメント、またはネットワークトラフィックの検査を必要とせずに、人間とコンピュータの両方がサービスの機能を発見して理解することを可能にする、  プログラミング言語に依存しないREST APIの標準的なインターフェイス記述を定義します。

引用元:https://github.com/OAI/OpenAPI-Specification

ここで一つポイントとなるのは REST API の記述用言語とそれに伴うエコシステム全体を指すということです。

ベースとなる考えは、REST API の仕様(URLパラメータや認証方式、HTTPメソッドは何を使うのか、レスポンスの仕様、JSON構造はどうなっているのか?等)を記述するための仕様であるということ。

以下のよなYAML(もしくはJSON ベース)でその仕様を記述します。

paths:
  /categories:
    get:
      summary: Return categories
      description: >-
        Returns records from the categories entity that match the specified
        query parameters.  All records will be returned when no parameters are
        specified.
      tags:
        - categories
      operationId: getAllcategories
      parameters:
        - name: $select
          in: query
          type: string
          description: >-
            A comma-separated list of properties to include in the results. When
            this field is left empty, all properties will be returned.
        - name: $orderby
          in: query
          type: string
          description: >-
            Order the results by this property in ascending or descending
            order.  Example for ascending: 'Name ASC' Example for descending:
            'Name DESC'
        - name: $top
          in: query
          type: integer
          description: The number of results to return.
        - name: $skip
          in: query
          type: integer
          description: This is the offset of results to skip when returning results.
        - name: $count
          in: query
          type: boolean
          description: >-
            When set, the results will return a count of results and not the
            actual results.
        - name: $filter
          in: query
          type: string
          description: >-
            Use this to filter the results by specific property values. For
            example, you can use the following filter to retrieve records with
            the name 'John': $filter=Name eq 'John'
      responses:
        '200':
          description: categories response
          schema:
            type: object
            properties:
              value:
                type: array
                items:
                  $ref: '#/definitions/categories'

この仕様があるおかげで、Swagger エコシステムでは、API ドキュメントの自動生成やテスト・ライブラリの生成などを実現することが可能になっています。

f:id:sugimomoto:20190120122040p:plain

SOAP では WSDLJSONだとJSON スキーマなどの仕様記述様式がありますが、そのREST 版であると捉えると、わかりやすいかなと。

OpenAPI(Swagger)で公開されているサービス

OData や GraphQL は日本でまだまだ馴染みが薄いですが、Swagger は結構人気な気がしますね。

国産クラウドサービスですと、「freee」や「CloudSign」「SmartHR」など

・freee:https://developer.freee.co.jp/docs/accounting/reference

・CloudSign:https://app.swaggerhub.com/apis/CloudSign/cloudsign-web_api/0.8.0

・SmartHR:https://developer.smarthr.jp/api/index.html

また、SendgridもSwaggerベースでドキュメントが公開されています。

・Sendgrid https://github.com/sendgrid/sendgrid-oai

その他、API Spec系

ちなみに、他にもAPI Spec系のサービス・仕様はありますが、トレンドの状況を見るとほぼ Swagger 一強かなと感じます。

・RAML https://raml.org/

・apiary.io https://apiary.io/

JSON Schema https://json-schema.org/

API Blueprint https://apiblueprint.org/

f:id:sugimomoto:20190120122051p:plain

対象の API

f:id:sugimomoto:20190120000738p:plain

対象の API は前回と一緒です。同じようにODataベースの REST API ではありますが、今回は Swagger(OpenAPI)でドキュメントが公開されている、ということを前提に進めます。

https://app.swaggerhub.com/apis/sugimomoto/CDataNorthWindSample/1.0.0

操作も同じように注文データ(orders)と注文明細データ(order_details)を取得して結合したものをコンソールで出力するというものです。

Client SDK の Code Generate

それでは、まず今回のキモとなる Client SDK Generateを行います。

SwaggerHub の API ドキュメント画面にアクセスすると、画面右上に「Export」ボタンがあります。

ここから、Server Side の Mock や Client SDK をダウンロードすることができます。

f:id:sugimomoto:20190120122101p:plain

以下のようなフォルダーが生成されます。デフォルトで Marven の pom.xml などが含まれているので、

f:id:sugimomoto:20190120122108p:plain

そのまま IntelliJ IDEA などでプロジェクトとしてインポートできます。

f:id:sugimomoto:20190120122113p:plain

プロジェクトを展開して、まずポイントとなる部分だけみてみましょう。

「package io.swagger.client.api」には、各エンドポイントのAPI 実行のコントロールを行うためのClientクラス郡が入っています。

f:id:sugimomoto:20190120122119p:plain

「package io.swagger.client.model;」には、取得できるJSON Objesをシリアライズ・デシリアライズするための Class が自動生成されていることがわかります。

このあたりを全部自動生成してくれるのが、素晴らしい!

f:id:sugimomoto:20190120122128p:plain

また、Test フォルダを覗くと、各エンドポイントの網羅的なテスト用のベースになる Class も自動生成されていることがわかります。

これらも Swagger Spec をベースに生成されるので楽ですね。

f:id:sugimomoto:20190120122138p:plain

「README.MD」ファイルには、Getting Started として、最初にリクエストを実施するためのサンプルコードも記載されていますので、使い方を把握するのも結構楽ちん。

[ Getting Started ]

Please follow the installation instruction and execute the following Java code:

import io.swagger.client.*;
import io.swagger.client.auth.*;
import io.swagger.client.model.*;
import io.swagger.client.api.CategoriesApi;

import java.io.File;
import java.util.*;

public class CategoriesApiExample {

    public static void main(String[] args) {
        ApiClient defaultClient = Configuration.getDefaultApiClient();
        
        // Configure API key authorization: authtoken_header
        ApiKeyAuth authtoken_header = (ApiKeyAuth) defaultClient.getAuthentication("authtoken_header");
        authtoken_header.setApiKey("YOUR API KEY");
        // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null)
        //authtoken_header.setApiKeyPrefix("Token");

        // Configure API key authorization: authtoken_query
        ApiKeyAuth authtoken_query = (ApiKeyAuth) defaultClient.getAuthentication("authtoken_query");
        authtoken_query.setApiKey("YOUR API KEY");
        // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null)
        //authtoken_query.setApiKeyPrefix("Token");

        // Configure HTTP basic authorization: basic
        HttpBasicAuth basic = (HttpBasicAuth) defaultClient.getAuthentication("basic");
        basic.setUsername("YOUR USERNAME");
        basic.setPassword("YOUR PASSWORD");

        CategoriesApi apiInstance = new CategoriesApi();
        Categories categories = new Categories(); // Categories | The categories entity to post
        try {
            Categories result = apiInstance.createcategories(categories);
            System.out.println(result);
        } catch (ApiException e) {
            System.err.println("Exception when calling CategoriesApi#createcategories");
            e.printStackTrace();
        }
    }
}

それでは、実際に実装を進めていきます。

実装コード

gist.github.com

Java クライアントアプリケーションから Swagger Client SDK を利用するにあたってのポイント

前回投稿した HTTP Client を実装してアプローチする場合に比べて、だいぶスッキリしたと思います。

認証周りや接続設定はApiClientをベースに、各 Swagger Specで定義されているパラメータが setpropertyName 形式で生成され、提供されているのでわかりやすいですね。

    ApiClient defaultClient = Configuration.getDefaultApiClient();
    defaultClient.setApiKey("XXXXXXXXXXX");

また、実査しにOrdersなどを取得する際に使っている getAllorders のメソッドの引数もポイントです。

以下のように、「String select, String orderby, Integer top, Integer skip, Boolean count, String filter」と6種類の引数が提供されているのですが、

    /**
     * Return orders
     * Returns records from the orders entity that match the specified query parameters.  All records will be returned when no parameters are specified.
     * @param select A comma-separated list of properties to include in the results. When this field is left empty, all properties will be returned. (optional)
     * @param orderby Order the results by this property in ascending or descending order.  Example for ascending: 'Name ASC' Example for descending: 'Name DESC' (optional)
     * @param top The number of results to return. (optional)
     * @param skip This is the offset of results to skip when returning results. (optional)
     * @param count When set, the results will return a count of results and not the actual results. (optional)
     * @param filter Use this to filter the results by specific property values. For example, you can use the following filter to retrieve records with the name 'John': $filter=Name eq 'John' (optional)
     * @return InlineResponse2004
     * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body
     */
    public InlineResponse2004 getAllorders(String select, String orderby, Integer top, Integer skip, Boolean count, String filter) throws ApiException {
        ApiResponse<InlineResponse2004> resp = getAllordersWithHttpInfo(select, orderby, top, skip, count, filter);
        return resp.getData();
    }

これはそのまま対象エンドポイントの URLパラメータ にマッチングされています。

https://app.swaggerhub.com/apis/sugimomoto/c-data_swagger_api_sample/1.0.0#/orders/getAllorders

f:id:sugimomoto:20190120122700p:plain

今回は OData をベースにしているため、とても素直な URL パラメータですが、それでも HTTP Client ベースで実装した場合のURL パラメータ組み立ての実装コードを一から書かなくてもいいと言うのは、やはりとても楽だなと思います。

おわりに

こうやって実際に実装コードベースで見ていくと、REST API を頑張って実装するのに比べてずいぶん省力化してできるなぁというのが、自分自身よく実感できました。

また、Web API を使う場合、実際の HTTP ベースでリクエストを行うモデルレイヤーだけでなく、最終的なビジネスロジック部分の記述のほうが重要性が高いわけですし、こういったところで省力化できるのはプロジェクト全体にとっても重要な部分だと思います。

それでは、次回は若干毛色を変えて、OData ベースでの実装を見ていきたいと思います。