Java EE をベースとして生まれて来るエンタープライズ・テクノロジーがますます増えているようです。けれども Java EE (現在は Jakarta EE と呼ばれています) だけでなく、MicroProfile や、Java EE の API と MicroProfile の API の組み合わせなど、選択肢は山ほどあります。利用可能なアプリケーション・コンテナーを調べてみると、選択肢の数はさらに増えていることがわかるはずです。2019 年のエンタープライズ開発者は、どのプラットフォーム、どの特定の標準、どのランタイムをベースにアプリケーションを開発すべきでしょうか?
Java EE の現状
Java EE から見ていきましょう。用途の広さ、開発者たちの間での理解度、そして個人的な経験から言うと満足度という点で、よく使われている Java EE 標準は多数あります。このような Java EE 標準の例としては、CDI、JAX-RS、JSON-B、JPA が挙げられます。こうした標準を使用すれば、効率的にエンタープライズ・アプリケーションを開発することができます。
こうした事実に反して、Java EE を使用するだけでは必要を満たせないのはなぜでしょうか?マイクロサービスが優勢を誇る現在、私たちが開発するアプリケーションは、本番環境で実行する際に重要となってくる非機能要件も満たさなければなりません。なかでもレジリエンシー、モニタリング、分散トレースといった要件は極めて重要ですが、標準的な Java EE ではこれらの要件に対処できないのが現状です。また、よく批判に上る別の側面として、プレーンな Java EE では、そのまますぐに注入可能な構成をサポートできません。
上述の要件は、実のところ今に始まったことではなく、過去 10 年にわたって必要とされてきたことだという主張もあるでしょう。この主張はまったく正当なものですが、システムを分散させる傾向が強まっているマイクロサービスの時代には、これらの要件がさらに重要性を増してきます。現在、業界ではこうした要件を懸念しているようです。特に、クラウド・ネイティブのアプリケーションではレジリエンシーと可観測性を実装することが要件となっています。
Jakarta EE
プレーンな Java EE では十分でないとしたら、Jakarta EE を使用すればどうでしょうか?Jakarta EE は、Java EE の後続で、Eclipse Foundation に移譲されています。現在、ベンダーに依存しない標準を開発するプロセスを形式化する作業が進められています。つまり、Jakarta EE は現在のところ準備段階にあり、開発者が入手して使用できる排他的バージョンの Jakarta EE というものはまだありません。このことから、この記事の残りで現在 Java EE 8 の一部となっている標準について説明する際は、引き続き Java EE を引き合いにします。
Eclipse MicroProfile
MicroProfile も Enterprise Java エコシステム内のイニシアチブです。MicroProfile は複数のベンダーによって開始されたものですが、その目的は、ベンダーに依存しないエンタープライズ・テクノロジーの開発を可能にすることでした。この背景には、Oracle 側による大きな進展が見られなかったという状況があります。Enterprise Java エコシステムが必要としていたのは、個々のアプリケーションに実際に必要となる機能だけを実装するマイクロサービスの開発を実現する方法です。
MicroProfile は Java EE 標準とその設計原則をベースに作成されています。例えば、MicroProfile 1.0 は CDI、JAX-RS、JSON-P だけで構成されています。Java EE 標準を拡張するには、個々の MicroProfile プロジェクトで現在 Java EE に含まれていない機能 (構成、レジリエンシー、モニタリング、分散トレースなど) を追加するという形がとられています。
新しい MicroProfile プロジェクトが出現して進歩していくスピードには目覚ましいものがあります。2018 年だけをとっても、MicroProfile 1.3、1.4、2.0、2.1 とこれらの MicroProfile を含むプロジェクトが出現しました。
MicroProfile がこれらすべてのプロジェクトを同梱するとしたら、依存する唯一のテクノロジーとして MicroProfile を選べないでしょうか?特に、いくつかのランタイムではプレーンな MicroProfile アプリケーションのデプロイがサポートされることを考えると、この選択は妥当なように思えます。
けれども複雑なエンタープライズ・アプリケーションを開発する場合、大抵は MicroProfile 単独では不足が生じてきます (例えば、JPA や JTA でのような標準装備のパーシスタンス・サポートなど)。このことは、Java EE の Concurrency API によって対処するような複雑な並列処理を行う場合にも言えます。
このことから、特定の側面では引き続き Java EE をベースにせざるを得ません。その正当な理由を、以降の例で説明します。
Java EE と MicroProfile: 2 つの世界それぞれの長所
MicroProfile は Java EE 標準をベースとしていることから、プレーンな Java EE アプリケーション内で複数の MicroProfile プロジェクトを統合することができます。この統合で、Java EE と MicroProfile の両方をサポートしているアプリケーション・コンテナーを使用すれば、デプロイメント成果物に追加の実装を同梱する必要さえなくなります。
その一例を見ていきましょう。
ここで取り上げるコーヒーのサンプル・アプリケーションは、coffee shop (コーヒー・ショップ) と barista (バリスタ) という 2 つの Java EE マイクロサービスからなります。どちらも、クラウド・ネイティブの環境で Docker コンテナー内で実行されることになっています。
coffee shop サービスについては、レジリエンシーと注入可能な構成で拡張する必要があります。そのためには、MicroProfile Fault Tolerance と MicroProfile Config の依存関係をサンプル・アプリケーションの Maven pom.xml
に追加します。
<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>coffee-shop</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.fault-tolerance</groupId>
<artifactId>microprofile-fault-tolerance-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.config</groupId>
<artifactId>microprofile-config-api</artifactId>
<version>1.3</version>
<scope>provided</scope>
</dependency>
</dependencies>
...
</project>
デプロイメント成果物をリーンな状態に保つため、依存関係を provided
として宣言しています。これで、これらの API がアプリケーション・コンテナーに既知になります。
クラウド・ネイティブ・アプリケーションの構成
構成に取り掛かりましょう。クラウド・ネイティブ・アプリケーションを構成する定番の方法は、アプリケーションの「外側」から構成することです。つまり、アプリケーションのバイナリー (Docker イメージなど) を再構成したり変更したりはせずに、必要な注入を実行時に適用するという方法です。注入を適用するには通常、UNIX 環境変数または Docker volume ファイルを使用します。Kubernetes をコンテナー・オーケストレーションとして使用するとしたら、Kubernetes がこれらの成果物を注入します。最新のエンタープライズ・アプリケーションに伴う 12 の要素が、この構成手法の背後にある動機を説明しています。
このサンプル・アプリケーションで構成済みの値を使用できるようにするために、開発者が行わなければならない作業は最小限で済みます。MicroProfile Config には、環境変数を含め、各種のデフォルト構成ソースが同梱されているためです。
以下に示す JAX-RS ヘルス・チェック・リソースに、構成済みの値を注入する方法が示されています。
import org.eclipse.microprofile.config.inject.ConfigProperty;
...
@Path("health")public class HealthResource {
@Inject
@ConfigProperty(name = "version")
String appServerVersion;
@GET
public Response health() {
return Response.ok("OK")
.header("Open-Liberty", appServerVersion)
.build();
}
}
アプリケーション・サーバーのバージョンは VERSION
環境変数に格納され、MicroProfile Config によって自動的に読み取られます。大文字と小文字の区別はここには関係してきません。
/health
を使用してヘルス・チェック・リソースにアクセスすると、バージョンを HTTP ヘッダーとして組み込んだ、OK
ステータスのレスポンスがヘルス・チェック・リソースから返されるようにします。開発者が行う作業はこれだけです。アプリケーションが MicroProfile Config をサポートする限り、デフォルトの構成ソースを利用できます。
レジリエンシー
coffee shop アプリケーションはもう一方の barista サービスと HTTP を使用して通信します。バックエンドが利用不可になった場合、アプリケーションに及ぶ影響を最小限に抑えなければなりません。そのためには、妥当なタイムアウトを定義することが不可欠となります。さらに本番環境では、ブレーカー・パターンを使用すると、無応答のバックエンドに対して無意味な接続がいくつも行われないようにすることができます。
以下のスニペットに、barista バックエンドと通信する Barista クラスが示されています。
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
...
@ApplicationScopedpublic class Barista {
private Client client;
private WebTarget target;
@PostConstruct
private void initClient() {
client = ClientBuilder.newBuilder()
.connectTimeout(1, TimeUnit.SECONDS)
.readTimeout(2, TimeUnit.SECONDS)
.build();
target = client.target("http://barista:9080/barista/resources/brews");
}
@CircuitBreaker
public void startCoffeeBrew(CoffeeType type) {
// ...
}
// ...
}
JAX-RS バージョン 2.1 以降、JAX-RS クライアントにはいくつかの timeout
構成メソッドが含まれています。これらのメソッドは、基礎となる HTTP クライアントの timeout
動作を定義するものです。
@CircuitBreaker
アノテーションは startCoffeeBrew メソッドにブレーカーの動作を加えて拡張します。呼び出しの多く (デフォルトでは 50% 以上) が失敗すると例外がスローされ、20 件の呼び出しが行われるよりも前に 5 秒以内で回路が開放されます。つまり、以降の呼び出しは直ちに失敗し、実際のメソッドが実行されることもありません。その後、5 秒が経過するとメソッドの実行が再試行され、再び例外を受け取るかどうかによって、開回路状態が維持されるか、閉回路状態に戻ります。@CircuitBreaker
アノテーション内のデフォルト値は変更できます。さらに、ブレーカーを起動する例外タイプと潜在的なフォールバック動作を変更することもできます。詳細については、このリンクをクリックするとダウンロードされるドキュメント「MicroProfile Fault Tolerance」を参照してください。
この小さなサンプル・アプリケーションの目的は、MicroProfile を使用すると、構成やレジリエンシーなどの必要な機能を追加して Java Enterprise アプリケーションを簡単に拡張する仕組みを説明することです。もちろん、プレーンな Java EE でも上述のブレーカーのような機能を実装することはできますが、その場合、マイクロサービス・アプリケーションの 1 つひとつに自分でコードを実装しなければなりません。
ランタイム・サポート
サンプルのマイクロサービス・アプリケーションを機能させるには、Java EE と MicroProfile の両方をサポートするアプリケーション・コンテナーにアプリケーションをデプロイする必要があります。理想的には、すべての依存関係を provided
として宣言し、シン・デプロイメント成果物を利用できるようにします。
これらのサンプル・アプリケーションのデプロイ先は、Java EE 8 と MicroProfile 2.1 の両方をサポートする Open Liberty です。以下に示す server.xml
構成ファイル内に、必要な機能を指定します。
<?xml version="1.0" encoding="UTF-8"?><server description="OpenLiberty Server">
<featureManager>
<feature>javaee-8.0</feature>
<feature>mpConfig-1.3</feature>
<feature>mpFaultTolerance-1.1</feature>
...
</featureManager>
...
</server>
上記の構成ファイルにより、アプリケーション・サーバーがアプリケーション内で使用されている個別の MicroProfile プロジェクトをサポートするようになります。個々の機能を指定したくないという場合は、これらの機能の上位プロジェクト microProfile-2.1
を利用することもできます。さらに、javaee-8.0
機能を分割して、プロジェクト内で実際に使用する標準だけを指定して置き換えることさえ可能です。
Java EE と最新バージョンの MicroProfile をどちらもサポートしているアプリケーション・サーバーには、Payara、Tom EE、WildFly もあります。これらのアプリケーション・サーバーを使用するのでも、このプログラミング・モデルを実現できます。
将来へ向けて: MicroProfile を Jakarta EE のインキュベーターとして使用する
現在のところ、Jakarta EE はまだ準備段階の真っ最中であり、私たちは MicroProfile 戦略の今後の潜在的な方向性に期待を寄せています。潜在的な方向性の 1 つとして、ほぼ間違いなく妥当なのは、MicroProfile を Jakarta EE 標準のインキュベーターとして見なすことです。こうすることで、エンタープライズ・エコシステムではベンダーに依存しないテクノロジーをより迅速に進歩させるために、今すぐあらゆる仕様を公式の標準に編成する必要がなくなります。公式の標準は、長期的にサポートされるものにしなければなりません。MicroProfile はすでに多数のベンダーでサポートされていることから、今までのプロジェクトとは異なり、インキュベーターとしての MicroProfile プロジェクトは、ベンダーに依存しない、標準テクノロジーに遥かに近いものになるはずです。そこで実力を発揮する機能を Jakarta EE に統合するという方法をとれば、Jakarta EE の長期的な進化が可能になるだけでなく、イノベーションも加速化されます。
まとめ
開発者のよく知っている API をエンタープライズ・プロジェクトで使用するのはもっともなことです。このことから Java EE API は広く使われているものの、いくつか不足する機能があります。それを補えるのが、MicroProfile プロジェクトです。したがって、MicroProfile は既存の Java EE 標準の拡張として見なすことができます。ソフトウェア開発者はよく知っているAPI を引き続き使用してアプリケーションを開発できる一方、注入可能な構成やブレーカーなどを MicroProfile によって追加すれば、こうしたボイラーパターンを自分で実装する必要がなくなります。
同様に、アプリケーションとクラウド・ネイティブ環境に適合するデプロイメント・モデルを選択するのも、もっともなことです。シン・デプロイメント成果物を使用すると、開発プロセスの生産性が向上します。こうした生産性の向上を可能にするのが、Java EE と MicroProfile の両方をサポートするコンテナーにアプリケーションをデプロイするという方法です。私からのアドバイスは、ランタイム環境の最適化に取り掛かる前に、デプロイメント成果物を最適化するというとです。、例えば、サーバー・インストールをスリム化する前に、依存関係をコンテナーに追加します。すべてのビルドを実行するごとに各成果物をビルドして送信すれば、成果物にランタイム依存関係が入り込むことがなくなります。