WADL(Web Application Description Language)を利用した Web API の作成

最近、'API'っていうと、RESTでJSONをレスポンスするような事を指してる人が多いと思います。 しかし、呼び出し側のコードも提供せず、'API'を名乗るのは如何なものかと思うんですよね。 SOAPRMIやCORBAとかは、APIといってもいいかもしれないけど。 RESTでJSONをレスポンスってボヤッとした通信が行われてるだけの感じがして。 RESTは、動かせる実感をすぐに感じられるから、とっつき易さはあるのだと思う。 しかし、利用者側各自で呼び出し用のコードやレスポンスのエンティティを書く必要がでてきて、そのサービスの利用者が複数いる場合、同じようなコード書くことになるんで無駄だよねと。合っているかどうか分からない、サービス仕様のドキュメントを見ながらクライアントを作成するのも微妙と思っていました。

SOAPには、WSDLとういのがあって、この定義から呼び出し側のコードを自動生成できるわけです。これのREST版ってないのかなと思って色々調べたら、WADL(Web Application Description Language)なんてものがあるじゃないですか! 但し、ググってても日本語の記事も、英語の記事もあんまりなさ気。流行ってないのかーと思いつつ試してみました。 作成したコードは、 github に上げてみました。WADLに対応しているJAX-RS(Jersey)を使っています。

サーバ側

例によって足し算アプリ。リクエストパラメータを2つ受け取り、結果をエンティティにいれてレスポンスするだけです。XMLレスポンスとJSONレスポンスで2つメソッドを用意しました。

package server;

import java.net.URI;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.XmlRootElement;

import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;

@Path("add")
public class Add {

    @Path("json")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Result json(@QueryParam("arg1") int arg1,
            @QueryParam("arg2") int arg2) {
        Result r = new Result();
        r.value = arg1 + arg2;
        return r;
    }

    @Path("xml")
    @GET
    @Produces(MediaType.APPLICATION_XML)
    public Result xml(@QueryParam("arg1") int arg1, @QueryParam("arg2") int arg2) {
        Result r = new Result();
        r.value = arg1 + arg2;
        return r;
    }

    @XmlRootElement
    public static class Result {
        public Integer value;
    }

    public static void main(String[] args) {
        JdkHttpServerFactory.createHttpServer(
                URI.create("http://localhost:8080/"),
                new ResourceConfig().packages(true, "server"));
    }
}

Jersey は、JDKに入っているHTTPサーバで動かすことができます。このmain関数を実行することにより、サーバが起動します。 プロジェクトは、gradlew に対応しているので Java8さえインストールされていれば、以下のコマンドをプロジェクトルートで叩けば起動できます(Gradle便利)。

gradlew run

以下のURLにアクセスすると、

http://localhost:8080/add/json?arg1=1&arg2=2

以下の用に、JSONがレスポンスされます。

{"value":3}

以下のURLにアクセスすると、

http://localhost:8080/add/xml?arg1=1&arg2=2

以下の用に、XMLがレスポンスされます。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><result><value>3</value></result>

で、以下のURLにアクセスすると、WADLが確認できます。アプリケーションがどういうインターフェースになっているか確認できます。

http://localhost:8080/application.wadl

出力

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<application xmlns="http://wadl.dev.java.net/2009/02">
    <doc xmlns:jersey="http://jersey.java.net/" jersey:generatedBy="Jersey: 2.15 2015-01-12 22:32:50"/>
    <doc xmlns:jersey="http://jersey.java.net/" jersey:hint="This is simplified WADL with user and core resources only. To get full WADL with extended resources use the query parameter detail. Link: http://localhost:8080/application.wadl?detail=true"/>
    <grammars>
        <include href="application.wadl/xsd0.xsd">
            <doc title="Generated" xml:lang="en"/>
        </include>
    </grammars>
    <resources base="http://localhost:8080/">
        <resource path="add">
            <resource path="json">
                <method id="json" name="GET">
                    <request>
                        <param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="arg1" style="query" type="xs:int"/>
                        <param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="arg2" style="query" type="xs:int"/>
                    </request>
                    <response>
                        <ns2:representation xmlns:ns2="http://wadl.dev.java.net/2009/02" xmlns="" element="result" mediaType="application/json"/>
                    </response>
                </method>
            </resource>
            <resource path="xml">
                <method id="xml" name="GET">
                    <request>
                        <param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="arg1" style="query" type="xs:int"/>
                        <param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="arg2" style="query" type="xs:int"/>
                    </request>
                    <response>
                        <ns2:representation xmlns:ns2="http://wadl.dev.java.net/2009/02" xmlns="" element="result" mediaType="application/xml"/>
                    </response>
                </method>
            </resource>
        </resource>
    </resources>
</application>

また、以下のURLにアクセスするとレスポンスのEntityの情報が確認できます。

http://localhost:8080/application.wadl/xsd0.xsd

<?xml version="1.0" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="result" type="result"/>

  <xs:complexType name="result">
    <xs:sequence>
      <xs:element name="value" type="xs:int" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

この wadl と xsd を利用してクライアントを作成します。

クライアント側

今回、Javaのサンプルなので、WADLからJavaのクライアントコードを生成できる wadl2javaを使いました。生成されるコードは、jersey-client に依存します。

で、以下のコマンドで Javaのコードを生成します。

wadl2java -o src/main/java -p client -s jaxrs20 http://localhost:8080/application.wadl

以下のコードが生成されます。

src/main/java/client
  Localhost.java
  ObjectFactory.java
  Result.java

Localhsot っていうクラス名は、生成時に指定して変えられないのかな。。 オプション -s jaxrs20 を指定しないと、JAX-RS 1系で生成されてしまうようです。

で、生成されたコードを利用したテストコードが以下です。

package client;

import static org.junit.Assert.assertTrue;

import java.net.URI;
import java.net.URISyntaxException;

import org.junit.Before;
import org.junit.Test;

import client.Localhost.Add;

public class LocalhostTest {

    Add add;

    @Before
    public void before() throws URISyntaxException {
        add = Localhost.add(Localhost
                .createClient(), new URI(
                "http://localhost:8080/"));
    }

    @Test
    public void testJson() {
        assertTrue(3 == add.json().getAsResult(1, 2).getValue());
    }

    @Test
    public void testXml() {
        assertTrue(5 == add.xml().getAsResult(2, 3).getValue());
    }
}

いいね! タイプセーフ!

以下でテスト実行

gradlew test

まとめ

WADL を利用すること自体、流行ってない感じがする。でもそういうやり方の選択肢があるのは良いと思う。Jersey は、WADL に対応してるけど、自分が好きなSpring MVC は、残念ながら対応していないようだ。ということで、'API'って名乗るなら、Spring MVC より Jersey の方が良いなと考えが変わってしまいました。

例えば、 wadl2typescript みたいなツールがあって、 WADL から typescriptのコードが吐き出せたら、Webブラウザから通信のコーディングもよりタイプセーフ的になって色々捗る気がする! 誰か、 wadl2typescript 作って! もうあったり。