[Java] Java EE—the Most Lightweight Enterprise Framework?

原文はこちら。
https://community.oracle.com/docs/DOC-1008823

Recommendations for ensuring a productive development process

昔々、J2EE、特にアプリケーションサーバーは非常に肥大化しheavyweightと考えられていました。開発者がアプリケーション開発のためにその技術を使うのはかなり面倒で、落胆させるものでした。しかし、J2EEフレームワークからJava EEに名前が変わってから、この前提は正しいとは言えません。Java EEが他のエンタープライズ・フレームワークと比較してどうなのか、フレームワークが軽量であるといえる条件はいったい何なのでしょうか。

テクノロジーを選択する際、重要な考慮点の一つは、開発プロセスにおける開発者の生産性です。エンジニアはユースケースの実装や収益を生み出す機能を実装することに最大限の時間を割きます。それが、企業がそのゴールへ向かうために必要だからです。

選択されたテクノロジとメソッドは、開発者がビルド、テスト、およびデプロイ、さらにアプリケーションの設定、ビジネスユースケースに関係のない部分の実装、ビルド環境や外部依存関係の構成といった部分で必要な時間を最小限に抑える必要があります。しかしながら、利用可能なテクノロジーのうち、大部分はこういったことに対応していません。

Why Standards?

他のフレームワークと比較した場合、Java EEの最大のメリットの一つは、利用するAPIが標準化されていることです。標準化と聞くとうんざりさせられたり、あまりイノベーティブでないように見えますが、これらの標準を使うことにはいくつかの利点があります。

Integration of Specifications

Java EEの特定のAPI、例えばContexts and Dependency Injection (CDI)やJAX-RS、JSON Processing (JSR 353)、Bean ValidationといったAPIは、協調して動作し、シームレスに繋がっています。特にCDIはアプリケーション・コンポーネント間の「糊」として利用されます。この仕様には、以下のような言葉で記載されています。
"If the container does support specification A and B, then A has to integrate and work well with B seamlessly."
(コンテナが仕様AとBをサポートする場合、AはBと統合し、シームレスに動作する必要がある)
例えば、JAX-RSはリクエストやレスポンス・エンティティとして利用されるJsonObjectのようなJSONP型をサポートします。そして、Validationが失敗した場合には正しいHTTPステータスコードを含めてBean Validation機能の呼び出しをサポートしています(Listing 1)。
@Path("duke") 
public class DukeResource {
     @GET
     public JsonObject getDuke() {
          return Json.createObjectBuilder()
                  .add("name", "Duke")
                  .build();
     }
     @POST
     public void create(@Valid @NotPlayedYet Game game) {
          // game object has been validated at this point
     }
}
Listing 1. JSONP and Bean Validation integration of JAX-RS

JSONP型の利用はcontent-typeがapplication/jsonであることを暗示し、Validationに失敗した場合には、HTTPステータスコード400 Bad Request が送信されることでしょう。これは設定コードを一切書かなくても自動的にやってくれます。
別の例として、CDIを使うと開発者が任意のBeanやユーザー定義オブジェクトをJava EE管理対象コンポーネントに対して@Injectを使って注入することができます。Listing 2は、別のCDI Managed Beanをすぐに使うBean Validation Validatorの例です。
public class GameNotPlayedValidator implements ConstraintValidator<Notplayedyet, Game> {
     @Inject
     GameHistory history;
     public void initialize(NotPlayedYet constraint) {
          // no initialization needed
     }

     public boolean isValid(Game game, ConstraintValidatorContext context) {
          return !history.exists(game);
     }
}
Listing 2. CDI integration of bean validation

統合は仕様の主要な側面で、統合があるおかげで直接的な開発者体験を実現します。開発者は統合や構成作業を実施するアプリケーションサーバーに任せることができ、そのおかげえアプリケーションの業務ロジックに集中することができるのです。

Convention-over-Configuration Driven Development

Java EEのconvention-over-configuration(設定より規約、以下CoC)ドリブンなアプローチゆえに、ほとんどの現実世界のアプリケーションはそれほど多くの設定は必要ありません。厄介なXMLディスクリプタの時代は終わりました。単純なJava EEアプリケーションの場合、XMLファイルは必要ありません。

宣言的アノテーションのおかげで、アノテーションが付いたシンプルなPOLOがHTTPリクエスト(@Path)を処理したり、トランザクション、監視、インターセプタを含む、Enterprise JavaBeans (EJB) Bean(@Stateless)として機能します。こうしたアプローチはこれまでに様々なフレームワークで検証済みであり、Java EEで標準化されてきました。

XMLディスクリプタは今でもデプロイ時の構成のために利用することができますが、 CoCは開発者の生産性を最大化することに有用です。

External Dependencies

デプロイメント・アーティファクトに付属する特別な依存関係がなくても動作する現実世界のエンタープライズプロジェクトはごくまれです。しかし、これらの依存関係が必要な理由は、主として、ロギングやエンティティマッピングフレームワークや、Apache CommonsやGoogle Guavaなどの一般的なライブラリなど、ユースケースではなく、テクノロジーによってもたらされます。

Java EE 7、特にJava 8とともに利用する場合は、たいていのユースケースをカバーする機能を備えているので、他の依存関係を必要としません。標準状態で備えていないものは、最小限のコードで実現できます。例えば、CDIプロデューサによる注入可能な設定、インターセプタによるサーキットブレーカ(Adam Bienのオープンソースライブラリを見てください)、Java 8のLambdaやStreamsを使った洗練されたコレクション操作といったものです。

もちろん、ここで車輪の再発明をしないことを主張することができますが、実際には、数行のカスタムコードを保存するためだけに、メガバイトの外部依存関係をデプロイメント・アーティファクトに含めることは意味がありません。

最大の問題は直接導入された依存性ではなく、推移的な依存性であることを経験上わかっています。推移的な依存関係は、アプリケーションサーバー上の既存のライブラリのバージョンと衝突し、激しい競合を引き起こすことが多々あります。 最終的に、開発者はプロジェクトに小さな機能を実装するよりも、競合の管理に多くの時間を費やします。これは、主としてユースケースドリブンの依存関係ではなく、テクノロジードリブンの依存関係の場合に当てはまります。

Listing 3は、Adam BienのJava EE 7 Essentials Archetypeにインスパイアされた、シンプルなJava EEプロジェクトのMaven POM(project object model)ファイルの例です。
AdamBien/javaee7-essentials-archetype - A quickstart maven archetype for creating greenfield JavaEE 7 projects
https://github.com/AdamBien/javaee7-essentials-archetype
<project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.sebastian-daschner</groupId>
 <artifactId>game-of-duke</artifactId>
 <version>1.0-SNAPSHOT</version>
 <packaging>war</packaging>

 <dependencies>
     <dependency>
          <groupId>javax</groupId>
          <artifactId>javaee-api</artifactId>
          <version>7.0</version>
          <scope>provided</scope>
     </dependency>
 </dependencies>

 <build>
     <finalName>game-of-duke</finalName>
 </build>

 <properties>
     <maven.compiler.source>1.8</maven.compiler.source>
     <maven.compiler.target>1.8</maven.compiler.target>
     <failOnMissingWebXml>false</failOnMissingWebXml>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
</project>
Listing 3. Java EE 7 Maven POM file

もちろん、アプリケーションによっては、ソフトウェアの目的を達成するために重要なライブラリの統合が必要な場合もありますが、それは、ビジネス要件によってこれらの依存関係を正当化されるべきです。 一般に、外部のライブラリを最小限に抑えるための時間と労力を節約することに多大な意味があります。


テストに関する依存関係では、JUnit、Mockito、または場合によってはArquillianなどのライブラリは重要で包含されているため、別の話ではありますが、テストについても依存関係に注意を払うことは理にかなっています。

Thin Deployment Artifacts

アプリケーションサーバーはJava EE APIを認識しているため、デプロイメント・アーティファクトにそれらを含める必要はなく、ビジネスロジックのみを最小のグルーコードと横断的関心事(cross-cutting-concerns)と共に含めればよいのです。

従って、ビルドプロセスでたくさんの依存関係をコピーする必要がないため、こうしたキロバイトサイズのアーティファクトは非常に短いビルド時間ですみます。これは各ビルドごとに数秒の差が発生する可能性があります。開発者とContinuous Integration (CI) サーバーが費やしている余分な時間を合計すると、かなりの差になります。より頻繁にプロジェクトをビルドすればするほど、その影響はますます大きくなります。特にContinuous Delivery (CD) シナリオでは影響が大きくなります。

短いビルド時間に加えて、小さいサイズのデプロイメント・アーティファクトは公開とデプロイメントの時間を短縮します。実装が既にランタイムに含まれているため、可動部分は常に最小です。

The Ideal Framework for Docker

これこそがJava EEがDockerなどのコンテナテクノロジで使用されるべき、完璧なフレームワークである理由です。 Dockerイメージはレイヤー構造で、イメージが構築される際、ベースイメージにはすでにOS、Javaランタイム、およびアプリケーションが含まれています。 したがって、ビルドごとに追加されるのは、デプロイメント・アーティファクトの最後の1キロバイトの薄いレイヤーだけです。この構造のおかげで、各ビルド時だけでなく、イメージのバージョン化や出荷時においても、大量のWARまたはスタンドアロンJARを使うアプローチに比べて時間とストレージを節約します。

どの段階でも、デプロイメント・アーティファクトが小さいサイズであれば、非常に高速で生産的なデプロイメント・パイプラインを可能にします。

Modern Application Servers

J2EEアプリケーションサーバーは、開始時間やデプロイメント時間、インストールサイズ、およびリソースフットプリントに関して、重量級ソフトウェアの実行形態でしたが、Java EEという新しい世界では、これはもはや真実ではありません。

WildFly、Payara、WebSphere Liberty、Profile、TomEEなどの最新のJava EE 7アプリケーション・サーバーはすべて数秒で起動してデプロイできます。内部が包括的にモジュール化されているため、必要なコンポーネントだけをロードし、可能な限り迅速に薄いアプリケーション・アーティファクトをデプロイすることができます。

昨今のインストールサイズとフットプリントは非常に合理的です。アプリケーションサーバーは単純なサーブレットコンテナほど多くのリソースを消費しませんが、本格的なJava EE機能を備えています。面白いことに、実行中のブラウザインスタンスのほうが、より多くのメモリを消費します。

そうは言っても、コンテナやオンプレミスであっても、サーバー毎にアプリケーションを1個のみデプロイすることができ、それが合理的である場合があります。その "one application per application server per container"(コンテナ毎にアプリケーションサーバ1つに対し1個のアプリケーション)のアプローチを使うと、最新のマイクロサービス・アーキテクチャに対する非常に生産的で柔軟性のあるソリューションを得ることができます。

Packaging

パッケージングについては、もはやEARファイルを使用する理由はありません。単一の専用サーバー上にアプリケーション全体を配置する方法では、その環境にすべてのコンポーネントをインストールする必要があります。これにより、ビルドとデプロイメントの時間がさらに短縮されます。それに加えて、この方法はEARファイルが引き起こしがちなクラスローディング階層の問題も回避します。

大部分のクラウドとマイクロサービスのデプロイメントでは、スタンドアロンのJARパッケージを使用します。これらのJARパッケージには、アプリケーションとランタイム実装の両方が含まれます。 Java EEの世界では、WildFly Swarm、Payara Micro、TomEE Embeddedなどのベンダー固有のツールチェーンを使用してこのアプローチを実現できます。

しかしながら、上述の理由により、可能であればビジネスロジックをランタイムから分離することを強く推奨します。これはつまりアプリケーションのコードのみを含むWARファイルにアプリケーションをパッケージングする、ということです。

私見ですが、スタンドアロンのJARファイルは、技術的な理由ではなく、企業の「政治的」な問題のために、インストールや操作のプロセスを制御できない場合に便利な回避策と考えます。この方法であれば、デプロイメント・アーティファクトに必要なものすべてをまとめ、必要とするJavaランタイムを使い、かなりの技術的ではない問題を回避することができます。

Recommendation for a Productive Development Process

エンタープライズプロジェクトで最も生産的なソリューションは以下のようなものです。One of the most productive solutions for enterprise projects is the following:
  • Java EE 7とJava 8を提供されたAPIのみと共に使う
  • JAX-RSリソースやJPAといった最小限の配管を伴う、ビジネスロジックのみを含むキロバイトサイズのWARファイルをビルドする
  • Dockerイメージの構築 - 構成済みのアプリケーションサーバを含むベースイメージにWARファイルのみを追加
  • コンテナを使ってアプリケーションをデプロイするCD (Continuous Delivery) パイプラインによってリリース

Conclusion

”heavyweight Java EE”の時代は確かに終わりました。Java EEの傘に含まれるAPIは、生産性が高く、楽しめる開発者体験と、標準内でのシームレスな統合を提供します。特に、アプリケーションコードをランタイムから分離するアプローチは、迅速かつ生産的な開発プロセスを可能にします。

いくつかのベンダーによって始まった新しいMicroProfileイニシアチブにより、今後、Java EEの必要なコンポーネントがさらに少なくなる可能があります。
MicroProfile.io - Optimizing Enterprise Java for a microservices architecture
https://microprofile.io/

See Also

About the Author

Sebastian Daschner (@daschners) はフリーランスのJavaコンサルタント、ソフトウェア開発者、アーキテクトです。彼はプログラミングとJava EEに熱心で、JCPに参加し、JSR 370のExpert Groupに参加し、GitHubのさまざまなオープンソースプロジェクトをハッキングしています。彼は6年以上Javaに取り組んでいます。Java以外にも、LinuxやDockerなどのコンテナテクノロジのヘビーユーザーです。自分のブログやTwitterでコンピュータサイエンスの実践を伝えています。
sebastiandaschner blog
https://blog.sebastian-daschner.com/

0 件のコメント:

コメントを投稿