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
はデフォルト名として利用されます。
template
と defaultName
は @NotEmpty
アノテーションがあり、YAML 設定ファイルで template
の定義が空だった場合、アプリケーションの開始時に例外となります。
template
と defaultName
の 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
インスタンスはマルチスレッド環境下での扱いが非常に簡素化されます。
第二にid
とcontent
は Java 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>
この設定は、Maven の package
フェーズでいくつかの処理を追加します。
- 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 の管理ポートでみられる運用ツールがあります。
メトリクスリソース(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 クライアント、データベースクライアント、ビュー、・・)。これらはユーザマニュアルで説明します。