Dropwizard Getting Started 〜勝手に翻訳〜

f:id:Naotsugu:20141209000851p:plain

Getting Started

ここでは単純な Dropwizard プロジェクト Hello World の作り方をガイドします。 この中で各種のライブラリと役割、Dropwizard の重要な概念、そしてあなたのプロジェクトとして拡張するための幾つかの技術的な用例を説明します。

概要

Dropwizard はライブラリでありフレームワークでもあります。全ての商用 Web アプリケーションとしてすぐに使える信頼できる機能実装と性能を提供することがゴールです。

これらの機能は再利用可能なライブラリとして抽出されているので、あなたのアプリケーションはスリムで焦点に集中でき、time-to-market とメンテナンス負荷が低減されます。

Jetty for HTTP

HTTP を扱わない Web アプリケーションは有りえないので、Dropwizard は直接あなたのプロジェクトに高度にチューニングされた組み込みのJetty HTTP サーバを含めます。複雑なアプリケーションサーバにあなたのアプリケーションを乗せるのではなく、Dropwizard プロジェクトでは HTTP サーバを起動するメインメソッドから成ります。

あなたのアプリケーションを単純なプロセスとして起動することで、製品における Java の望ましくない面を取り除くことができます(PermGenの問題、アプリケーションサーバの設定とメンテナンス、難解なデプロイツール、クラスローダのトラブル、隠蔽されたアプリケーションログ、複数のアプリケーションで単一のガベージコレクションを動作する際のチューニング)。 そして既存のUnix管理ツールの全てを使うことができるようになります。

Jersey for REST

RESTful ウェブ・アプリケーションの構築には、機能面とパフォーマンスにおいて Jersey(JAX-RS 参照実装) に勝るものはないとわかりました。 HTTP リクエストを、単純なJavaオブジェクトへ優雅にマッピングし、クリーンでテストしやすいクラスを書くことができます。 さらにストリーミング出力、マトリックス URL パターン、条件 GET リクエストなど、多くの機能をサポートしています。

Jackson for JSON

web のデータフォーマットに関して、JSON は共通言語になりました。 Jackson は JVM におけるJSONの王です。高速であることに加え、洗練されたオブジェクトマッパを持ち、ドメインモデルをそのままエクスポートできます。

Metrics for metrics

Metrics ライブラリは、商用環境におけるあなたのコードの振る舞いに対して比類ない洞察を得る情報を提供します。

And Friends

Jetty, Jersey, そして Jackson, に加え、Dropwizard は後悔のない素早い出荷を助けるためのいくつかのライブラリを含みます。

  • Guava は高度に最適かされたイミュータブルなデータ構造とJavaの開発スピードを上げる多くのクラスを提供します。
  • Logback と slf4j はパフォーマンスの高い柔軟なロギングを提供します
  • Hibernate Validator は JSR-303 の参照実装であり、簡単で宣言的なユーザ入力に対するバリデーションフレームワークで、国際化されたエラーメッセージを提供します
  • Apache HttpClient と Jersey クライアントライブラリは、低レベルから高レベルまでの他のWebサービスとのインタラクションを可能とします
  • JDBI は Java でリレーショナルデータベースを扱う最も簡素な方法です
  • Liquibase は開発とリリースサイクルの中で、DDLスクリプトに変わりあなたのデータベーススキーマに高度なリファクタリングを提供します
  • Freemarker と Mustache はユーザに向けたアプリケーションのための単純なテンプレーティングシステムです。
  • Joda Time は日時を扱う完全で正当なライブラリです

ではこれらの背景を元にして進みましょう

Setting Up Maven

新しい Dropwizard アプリケーションの構築には Maven を利用することを勧めます。あなたが Ant/Ivy, Buildr, Gradle, SBT, Leiningen, Gant のファンであったら、それは素晴らしいですが、私たちは Maven を利用します。ここでの例も Maven を利用して進めていきます。

ここではあなたが Maven プロジェクトを作る方法を知っていると仮定しますが、そうでなければ Maven のリファレンスをご覧ください。

最初にdropwizard.versionプロパティを POM に追加します。現在の Dripwizard のバーションは 0.7.0 です。

    <properties>
        <dropwizard.version>0.7.0</dropwizard.version>
    </properties>

dropwizard-core ライブラリの依存を追加します。

    <dependencies>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-core</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
    </dependencies>

これで XML は十分です。Maven プロジェクトの準備は整いました。実際のコードを書き始めましょう。

Creating A Configuration Class

どの Dropwizard アプリケーションもConfigurationクラスのサブクラスを定義します。このクラスにて環境毎のパラメータを設定します。 パラメータは YAML 形式の設定ファイルで定義し、アプリケーション設定クラスのインスタンスとしてデシリアライズされと検証を行います。

我々が構築するアプリケーションは、高パフォーマンスな Hello World サービスです。このサービスは環境に応じて挨拶の方法を変えることができるという要求があります。

挨拶のためのテンプレートを定義し、ユーザの名前が定義されていない場合はデフォルトの名前を利用するというケースを扱っていきます。

設定クラスは以下のようになります。

   package com.example.helloworld;

    import io.dropwizard.Configuration;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import org.hibernate.validator.constraints.NotEmpty;

    public class HelloWorldConfiguration extends Configuration {
        @NotEmpty
        private String template;

        @NotEmpty
        private String defaultName = "Stranger";

        @JsonProperty
        public String getTemplate() {
            return template;
        }

        @JsonProperty
        public void setTemplate(String template) {
            this.template = template;
        }

        @JsonProperty
        public String getDefaultName() {
            return defaultName;
        }

        @JsonProperty
        public void setDefaultName(String name) {
            this.defaultName = name;
        }
    }

一つずつ見ていきましょう。

このクラスが YAML ファイルからデシリアライズされると、YAML オブジェクトから2つのルートレベルフィールドが読み込まれます。 template は 挨拶のためのテンプレートで、defaultName はデフォルト名として利用されます。 templatedefaultName@NotEmpty アノテーションがあり、YAML 設定ファイルで template の定義が空だった場合、アプリケーションの開始時に例外となります。

templatedefaultName の getter と setter は、@JsonProperty アノテーションが付いています。これは Jackson がこれらのプロパティを YAML からデシリアライズするだけでなく、シリアライズも行うことを指示しています。

我々の YAML ファイルは以下のようになります。

   template: Hello, %s!
    defaultName: Stranger

Dropwizard は、この他にも多くの設定パラメータがありますが、妥当なデフォルト値があるため、あなたの設定ファイルは小さく、焦点を絞った定義が可能となっています。

それでは YAML ファイルを hello-world.yml として保存してアプリケーションクラスの作成に取り掛かりましょう。

Creating An Application Class

Configuration のサブクラスと Application のサブクラス が Dropwizard アプリケーションの中核となります。 Application クラスは基本機能を提供する各種バンドルとコマンドをまとめます。これらについては後でみるとして、現時点でのHelloWorldApplication は以下のようになります。

   package com.example.helloworld;

    import io.dropwizard.Application;
    import io.dropwizard.setup.Bootstrap;
    import io.dropwizard.setup.Environment;
    import com.example.helloworld.resources.HelloWorldResource;
    import com.example.helloworld.health.TemplateHealthCheck;

    public class HelloWorldApplication extends Application<HelloWorldConfiguration> {
        public static void main(String[] args) throws Exception {
            new HelloWorldApplication().run(args);
        }

        @Override
        public String getName() {
            return "hello-world";
        }

        @Override
        public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
            // nothing to do yet
        }

        @Override
        public void run(HelloWorldConfiguration configuration,
                        Environment environment) {
            // nothing to do yet
        }

    }

HelloWorldApplication は アプリケーションの設定ファイルである HelloWorldConfiguration により型付けされています。initialize メソッドはアプリケーションの起動に先立ちアプリケーションの設定を行います(バンドルやソースプロバイダなど)。 static main メソッドがアプリケーションのエントリポイントとなります。 まだ機能実装をしていないため run メソッドに処理を追加していきます。

Creating A Representation Class

Hello World アプリケーションの実装に入る前に、われわれの必要とする API について考えましょう。 幸いにもわれわれのアプリケーションは業界標準の RFC1149 に従う必要があり、以下のような JSON 表現に従います。

 {
      "id": 1,
      "content": "Hi!"
    }

id フィールドは挨拶のユニークな識別子で、content は挨拶のテキスト表現です。ありがたいことに、これは業界標準に直接沿ったものですね。

この表現を表すモデルクラスを作成しましょう。

   package com.example.helloworld.core;

    import com.fasterxml.jackson.annotation.JsonProperty;
    import org.hibernate.validator.constraints.Length;

    public class Saying {
        private long id;

        @Length(max = 3)
        private String content;

        public Saying() {
            // Jackson deserialization
        }

        public Saying(long id, String content) {
            this.id = id;
            this.content = content;
        }

        @JsonProperty
        public long getId() {
            return id;
        }

        @JsonProperty
        public String getContent() {
            return content;
        }
    }

これは簡素な POJO ですが、いくつか注目すべき点があります。

第一に不変オブジェクトであり、Saying インスタンスはマルチスレッド環境下での扱いが非常に簡素化されます。

第二にidcontentJava Bean プロパティの標準に従うものとなっています。これにより Jackson が JSON へのシリアライズを行うことができます。Jackson のオブジェクトマッピングidフィールドのJSON化に getID() を使い、content フィールドには getContent() を使います。

最後に、content のサイズが3以下である検証が含まれます。

これらの表現にが定義できたので、リソースの定義を行いましょう。

Creating A Resource Class

Jersey は Dropwizard アプリケーションの主要部分です。 各リソースクラスはURIテンプレートと関連付けられます。

われわれのアプリケーションでは、/hello-world という URI により、新しい Saying インスタンスが返却されます。 ですので、リソースクラスは以下のようになります。

   package com.example.helloworld.resources;

    import com.example.helloworld.core.Saying;
    import com.google.common.base.Optional;
    import com.codahale.metrics.annotation.Timed;

    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 java.util.concurrent.atomic.AtomicLong;

    @Path("/hello-world")
    @Produces(MediaType.APPLICATION_JSON)
    public class HelloWorldResource {
        private final String template;
        private final String defaultName;
        private final AtomicLong counter;

        public HelloWorldResource(String template, String defaultName) {
            this.template = template;
            this.defaultName = defaultName;
            this.counter = new AtomicLong();
        }

        @GET
        @Timed
        public Saying sayHello(@QueryParam("name") Optional<String> name) {
            final String value = String.format(template, name.or(defaultName));
            return new Saying(counter.incrementAndGet(), value);
        }
    }

上から順に見ていきましょう。

HelloWorldResourceは2つのアノテーションがあります。@Path@Produces です。 @Path("/hello-world") は、Jersey に このリソースを /hello-world という URI へ関連付けることを指示しています。 @Produces(MediaType.APPLICATION_JSON) は Jersey のコンテンツが application/json のコンテンツタイプで生成することを指示しています。

HelloWorldResourceコンストラクタは2つのパラメータを受け取ります。 template は挨拶の生成に使い、defaultName はユーザが自身の名前を伝えなかった場合に利用されます。

AtomicLong はIDを生成するためのスレッドセーフな方法を提供します。

リソースクラスはマルチスレッドで利用されるため、ステートレスまたは不変として扱わなければならないことに注意してください

#sayHello(Optional<String>) はこのクラスの主要メソッドで、とても単純です。 @QueryParam("name") アノテーションは Jersey にクエリパラメータとのマッピングを指定しています。 クライアントが/hello-world?name=Dougieというリクエストを送ると、sayHello メソッドOptional.of("Dougie")を引数に呼び出されます。 クエリ文字が指定されなかった場合、sayHello は Optional.absent() として呼び出されます。 (Dropwizard は Jersey を拡張し、Guava の Optional を利用できるようになっています)

sayHello メソッドの中では、カウンタがインクリメントされ、テンプレートでフォーマットされた新しい Saying のインスタンスが返却されます。 さらに、sayHello メソッドは @Timed アノテーションにより、自動的にメソッドの実行時間を Metrics のタイマで記録します。

sayHello メソッドが実行された後、Jersey が Saying インスタンスを得て、application/json 形式でクライアントに返却できるプロバイダを探します。 Dropwizard は Java オブジェクトと JSON オブジェクトの相互変換プロバイダを提供します。プロバイダはクライアントに application/json 形式の JSON とともに 200 OK のレスポンスを返却します。

Registering A Resource

実際に動かす前に、HelloWorldApplication に戻り、リソースクラスの登録を行います。

run メソッドの中に、HelloWorldConfigurationインスタンスからテンプレートとデフォルトの名前を設定したHelloWorldResource を作成し、アプリケーションの Jersey 環境へ追加します。

   @Override
    public void run(HelloWorldConfiguration configuration,
                    Environment environment) {
        final HelloWorldResource resource = new HelloWorldResource(
            configuration.getTemplate(),
            configuration.getDefaultName()
        );
        environment.jersey().register(resource);
    }

アプリケーションがスタートすると、設定ファイルからのパラメータで新しいリソースインスタンスを生成し、Environment に設定します。 Environment はアプリケーションが機能するのに必要な設定情報のレジストリとして機能します。

さらに先に進む前に、ヘルスチェック機能をアプリケーションに追加しておきましょう。

Creating A Health Check

ヘルスチェックは商用環境でアプリケーションが動作しているかを調べる最小限のテスト機能です。 あなたのアプリケーションにも少なくとも最小限のヘルスチェック機能を組み込むことを強くお勧めします。

文字列のフォーマッティングなどは(データベースのコネクションプールなどと違い)大抵は失敗することがないので、われわれはもう少し創造的なヘルスチェックを行うことにします。 ここでは与えられたテンプレートに従ったフォーマットを実際に行うというヘルスチェックを追加します。

   package com.example.helloworld.health;

    import com.codahale.metrics.health.HealthCheck;

    public class TemplateHealthCheck extends HealthCheck {
        private final String template;

        public TemplateHealthCheck(String template) {
            this.template = template;
        }

        @Override
        protected Result check() throws Exception {
            final String saying = String.format(template, "TEST");
            if (!saying.contains("TEST")) {
                return Result.unhealthy("template doesn't include a name");
            }
            return Result.healthy();
        }
    }

TemplateHealthCheck は 2つのチェックを行います。それはテンプレートが実際に様式に従った文字として与えられているか、 そしてテンプレートは与えられた名前で実際の出力を行えるかをテストします。

様式に沿わないテンプレートの場合でフォーマットに失敗する場合には IllegalFormatException となり暗黙的にテストが失敗します。 与えられたテスト用の文字が含まれない場合には unhealthy となります。

Adding A Health Check

Dropwizard ではほとんど同じやり方となりますが、Environment に適切なパラメータでインスタンスを生成して登録しましょう。

   @Override
    public void run(HelloWorldConfiguration configuration,
                    Environment environment) {
        final HelloWorldResource resource = new HelloWorldResource(
            configuration.getTemplate(),
            configuration.getDefaultName()
        );
        final TemplateHealthCheck healthCheck =
            new TemplateHealthCheck(configuration.getTemplate());
        environment.healthChecks().register("template", healthCheck);
        environment.jersey().register(resource);
    }

さて、すべての準備が整いました。

Building Fat JARs

Dropwizard アプリケーションは fat JAR ファイルとしてビルドすることを推奨します。 単一の jar ファイルはアプリケーションで必要となる全てのクラスファイルを含みます。 これにより環境の違いによるインストールライブラリの違いなどを気にせず、単一の配備アーティファクトだけを考えればよくなります。

fat JAR として Hello World アプリケーションをビルドするために、maven-shade プラグインを導入して設定を追加します。 pom.xml セクションに追加します。

 <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>1.6</version>
        <configuration>
            <createDependencyReducedPom>true</createDependencyReducedPom>
            <filters>
                <filter>
                    <artifact>*:*</artifact>
                    <excludes>
                        <exclude>META-INF/*.SF</exclude>
                        <exclude>META-INF/*.DSA</exclude>
                        <exclude>META-INF/*.RSA</exclude>
                    </excludes>
                </filter>
            </filters>
        </configuration>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>shade</goal>
                </goals>
                <configuration>
                    <transformers>
                        <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                        <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                            <mainClass>com.example.helloworld.HelloWorldApplication</mainClass>
                        </transformer>
                    </transformers>
                </configuration>
            </execution>
        </executions>
    </plugin>

この設定は、Mavenpackage フェーズでいくつかの処理を追加します。

  • fat JAR に含まれるコンテンツの依存ライブラリを含まない pom.xml を生成
  • 著名済みの JAR からデジタル著名を除外(こうしない場合、著名が無効となりJARのロードが行われない)
  • 代わりに、META-INF/services にエントリを上書き
  • com.example.helloworld.HelloWorldApplication を JAR の MainClass に設定

Versioning Your JARs

JAR に含まれるマニフェストに Implementation-Version を指定してあれば、Dropwizard はこれを使うことができます。 Maven でこの情報を埋め込むには、pom.xml<build><plugins> セクションに以下のプラグインを追加します。

 <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.4</version>
        <configuration>
            <archive>
                <manifest>
                    <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                </manifest>
            </archive>
        </configuration>
    </plugin>

これは、あなたのマシンに配備するアプリケーションのバージョンを把握するのに便利です。

では、プロジェクトディレクトリに移動し、mvn package を実行しましょう。以下のように表示されるはずです。

 [INFO] Including org.eclipse.jetty:jetty-util:jar:7.6.0.RC0 in the shaded jar.
    [INFO] Including com.google.guava:guava:jar:10.0.1 in the shaded jar.
    [INFO] Including com.google.code.findbugs:jsr305:jar:1.3.9 in the shaded jar.
    [INFO] Including org.hibernate:hibernate-validator:jar:4.2.0.Final in the shaded jar.
    [INFO] Including javax.validation:validation-api:jar:1.0.0.GA in the shaded jar.
    [INFO] Including org.yaml:snakeyaml:jar:1.9 in the shaded jar.
    [INFO] Replacing original artifact with shaded artifact.
    [INFO] Replacing /Users/yourname/Projects/hello-world/target/hello-world-0.0.1-SNAPSHOT.jar with /Users/yourname/Projects/hello-world/target/hello-world-0.0.1-SNAPSHOT-shaded.jar
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 8.415s
    [INFO] Finished at: Fri Dec 02 16:26:42 PST 2011
    [INFO] Final Memory: 11M/81M
    [INFO] ------------------------------------------------------------------------

おめでとうございます! 最初の Dropwizard がビルドできました。早速起動してみましょう。

Running Your Application

ビルドしたJAR ファイルを起動します。 プロジェクトディレクトリにて次のようにします。

java -jar target/hello-world-0.0.1-SNAPSHOT.jar

以下のような説明が表示されるはずです。

usage: java -jar hello-world-0.0.1-SNAPSHOT.jar
       [-h] [-v] {server} ...

positional arguments:
  {server}               available commands

optional arguments:
  -h, --help             show this help message and exit
  -v, --version          show the service version and exit

Dropwizard は最初のコマンドライン引数を取り、合致するコマンドへディスパッチします。 この場合、server コマンドのみが有効で、あなたのアプリケーションを HTTP サーバとして起動します。 server コマンドは設定ファイルが必要で、先ほど作成した設定ファイルを渡してやります。

java -jar target/hello-world-0.0.1-SNAPSHOT.jar server hello-world.yml

すると以下のような出力が得られるはずです。

INFO  [2011-12-03 00:38:32,927] io.dropwizard.cli.ServerCommand: Starting hello-world
INFO  [2011-12-03 00:38:32,931] org.eclipse.jetty.server.Server: jetty-7.x.y-SNAPSHOT
INFO  [2011-12-03 00:38:32,936] org.eclipse.jetty.server.handler.ContextHandler: started o.e.j.s.ServletContextHandler{/,null}
INFO  [2011-12-03 00:38:32,999] com.sun.jersey.server.impl.application.WebApplicationImpl: Initiating Jersey application, version 'Jersey: 1.10 11/02/2011 03:53 PM'
INFO  [2011-12-03 00:38:33,041] io.dropwizard.setup.Environment:

    GET     /hello-world (com.example.helloworld.resources.HelloWorldResource)

INFO  [2011-12-03 00:38:33,215] org.eclipse.jetty.server.handler.ContextHandler: started o.e.j.s.ServletContextHandler{/,null}
INFO  [2011-12-03 00:38:33,235] org.eclipse.jetty.server.AbstractConnector: Started BlockingChannelConnector@0.0.0.0:8080 STARTING
INFO  [2011-12-03 00:38:33,238] org.eclipse.jetty.server.AbstractConnector: Started SocketConnector@0.0.0.0:8081 STARTING

あなたの Dropwizard アプリケーションが起動し、8080 ポートでリスンしています。アドミニストレーションリクエストは 8081 ポートです。 Ctrl C にて処理中のリクエストの処理を待ち、サーバソケットを閉じ、アプリケーションがシャットダウンします。

その前に、挨拶を得るには(http://localhost:8080/hello-world) もう一つ(http://localhost:8080/hello-world?name=Successful+Dropwizard+User) にアクセスしてみましょう。

すばらしい。しかしあなたのアプリケーションでできるのはこれだけではありません。 Dropwizard の管理ポートでみられる運用ツールがあります。

f:id:Naotsugu:20141208235853p:plain

メトリクスリソース(http://localhost:8081/metrics)に遷移すれば、JSON としてメトリクス情報を見ることができます。

スレッドリソース(http://localhost:8081/threads)はプロセスで動いているスレッドダンプをすぐに確認できます。

ヘルスチェックリソース(http://localhost:8081/healthcheck)は、私たちの書いたヘルスチェッククラスを起動し、以下のような出力が得られるでしょう。

* deadlocks: OK
* template: OK

template の表示は、TemplateHealthCheck の結果で、特に驚きもなくパスしています。 deadlocks の表示はJVMスレッドのデッドロックを検知する組み込みのヘルスチェック結果です。

Next Steps

おめでとうございます。あなたは商用利用の準備ができた Hello World アプリケーションを手に入れました(テストが未ですが)。このアプリケーションは、秒間 30,000-50,000 リクエストを処理することができます。 うまくいったのであれば、Dropwizard は RESTful Web アプリケーションを開発するのに驚異的なプラットフォームを提供するために、Jetty, Jersey, Jackson, そして他の安定し、円熟したライブラリをどのように組み合わせているか感じていただけたでしょう。 ここでカバーしたものに加え、Dropwizard にはまだまだたくさんのものがあります(コマンド、バンドル、サーブレット、さらなるコンフィグレーション、バリデーション、HTTP クライアント、データベースクライアント、ビュー、・・)。これらはユーザマニュアルで説明します。