Java EE開発における“リアクティブ・プログラミング”の実践

Oracle Java & Developers編集部
2015-09-24 11:00:00
  • このエントリーをはてなブックマークに追加

近年、アプリケーションの応答性やリソース使用効率を高める「リアクティブ・プログラミング」の重要性が高まっている。これをJava EE開発で実践する際のポイントをJava EEエバンジェリストのレザ・ラーマン氏が解説する。


米国オラクル Java EEエバンジェリストのレザ・ラーマン氏

 Java EEがエンタープライズ・システム開発の中核技術の座を担うようになってから長い年月が経過した今日、旧式化した多くのJ2EE/Java EEアプリケーションが最新のJava EEによるモダナイゼーションの必要性に迫られている。「その際には、ぜひリアクティブ・プログラミングを実践していただきたい」と呼びかけるのは、米国オラクルでJava EEエバンジェリストを務めるレザ・ラーマン氏だ。Java EE開発におけるリアクティブ・プログラミングとは、どのようなものか? ラーマン氏による解説をお届けする。

※本記事は、日本オラクルが2015年4月に開催した「Java Day Tokyo 2015」におけるラーマン氏のセッション「Reactive Java EE - Let Me Count the Ways!」の内容を基に構成しています。

なぜリアクティブ・プログラミングが重要なのか?

 読者の中には、「リアクティブ(Reactive)」という概念になじみのない方がおられるかもしれない。だが、この概念はアプリケーション開発の世界では1980年代後半から使われており、実は目新しいものではない。ラーマン氏は、その意味を次のように説明する。

 「ソフトウェア開発におけるリアクティブとは、アプリケーションの一部を変更すると、その効果が別の個所に現れる作用を指します。例えば、表計算シートでいずれかのセルの内容を変更すると、それに依存した他のセルの内容も変更されます。このような作用を指してリアクティブと呼び、それを生み出すコードを書くことをリアクティブ・プログラミングと呼ぶようになったのです」(ラーマン氏)

 それでは今、これに注目すべき理由は何か? 今日、リアクティブ・プログラミングについて検討する意義があるのは、それが次のような効果を生み出すからだ。

  • イベント・ドリブン:何らかの変化が起きるとイベントが発生し、それがトリガーとなって特定の処理が実行される
  • 非同期:実行中のスレッドの処理内容が、非リアルタイムに他の部分に影響を与える
  • ノンブロッキング:実行中のスレッドが、入力待ちなどでブロックされない
  • メッセージ・ドリブン:イベント・ドリブンとほぼ同義で、何らかのメッセージを受け取ると、それをトリガーにして処理を実行する

 ラーマン氏は、リアクティブ・プログラミングがもたらす効果として、応答性や耐障害性、拡張性、リソース使用効率の向上などを挙げたうえで、アプリケーション開発者が考慮すべきポイントを次のように話す。

 「Java EEでは現在、アプリケーション・サーバやランタイムのレベルで拡張性や耐障害性の強化が進められており、開発者は自分が書くコードの拡張性や耐障害性を気にする必要はありません。ただし、よりよいアプリケーションを作りたいと考えるのなら、応答性やリソース使用効率の向上をもたらすリアクティブ・プログラミングを実践してください。

 例えば、システムのインタフェースは、少なくとも見かけ上はユーザーの操作に即座に応答すべきです。これはユーザー・インタフェースのスレッドとビジネス・ロジックのスレッドを非同期に実装することによって実現できます。また、複数のスレッドを非同期で実行したり、あるいはノンブロッキングのスレッドを使ったりすることで、ハードウェア・リソースを最大限に活用して高いスループットを得ることができます。

 こうした『よりよいユーザー・エクスペリエンス』や『高いリソース使用効率』は、開発者がリアクティブ・プログラミングに取り組む大きなモチベーションになるはずです」

 IoT(Internet of Things)やM2M(Machine to Machine)の普及も、リアクティブ・プログラミングの必要性を高めている。

 現在、IoTやM2Mによるシステムが世界中で増加している。これまで、システムへの入力は主に人が行っていたが、マシン同士が直接コミュニケーションするようになると、コミュニケーションの量とスピードがケタ違いに増大し、システムの負荷は高まる。そのため、これらの分野ではリソース使用効率を高めるリアクティブ・プログラミングが大きな効果を生むのだ。

 また、近年のモバイル・デバイスの普及拡大も考慮すべき現象である。多くのモバイル・アプリケーションはバックエンドとの通信に依存しており、ユーザー数の拡大に伴ってバックエンド・システムの負荷は増大している。そうした中で、より多くのユーザーにリアルタイムに応答していくうえで、リアクティブ・プログラミングは有効な手段なのである。

 ただし、ラーマン氏は「リアクティブ・プログラミングは万能の解決策というわけではない」とも語る。同期のブロッキング・コードに比べると、非同期のノンブロッキング・コードは実装の難易度が高く、保守も難しくなる。リアクティブ・プログラミングにより、コードの複雑性が増すことは避けられないのだ。

 「例えば、拡張性の問題は、ハードウェアを水平、または垂直に拡張することによって解決できることがあります。同期用のシンプルなコードを沢山のハードウェアで動かすのが適切か、それとも非同期用の複雑なコードを、より少ないハードウェアで動かすのが適切かは、それぞれのケースに応じて判断する必要があるでしょう。もし、応答性に著しい問題があるのなら、おそらくハードウェアの増強は最終的な解決策にはなりません。その場合は、リアクティブ・プログラミングが解となるはずです」(ラーマン氏)

JMSによるリアクティブ・プログラミング

 それでは、Java EEの各APIは、リアクティブ・プログラミングでどう使えるのか。主なAPIについて見ていこう。まず取り上げるのはJMS(Java Message Service)だ。

 JMSは、J2EEの時代から使われている古いAPIだが、主にメッセージ・ドリブンなプログラムを作るために使われており、リアクティブ・プログラミングに適している。JMSを使用せずにメッセージ・ドリブンなプログラムを作ることも可能だが、JMSを使うことで、システムの疎結合化やトランザクション・ベースのシステムの構築、耐障害性の向上、ロード・バランシングなどを実現できる。

 また、EJB 2.0で追加された「Message Driven Bean」をJMSと組み合わせて使うことも可能だ。今日のMessage Driven Beanはアノテーションが付加されたPOJO(Plain Old Java Object)に過ぎないが、これを使うことでメッセージの受信側でもスレッド・プールを使った負荷分散、耐障害性の向上、信頼性の確保が可能になる。

 次に示すのは、POJOでラッピングしたメッセージを送信するJMS 2.0ベースのコードだ。

【リスト1:POJOでラッピングしたメッセージを送信するJMS 2.0ベースのコード】
@Inject JMSContext jmsContext; @Resource(lookup = "jms/HandlingEventRegistrationAttemptQueue") Destination handlingEventQueue; …略… public void receivedHandlingEventRegistrationAttempt(HandlingEventRegistrationAttempt attempt) { …略… jmsContext.createProducer() .setDeliveryMode(DeliveryMode.PERSISTENT) // The default :-) .setPriority(LOW_PRIORITY) .setDisableMessageID(true) .setDisableMessageTimestamp(true) .setStringProperty("source", source) .send(handlingEventQueue, attempt); }

 このコードでは、メッセージのペイロードを送信するのと並行して、他の処理も行っている。メッセージのプロパティには配送モードや優先度を設定でき、メッセージIDやタイムスタンプが不要ならば削除することもできる。

 また、次に示すのはメッセージの受信側となるMessage Driven Beanのコードだ。

【リスト2:メッセージを受信するMessage Driven Beanのコード】
@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/HandlingEventRegistrationAttemptQueue"), @ActivationConfigProperty(propertyName = "messageSelector", propertyValue = "source = 'mobile'")}) public class HandlingEventRegistrationAttemptConsumer implements MessageListener { …略… public void onMessage(Message message) { …略… HandlingEventRegistrationAttempt attempt = message.getBody(HandlingEventRegistrationAttempt.class); …略… } }

 こちらのコードでは、受け取ったメッセージを開封した後、メッセージ・セレクタを使ってフィルタをかけている(この例では、発信元が「mobile」のメッセージだけを抽出)。

 JMSを使うと、エラーが発生した場合にはランタイム例外のエラー・メッセージが送信元に送られ、メッセージの再送が行われる。「システムが負荷分散したクラスタで動いている場合にも、特別なコーディングを行う必要がない点が利点だと言えます」とラーマン氏は説明する。

非同期セッションBeanによるリアクティブ・プログラミング

 続いて紹介するのは、EJB 3.1から利用可能になった非同期セッションBeanである。

 ラーマン氏によれば、非同期セッションBeanは、メッセージ・ドリブンなプログラムを書くための最も簡単な方法である。これを使うと、非同期のプログラムを同期プログラムと同じように書くことができる。必要なのは、非同期で実行したいタスクをアノテーションを付加した別のスレッドに置くことだけだ。

 ある支払い処理を想定した次のコードでは、アノテーション「@Asynchronous」を指定することで非同期に処理が行われるようになっている。

【リスト3:非同期セッションBeanによる支払い処理のコード(1)】
@Stateless public class ReportGeneratorService { @Asynchronous public Future<Report> generateReport(ReportParameters params){ try{ Report report = renderReport(params); return new AsyncResult(report); } catch(ReportGenerationException e) { return new AsyncResult(new ErrorReport(e)); } --------------------- @Asynchronous public void processPayment(Payment payment){ // CPU/IO heavy tasks to process a payment }

 なお、メッセージを受信した後、送信元にレポートを返す必要がある場合は、次のようなコードを書くことになる。

【リスト4:非同期セッションBeanによる支払い処理のコード(2)】
@Inject ReportGeneratorService reportGeneratorService; …略… Future<Report> future = reportGeneratorService.generateReport(parameters); …略… if (future.isDone()) { Report report = future.get(); …略… } …略… future.cancel(true);

 非同期セッションBeanがJMSと異なるのは、疎結合ではなく、永続性も担保されないという点だ。つまり、エラーが発生した場合にはメッセージの内容が失われてしまう。メッセージの保持が重要な場合はJMSを使うべきだろう。

このサイトでは、利用状況の把握や広告配信などのために、Cookieなどを使用してアクセスデータを取得・利用しています。 これ以降ページを遷移した場合、Cookieなどの設定や使用に同意したことになります。
Cookieなどの設定や使用の詳細、オプトアウトについては詳細をご覧ください。
[ 閉じる ]