REST API をタイプセーフに呼び出す(Spring Boot + Swagger Code Generator)

はじめに

REST API をタイプセーフに呼び出したいのです。

その実現のため WADLとかを追いかけてたんですが、Swagger は完全にノーマークでした。 WADLは、実質終わっているような。

昨年(2015年11月)、Microsoft, Google, IBM などにより Open API Initiative という団体が結成されました。 Swagger をベースに、REST API の記述標準化を目指しているようです。

f:id:t1000leaf:20160111000317p:plain

で、今回、「Spring Boot で作成した REST APISwagger Code Generator により自動生成された Java のコードを利用して呼び出す」ということを試してみました。ソースは、github にアップしています。

サンプルコード

Server

まずは、Spring Boot で簡単な REST API を作ります。

Application.java

main メソッドを持つアプリケーションを起動するクラスです。

package server;

import static springfox.documentation.builders.PathSelectors.*;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication
@EnableSwagger2 // (1)
public class Application {

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

    @Bean
    public Docket documentation() {
        return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
                .paths(regex("^/(?!error).*$")).build().pathMapping("/").apiInfo(metadata());
    }

    @Bean
    public UiConfiguration uiConfig() {
        return UiConfiguration.DEFAULT;
    }

    private ApiInfo metadata() {
        return new ApiInfoBuilder().title("Calculate API").version("1.0").build();
    }
}

(1)で EnableSwagger2 アノテーションを付けています。これを付けるだけで、指定のURLにアクセスすることにより、Open API Spec が取得できるようになります。

CalculateController.java

単純な Controller クラスを作成します。二つの整数をリクエストで受け取り、足し算・引き算の結果をレスポンスするだけです。

package server;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CalculateController {

    @RequestMapping(value = "/calculate", method = RequestMethod.GET)
    public Result get(@RequestParam Integer arg1, @RequestParam Integer arg2) {

        Result result = new Result();
        result.add = arg1 + arg2;
        result.subtract = arg1 - arg2;
        return result;
    }

    public static class Result {
        public Integer add;
        public Integer subtract;
    }
}

(自分の環境だとClass 'springfox.documentation.swagger.web.ClassOrApiAnnotationResourceGrouping' is marked deprecatedのような警告が消せない。。)

アプリケーションを起動し、http://localhost:8080/calculate?arg1=2&arg2=1 とアクセスすると、{"add":3,"subtract":1}JSON がレスポンスされます。

http://localhost:8080/v2/api-docs にアクセスすると Open API Spec が確認できます。以下のように表示されます(見やすいようにフォーマットしてます)。

{
    "basePath": "/",
    "definitions": {
        "Result": {
            "properties": {
                "add": {
                    "format": "int32",
                    "type": "integer"
                },
                "subtract": {
                    "format": "int32",
                    "type": "integer"
                }
            }
        }
    },
    "host": "localhost:8080",
    "info": {
        "contact": {},
        "license": {},
        "title": "Calculate API",
        "version": "1.0"
    },
    "paths": {
        "/calculate": {
            "get": {
                "consumes": ["application/json"],
                "operationId": "getUsingGET",
                "parameters": [
                    {
                        "description": "arg1",
                        "format": "int32",
                        "in": "query",
                        "name": "arg1",
                        "required": true,
                        "type": "integer"
                    },
                    {
                        "description": "arg2",
                        "format": "int32",
                        "in": "query",
                        "name": "arg2",
                        "required": true,
                        "type": "integer"
                    }
                ],
                "produces": ["*/*"],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/Result"
                        }
                    },
                    "401": {
                        "description": "Unauthorized"
                    },
                    "403": {
                        "description": "Forbidden"
                    },
                    "404": {
                        "description": "Not Found"
                    }
                },
                "summary": "get",
                "tags": ["calculate-controller"]
            }
        }
    },
    "swagger": "2.0",
    "tags": [
        {
            "description": "Calculate Controller",
            "name": "calculate-controller"
        }
    ]
}

Client のコードを生成する

Swagger Code Generator の jar を取得し、Java Client のコードを生成します。

$ wget http://repo1.maven.org/maven2/io/swagger/swagger-codegen-cli/2.1.5/swagger-codegen-cli-2.1.5.jar
$ java -jar swagger-codegen-cli-2.1.5.jar generate \
  -i http://localhost:8080/v2/api-docs \
  -l java \
  -o out/java

生成されたコードは、JAX-RS や Joda に依存していてちょっと残念な感じではあります。

swagger codegen は、複数の言語に対応しており、以下ように確認できます。

$ java -jar swagger-codegen-cli-2.1.5.jar langs
Available languages: [android, async-scala, csharp, dart, flash, python-flask, java, javascript, jaxrs, inflector, jmeter, nodejs, objc, perl, php, python, qt5cpp, ruby, scala, scalatra, silex-PHP, sinatra, slim, spring-mvc, dynamic-html, html, swagger, swagger-yaml, swift, tizen, typescript-angular, typescript-node, akka-scala, CsharpDotNet2, clojure]

typescript-angular じゃなく typescript-jquery が欲しい感じ。 Codegen のカスタマイズについては、この辺の記事が参考になりそう。

依存ライブラリを選ぶ(2016-01-24追記)

依存ライブラリを選べるみたいです。

javaの場合の確認コマンド

java -jar swagger-codegen-cli-2.1.5.jar config-help -l java

出力

(省略)
        library
            library template (sub-template) to use (Default: <default>)
                <default> - HTTP client: Jersey client 1.18. JSON processing: Jackson 2.4.2
                feign - HTTP client: Netflix Feign 8.1.1
                jersey2 - HTTP client: Jersey client 2.6
                okhttp-gson - HTTP client: OkHttp 2.4.0. JSON processing: Gson 2.3.1
                retrofit - HTTP client: OkHttp 2.4.0. JSON processing: Gson 2.3.1 (Retrofit 1.9.0)
                retrofit2 - HTTP client: OkHttp 2.5.0. JSON processing: Gson 2.4 (Retrofit 2.0.0-beta2)

feign に依存させる場合

$ java -jar swagger-codegen-cli-2.1.5.jar generate \
  -i http://localhost:8080/v2/api-docs \
  -l java -Dlibrary=feign \
  -o out/java

Client のコードを利用する

Client のコードを利用するテストコードを作成します。

package client;

import static org.junit.Assert.*;

import org.junit.Test;

import io.swagger.client.ApiClient;
import io.swagger.client.ApiException;
import io.swagger.client.api.CalculatecontrollerApi;
import io.swagger.client.model.Result;

public class CalculatecontrollerApiTest {
    @Test
    public void testGetUsingGET() throws ApiException {
        ApiClient client = new ApiClient();
        client.setBasePath("http://localhost:8080");
        CalculatecontrollerApi api = new CalculatecontrollerApi(client);
        Result result = api.getUsingGET(2, 1); // (1)
        System.out.println(result.getAdd());
        System.out.println(result.getSubtract());
        assertEquals(new Integer(3), result.getAdd());
        assertEquals(new Integer(1), result.getSubtract());
    }
}

(1)でサーバにリクエストしています。このテストを実行すると、標準出力に以下のように出力されます。

3
1

こんな感じで、REST API がタイプセーフに呼び出せるようになるわけです!

終わりに

アプリケーション間の通信と言えば、CORBA や SOAP がありましたが、あまり流行りませんでしたね。今はさらに Thrift とか gRPC とか色々ありますね。JVM言語縛りだったら RMI で良いんじゃないかとも思っていますが。

一番受け受け入れられたのは、ボヤっとした通信をしている REST API でした。そこに Swagger は、ドキュメントの自動生成やクライアントのコードの自動生成をもたらしました(WADLは何故流行らなかったんだろう)。緩く開発したい派は、自動生成されたドキュメントをみて自分でクライアントのコード書いて開発すればいいし、厳格に開発したい派は、自動生成されたクライアントコードを利用すればいいし、選択の余地があって良いのかなーと思うところです。