BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル GrailsとFlexによるJEEアプリケーション作成

GrailsとFlexによるJEEアプリケーション作成

Javaプラットフォームは堅牢で成熟した企業アプリケーションのプラットフォームに進化しました。成熟したアプリケーション・プラットフォームとなった証の一つが派生技術や他の技術と統合するためのオプションがたくさんあることです。この記事では伝統的なJEEアプリケーション開発から派生したGrailsと、Javaと併用できる別の技術であるFlexを使ってJEEアプリケーションを作成する方法について見ていきます。どちらのフレームワークも高い生産性を誇ります。この二つのフレームワークを組み合わせることによってJ2EEアプリケーションにリッチなフロントエンドを提供すると同時に高い生産性を確保することが望めます。

Grailsは元々Groovyを利用してJVM上で稼動するWebアプリケーション・フレームワークで、SpringやHibernateのように有名なものです。素早いアプリケーション構築を実現するために「規約は設計に勝る」という原理を採用しています。コンポーネント間の共通的なふるまいを定義する上でGroovyの動的な性質を加えることはアプリケーション・フレームワークをとても強力なものにします。また、Grailsのプラグイン・アーキテクチャによって他のフレームワークとの統合やアプリケーション間での機能の再利用がとても簡単になります。

FlexはFlashPlayer上で実行されるSWFアプリケーションを開発するためのRIA開発キットで、Adboe社(以前はMacromedia社)が提供するFlash開発キットの最新版になります。リッチなウィジェットとそれらを結び付ける強力な言語体系の他にも分散アプリケーションの開発をとても簡単にする発展的な通信ソリューションも提供しています。Flexでは二種類の文法が使えます。MXMLとActionScriptです。MXMLはXMLベースの文法で標準コンポーネントを元にユーザ・インタフェースを定義するのに使われます。そしてActionScriptはそれらのコンポーネントの動的な振舞いを定義するのに使われます。

GrailsとFlexの統合 - 課題

GrailsとFlexのように異なる基盤の上に成り立っている二つのフレームワークを組み合わせる場合には、とりわけ通信に絡む問題が発生するものです。

  1. 一方のコンポーネントはどのようにして他方にある通信すべき相手となるコンポーネントを発見したらいいか?
    GrailsはそもそもサーバにあるJVM上で稼働するWebアプリケーション・フレームワークです。一方でFlexはクライアントと(ちょっとした)サーバ側コンポーネントを持つRIAプラットフォームです。そして、サーバ側のコンポーネントはWebアプリケーションとしてデプロイされます。従ってこの二つのフレームワークの統合はWebアプリケーション・コンテナの内部で行われることになります。
    FlexのUI上でユーザによって開始された通信はビジネス・ロジックを実行するためにGrailsのコンポーネントに到達する必要があります。FlexのUIコンポーネントはどのようにすればGrailsの適切なコンポーネントに到達できるでしょうか?
  2. フレームワーク間でどのようにデータを変換するのか?
    FlexではActionScriptを使ってデータを表現します。GrailsはJavaとGroovyのオブジェクトを使います。FlexのUIを使ってサーバに送信されたActionScriptのオブジェクトはアプリケーションにとって意味のあるデータ構造に変換されなければならないのです。どうしたらいいでしょうか?
  3. あるユーザが行った変更の内容をどのように他のユーザに通知するか?
    この問題は複数ユーザから利用されるアプリケーションの一般的な問題ですが、異なる二種類のフレームワークを使うことによって解決策を探すのがより大変なこととなります。(FlexのUIで行われたユーザのアクションを起点にして)Grailsアプリケーションで(データの)変更が行われ、その結果はFlexのUIを通して他の多くのユーザに通知される必要があります。どうすればこんなことができるでしょうか?

後続の三つの章では上記の疑問を一つずつ詳細に取り上げ、GrailsとFlexを使って答えを探していきます。

統合 - メッセージの送信先を見つける

一方のコンポーネントはどのようにして他方にある通信すべき相手となるコンポーネントを発見したらいいか?

GrailsとFlexに限ればこの問題はFlexコンポーネントがどのようにして適切なGrailsコンポーネントに対してデータを要求したり、ユーザの代わりに操作の実行を依頼するのかということになります。この問題の解決策について考察するため、まずはFlexの通信システムについて詳しく見てみましょう。

Flexのクライアント-サーバ通信

Flexの通信システムはクライアント部分とサーバ部分という二つの部分に分けることができます。クライアント部分にはアプリケーションがメッセージを送受信するためのコンポーネントが含まれていて、例えばRemoteObjectやConsumerといったコンポーネントが含まれます。これらのコンポーネントはサーバ側になる特定の"サービス"オブジェクトと関連付けられています。例えばRemotingServiceやMessagingServiceなどになります。クライアント・コンポーネントと関連付けられたサービス・コンポーネントの組み合わせが典型的な通信パターンをサポートしています。例えばConsumer, ProducerとMessagingServiceを使えばアプリケーションでPublish-Subscribe(出版/購読)型の通信を利用できるようになります。

クライアントとサーバの間の通信はチャンネルを通して行われます。チャンネルにはいくつかの実装があります。最も重要なのがAMFChannelとRTMPChannelになります。AMFChannelはHTTPをベースにしているので要求/応答型のアーキテクチャになります。このチャンネルとMessagingServiceを使うことでPublish-Subscribe型の通信に対応できます。この組み合わせでは発行された新しいメッセージを取ってくるためにチャンネルを通して定期的に要求が送信されます。こういった場合、より効率的なのはRTMPChannelになります。このチャンネルはTCP/IPをベースにしてクライアントとサーバ間のオープン・コネクションをサポートします。この方式では両方向に即座にメッセージの送受信を行うことができるのです。BlazeDSはAdobe社によるFlexのオープンソース実装ですが、残念ながらRTMPChannelの実装が含まれていません。

Flexの通信技術において一番重要な部分はDestination(宛先)です。これは通信チャンネルのサーバ側のエンドポイントになります。サービスがDestinationを提供し、クライアント・コンポーネントはDestinationを通してサービスに接続します。そして接続されたクライアント・コンポーネントからメッセージをDestinationへの送信したり、Destinationから受信したりすることができるのです。DestinationはFactory(ファクトリ)を通して生成することができます。

Grailsのリモートへの公開:Service

Flexの複雑な通信基盤をどのようにしてGrailsと組み合わせたらいいのでしょうか?Grailsはいくつかのタイプのオブジェクトを識別します。ドメイン・オブジェクト、コントローラ、ビュー、そしてサービスです。Grailsにおいてサービスとは外部の通信チャンネル、例えばHTTPを通して、いくつかの関数やサービスを公開するオブジェクトのことを指します。FlexのDestinationがGrailsのサービスに相当します。

これがまさにGrailsのflexプラグイン(リンク)が提供する解決策になります。Grails内でFlexに公開するマークが付けられているサービスは全てFlexフレームワーク内でDestinationとして登録されます。GrailsはFlex内に用意された特殊なRemotingServiceを通して特定のファクトリによってマーク付けされたサービスをFlexに追加します。このFactoryがGrails内部で使われるSpringのコンテキストに対応するサービスを登録します。これらの設定は全てservices-config.xmlというファイル内にあり、このファイルはGrailsのflexプラグインによって適切な場所にコピーされます。

class UserService {
  static expose = ['flex-remoting']
  def List all() {
    User.createCriteria().listDistinct {}
  }
  def Object get(id) {
    User.get(id);
  }
  def List update(User entity) throws BindException {
    entity.merge();
    if (entity.errors.hasErrors()) {
      throw new BindException(entity.errors);
    }
    all();
  }
  def List remove(User entity) {
    entity.delete();
    all();
  }
}

上記の設定によりUserServiceはflex内でRemoteObjectとして公開されます。以下のMXMLの断片はこのオブジェクトの使い方を示したものです。RemoteObjectのdestinationは"userService"となっていて、これは対象となるオブジェクトのGrails内部での名称になります。そのサービス・オブジェクトにある全てのメソッドがリモート操作となります。これらの操作はActionScriptから普通のメソッド同様に実行することができ、その結果やエラー内容はActionScriptの通常のイベント同様に扱うことができます。

...
  <mx:RemoteObject id="service" destination="userService">
    <mx:operation name="all" result="setList(event.message.body)"/>
    <mx:operation name="get" result="setSelected(event.message.body)"/>
    <mx:operation name="update"/>
    <mx:operation name="remove"/>
  </mx:RemoteObject>
...

結論

Grailsのflexプラグインが提供する統合問題の解決方法はとても洗練されています。使うのは簡単でほとんど自動化されています。「規約は設定に勝る」という精神の下、DestinationがFlexの設定に追加される際に利用される名前には命名規約が使われます。

データ変換

フレームワーク間でどのようにデータ(今回の場合はJavaとActionScriptのオブジェクト)を変換するのか?

この問題を分析するためには二つのフレームワークの接点を認識することが重要です。Flexは(Webサーバにある)Javaコンポーネントと(クライアントにある)ActionScriptコンポーネントからなります。従ってGrailsとFlexの境界はWebサーバ上、つまりは両側(GrailsとFlex)にあるJavaアプリケーションにあるということになります。

FlexのJavaコンポーネントはもっぱらFlexクライアントと通信することに焦点を当てています。このデータ通信にはActionScriptオブジェクトをベースにしたAMFプロトコルが使われます。サーバ・コンポーネントであるJavaコードがデータをActionScriptのオブジェクトに変換してチャンネルを越えてシリアライズ化します。FlexはJavaのプリミティブ型と標準的なオブジェクト(例えば、Data型やCollection型)に組み込みで対応しています。ActionScriptは動的言語なので、不確定な構造をしたオブジェクトもサポートします。JavaオブジェクトのフィールドはActionScriptオブジェクトの動的なプロパティとして変換されます。このような型情報を持たないオブジェクトをGrailsのドメイン・オブジェクトに変換する方法はもう少し複雑です。デフォルトではMapが作成され、各プロパティがキーと値の組み合わせで保存されます。

Groovyのドメイン・オブジェクトと同じプロパティを持つActionScriptのクラスを作成し、両方をアノテーションによって関連付けると、Flexはより簡単にこの変換を行います。このようなGroovy-ActionScriptのペアの例を以下に示します。

Groovy ActionScript
class User implements Serializable {
    String username
    String password
    String displayName
}
[RemoteClass(alias="User")]
public class User {
  public var id:*
  public var version:*
  public var username:String;
  public var password:String = "";
  public var displayName:String;


  public function toString():String {
    return displayName;
  } 
}

"RemoteClass"アノテーションがActionScriptクラスをaliasプロパティで指定されたJava(またはGroovyの)クラスと関連付けます。このプロパティには完全修飾クラス名を指定する必要があります。Grailsではドメイン・クラスは一般的にデフォルト・パッケージに追加されます。Grailsクラスの全てのフィールドがActionScriptクラスにコピーされます。プロパティ名はピッタリ一致していなければいけません。全てのドメイン・クラスに対して"id"と"version"というフィールドはGrailsによって自動的に追加され、クライアントとの通信の間中これらの情報が保持されます。

結論

FlexがJava(またはGroovy)とのデータ変換のために提供する手段は多くコードの重複を生みます。全てのドメイン・クラスが二度定義されます。一度はGroovy(またはJava)で、もう一度はActionScriptです。このことによって、クライアント特有のコードを追加することが可能になります。例えばActionScriptのコードだけに画面表示のためのコードを追加すると言ったことです。また、この二重定義によってどちらの開発言語でもコード補完機能が提供出来るようになります。設定にアノテーションを使うのはとても便利です。

複数ユーザ

あるユーザが行った変更の内容をどのように他のユーザに通知するか?

同時に複数のユーザが利用することが想定されるアプリケーションでは、あるユーザが行った共有データへの更新を他のユーザへ通知することが一つの挑戦になります。他のユーザにとってこの通知はサーバを起点とする通信となります。

一つの中心となる起点(サーバ)から多くの受信者(クライアント)への通信を行う場合にはしばしばPublish/Subscribe型の技術が役立ちます。この方法ではクライアントがサーバに(購読の)登録をします。そしてサーバから関心のあるメッセージが発行されるとクライアントに通知が届くことになります。

Grails内からJavaを実行することが出来るので、JMSも使うことができます。JMSはJavaにおける標準的なアプリケーション間のメッセージング技術でPublish/Subscribe型の通信にも対応しています。Flexにも、Publish/Subscribeをサポートする独自のメッセージング・コンポーネントがありますが、アダプタを使ってJMSと統合することも可能です。

Grailsに対するJMSの設定

多くにならい、GrailsにはJMSプラグインがあります。このプラグインによってあらゆるコントローラやサービスのクラス用のJMSの宛先にメッセージを送信するための便利なメソッドが追加されます。これらのメソッドを(前章で説明した)UserServiceクラスから使うことでJMSを通して変更を全てのクライアントに通知することができます。

class UserService {
  ...
  def List update(User entity) throws  BindException {
    entity.merge(flush:true );
    if  (entity.errors.hasErrors()) {
      throw new BindException(entity.errors)
    }
    sendUpdate();
    all();
  }
  def List remove(User entity) {
    entity.delete(flush:true );
    sendUpdate();
    all();
  }
  private def void sendUpdate() {
    try  {
      sendPubSubJMSMessage("tpc",all(),[type:"User"]);
    } catch (Exception e) {
      log.error("Sending updates failed.", e);
    }
  }
}

どのメッセージをどのタイミングで送信するかはサービス側で決定することができます。クライアントがデータの更新や削除を行うと、全件データを含んだリストがメッセージ送信されます。データは特定のトピック、この場合は"tpc"、として送信されます。このトピックの購読を登録している購読者全員が新しいデータを受け取ることになります。リストに含まれるオブジェクトの型(この場合は"User"型)情報はメッセージ内にメタデータとして保存されていて、購読者がサーバに購読登録する際に特定の型だけを指定することが出来るようになっています。

GrailsアプリケーションからJMSを使えるようにするためには、JMSプロバイダの実装を用意する必要があります。調度Grailsアプリケーションから簡単に使えるオープン・ソースの実装がApacheから提供されています。ApacheMQのライブラリをGrailsアプリケーションのlibディレクトリに追加して以下の記述をconf/springディレクトリにあるresources.xmlファイルに追加すればコネクション・ファクトリを使えるようになります。

...
  <bean id="connectionFactory"
class="org.apache.activemq.pool.PooledConnectionFactory"
destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://localhost"
/> </bean> </property> </bean> ...

FlexでJMSメッセージを受信する

flexにはまだUserServiceとやり取りするためのリクエスト-レスポンス型通信をサポートするRemotingServiceの設定しかしてありません。このサービスはflexプラグインによってGrailsに追加されたものです。ここではさらにPublish-Subscribe型通信をサポートするMessagingServiceを追加する必要があります。

...
    <service id="message-service" class="flex.messaging.services.MessageService" messageTypes="flex.messaging.messages.AsyncMessage">

<adapters>
<adapter-definition id="jms" class="flex.messaging.services.messaging.adapters.JMSAdapter" default="true"/>
</adapters> <destination id="tpc">
<properties>
<jms>
<message-type>javax.jms.ObjectMessage</message-type> <connection-factory>ConnectionFactory</connection-factory> <destination-jndi-name>tpc</destination-jndi-name> <delivery-mode>NON_PERSISTENT</delivery-mode> <message-priority>DEFAULT_PRIORITY</message-priority> <acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode> <transacted-sessions>false</transacted-sessions> <initial-context-environment> <property> <name>Context.PROVIDER_URL</name> <value>vm://localhost</value> </property> <property> <name>Context.INITIAL_CONTEXT_FACTORY</name> <value>org.apache.activemq.jndi.ActiveMQInitialContextFactory</value> </property> <property> <name>topic.tpc</name> <value>tpc</value> </property>
</initial-context-environment>
</jms>
</properties>
</destination>

</service>
...

services-config.xmlに対して上記のように新しくMessagingServiceとJMSAdapterの記述を追加しました。このアダプタがservice内にあるdestinationをJMSのリソースと関連付けます。serviceにはflexで記述したconsumerクラスが購読申込をするdestinationに関する設定も含まれます。大半がよく知られたJMSのプロパティです。initial-context-environment内にある"topic.tpc"プロパティはActiveMQ独自のプロパティで、コンテキストに"tpc"という名前のトピックを登録しています。

...
  <mx:Consumer destination="tpc" selector="type = 'User'"
    message="setList(event.message.body)"/>
...

Flexクライアントのコードはとても単純です。購読者コンポーネントが特定の宛先に向けて送信され、セレクタに合致するメッセージを受信します。このケースでは購読者が"type"メタデータのプロパティ値が"User"であるメッセージにだけ興味があることを示すためにセレクタを使っています。どの受信メッセージもメッセージの中身には表示するためのUserオブジェクトのリストが含まれているはずです。メッセージの中身の扱い方は調度RemoteObjectの"all"操作の戻り値に対する扱い方と同じになります。

結論

GrailsとFlexで作られたアプリケーションにおいて変更情報を多くのユーザに通知する方法は完全に標準コンポーネントだけで構築することができます。関連するコンポーネントの多さが設定と実装を複雑にしているかも知れません。しかし、ちゃんと設定さえしてしまえばこの方法はとても直観的な方法です。

解決方法を組み合わせる

前の三章で紹介した解決方法を思い出して下さい。Flex/Grailsアプリケーションのクライアント・サーバー間でドメインの状態を通信し合うための汎用的な解決方法として組み合わせることが出来るように思えます。この章ではそのような汎用的な方法がどのようなものなのか見ていきます。

サーバ・サイドのコードを汎用化する

課題1から3に対する解決方法として提示したサーバ・サイドのコードは既にGroovyの一つのサービスとして統合されています。このサービスは現状ではドメイン・クラスUserに特化しています。動的言語であるGroovyを使えばとても簡単に、このサービスをあらゆるドメイン・クラスに適用可能なように一般化することができます。

import org.codehaus.groovy.grails.commons.ApplicationHolder

class CrudService {
  static expose = ['flex-remoting']

  def List all(String domainType) {
    clazz(domainType).createCriteria().listDistinct {}
  }

  def Object get(String domainType, id) {
    clazz(domainType).get(id)
  }

  def List update(String domainType, Object entity)
      throws BindException {
    entity.merge(deepValidate:false, flush:true)
    if (entity.errors.hasErrors()) {
      throw new BindException(entity.errors)
    }
    sendUpdate(domainType);
    all(domainType);
  }

  def List remove(String domainType, Object entity) {
    entity.delete(flush:true);
    sendUpdate(domainType);
    all(domainType);

  }
  private def Class clazz(className) {
    return ApplicationHolder.application.getClassForName(className);
  }

  private def void sendUpdate(String domainType) {
    try {
      sendPubSubJMSMessage("tpc", all(domainType), [type:domainType]);
    } catch (Exception e) {
      log.error("Sending updates failed.", e);
    }
  }
}

この仕組みの中心となる仕掛けはどのドメインを返したらいいのかをクライアントに決定させることです。この目的のためにサーバ側でドメインの種類を特定する必要がある全てのサービスに対してパラメータが用意されています。ドメインの種類を現すクラスの名前がこのパラメータにピッタリでしょう。結果としてこのサービスはドメイン・オブジェクトに対するC(reate:生成)R(etrieve:取得)U(pdate:更新)D(elete:削除)の操作を提供しますのでCrudServiceと呼ぶことにします。

あらゆる変更が発生するとCrudServiceはJMSトピックに更新を通知します。この更新情報にはアプリケーションがその時点で把握しているドメイン・オブジェクト全てのリストが含まれます。クライアント側で簡単に興味のある更新だけを判別できるようにするため、ドメインの種類を現すクラスの名前がメッセージのメタデータとして追加されています。

クライアントのコード

1から3の解決方法に挙げたクライアント・サイドのActionScriptコードも一つのクラスにまとめることが出来ます。そして、まとめたクラスのインスタンスを使ってクライアント・サイドにある特定のドメインに対する全インスタンスの集合を管理することができます。

public class DomainInstancesManager
{
  private var domainType : String;
  public function EntityManager(domainType : String, destination : String) {
    this.domainType = domainType;
    initializeRemoteObject();
    initializeConsumer(destination);
  }

  private var  _list : ArrayCollection = new ArrayCollection();
  public function get list () : ArrayCollection {
    return _list;
  }
  private function setList(list : *) : void {
    _list.removeAll();
    for each (var o : * in list) {
      _list.addItem(o);
    }
  }

  internal static function defaultFault(error : FaultEvent) : void {
    Alert.show("Error while communicating with server: " + error.fault.faultString);
  }
  ...
}

クライアントのActionScript実装は主に二つのコンポーネントから成ります。Request/Response型通信の扱いを容易にするRemoteObjectと、Producer/Subscriber型通信の扱いを容易にするConsumerです。前章まではこれらのオブジェクトをMXMLコードで初期化していましたが、ActionScriptから生成することもできます。上記のコード片は両方のコンポーネントが使う一般的な構造を示しています。インスタンスを格納するためのリストとエラー・ハンドラです。インスタンスを格納するリストはどちらの通信コンポーネント経由のメッセージからも更新されます。

  ...
  private var consumer : Consumer;
  private function initializeConsumer(destination : String) : void {
    this.consumer = new Consumer();
    this.consumer.destination = destination;
    this.consumer.selector = "type ='" + domainType + "'";
    this.consumer.addEventListener(MessageEvent.MESSAGE, setListFromMessage);
    this.consumer.subscribe();
  }

  private function setListFromMessage(e : MessageEvent) : void {
    setList(e.message.body);
  }
...

このコード片はConsumerがActionScriptでどのような構造となっているのかを示していて、これがサーバから発行されたメッセージを受信することになります。ここでselectorプロパティは特定のdomainTypeに設定され、メタデータに含まれるtypeがこれと一致するメッセージだけを受信することが出来ます。そのようなメッセージを受信すると、イベント・ハンドラが実行されリストが更新されます。

次のコード片はRemoteObjectをRequest/Response型通信のエンドポイントとして設定する部分に焦点を当てています。必要となる全ての操作がRemoteObjectのoperationsプロパティに追加され、簡単に実行できるようになっています。

...
private var service : RemoteObject;
private var getOperation : Operation = new Operation();
public function initializeRemoteObject() {
	this.service = new RemoteObject("crudService");

	var operations:Object = new  Object();
	operations["all"] =  new  Operation();
	operations["all"].addEventListener(ResultEvent.RESULT, setListFromInvocation);
	operations["get"] = getOperation
	operations["remove"] = new  Operation()
	operations["remove"].addEventListener(ResultEvent.RESULT, setListFromInvocation);
	operations["update"] = new  Operation()
	operations["update"].addEventListener(ResultEvent.RESULT, setListFromInvocation);
	this .service.operations = operations;
	this .service.addEventListener(FaultEvent.FAULT, defaultFault);

	// Get the instances from the server.
	this.service.all(domainType);
	}

public function get(id : *, callback : Function) : void {
	var future: AsyncToken = getOperation.send(domainType, id);
	future.addResponder(new CallbackResponder(callback));
}

public function update(entity : Object) : void {
	service.update(domainType, entity);
}

public function remove(entity : Object) : void {
	service.remove(domainType, entity);
}

private function setListFromInvocation(e : ResultEvent) : void {
	setList(e.message.body);
}
...

ほとんどのメソッドは単にサービスの一つメソッドに処理を委譲しているだけです。これらの操作全てが同時実行可能で非同期な操作になります。サービスの処理が終わると、戻り値は登録されたイベントハンドラ(setListFromInvocation)で処理され、リストを更新します。'getOperation'だけは少々例外的で、結果がいろんな場所で使われます。結果を取得するためには呼び出し毎にCallbackResponderを登録して結果を処理する必要があります。このCallbackResponderは受信したメッセージに含まれるFunctionを実行することになります。

import  mx.rpc.IResponder;
import  mx.rpc.events.ResultEvent;

public  class CallbackResponder implements  IResponder {
  private  var callback : Function;
  function CallbackResponder(callback : Function) {
    this .callback = callback;
  }

  public  function result(data : Object) : void  {
    callback(ResultEvent(data).message.body);
  }

  public  function fault(info : Object) : void  {
    DomainInstancesManager.defaultFault(info);
  }
}

汎用化されたパッケージの利用

では、この汎用化されたパッケージをどのように使えばいいのでしょうか?二番目の問題の解決方法で見たのと同じUserオブジェクトのインスタンスを管理する例を見てみましょう。下記のMXMLコードはシステムに存在するUserの詳細情報を編集するためのPopUpDialog(ポップ・アップ・ダイアログ)を定義しています。このダイアログの外観は下図のようになります。インスタンス変数の'manager'は、ドメインの種類としてUserを扱う、DomainInstanceManagerに初期化されます。画面にはmanagerのlistプロパティで提供される全てのユーザが表示されます。ここではユーザのdisplayNameが表示されます。

<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:users="users.*" title="User Manager">
  <mx:Script>
    <![CDATA[
      import crud.DomainInstancesManager;
      import mx.managers.PopUpManager;
      [Bindable]
      private var  manager : DomainInstancesManager = new DomainInstancesManager("User", "tpc");

      private function resetForm() : void {
        selectedUser = new User();
        secondPasswordInput.text = "";
      }

      private function  setSelected(o : Object) : void
      {
        selectedUser = User(o);
        secondPasswordInput.text = selectedUser.password;
      }
    ]]>
  </mx:Script>
  <users:User id="selectedUser"
    displayName="{displayNameInput.text}"
    username="{usernameInput.text}"
    password="{passwordInput.text}"/>
  <mx:List height="100%" width="200" dataProvider="{manager.list}" labelField="displayName"
    itemClick="manager.get(User(event.currentTarget.selectedItem).id, setSelected)"/>
  <mx:VBox height="100%" horizontalAlign="right">
    <mx:Form>
      <mx:FormItem label="Display Name">
        <mx:TextInput id="displayNameInput" text="{selectedUser.displayName}"/>
      </mx:FormItem>
<mx:FormItem
label="User Name"> <mx:TextInput id="usernameInput" text="{selectedUser.username}"/> </mx:FormItem>
<mx:FormItem
label="Password"> <mx:TextInput id="passwordInput" text="{selectedUser.password}" displayAsPassword="true"/> </mx:FormItem>
<mx:FormItem
label="Password"> <mx:TextInput id="secondPasswordInput" text="" displayAsPassword="true"/> </mx:FormItem>
</mx:Form>
<mx:HBox
width="100%"> <mx:Button label="New User" click="{resetForm()}"/> <mx:Button label="Update User" click="{manager.update(selectedUser);resetForm()}"/> <mx:Button label="Remove User" click="{manager.remove(selectedUser);resetForm()}"/> </mx:HBox>
<mx:Button
label="Close" click="PopUpManager.removePopUp(this)"/> </mx:VBox>
</mx:TitleWindow>

リスト内のアイテムが選択されると、関連するユーザ・オブジェクトがサーバから取得され'selectedUser'プロパティに格納されます。フォーム内のフィールドとのバインドを簡単にするため、このプロパティはMXMLで定義されています。'selectedUser'プロパティのプロパティとフォーム内の入力フィールドは「双方向に」結び付けられているので、(サーバからのイベントによって)'selectedUser'プロパティの値が更新されれば入力フィールドに反映され、(ユーザの入力によって)フィールドが更新されると'selectedUser'プロパティの値に反映されます。画面内のボタンはmanagerのメソッドと関連付けられ、'selectedUser'がパラメータとして渡されます。メソッドの実行結果はmanagerが管理するリストに反映され、そのリストとバインディングされた画面内のリストにも反映されます。

見解

このパッケージを利用すればある特定の型のクライアント・サイドにある全オブジェクトのリストを管理することが出来るということに注意する必要があります。特定のデータやインスタンス数を制限したいデータにとってはいい方法となるでしょう。しかし他の種類のデータにとっては完全なリストを管理するというのは不要であるか場合によっては不適切なこともあるのです。そのような場合はリストの一部に対しても同様の理屈が成り立ちます。

一つ興味深い点はクライアントがデータを変更すると(ドメイン・オブジェクトを生成しようが、更新しようが、削除しようが)必ず新しいリストを含んだ結果が返されるということです。そして他のユーザと同様に最新のリストを含んだメッセージも受信します。つまりクライアントは自分が更新をするたびに2種類の結果を受け取ることになります。一つ目(自分が行った更新処理の結果)は落とされますが、更新結果はシステムに反映されます。というのもたいていの場合、処理結果の方がJMSメッセージより応答が早いからです。

も一つ触れておくべきなのは、このモデルには同時実行制御に関する問題があるということです。というのは、更新結果(この場合は全件のリスト)は別のチャンネルを通過して返されるからです。あるメッセージはそれより後に送信されたメッセージよりも遅れて到着することがあります。これはつまりクライアントが古いデータを見ているということを意味します。この問題に対する一つの対処法はメッセージに通し番号を含めてメッセージを受信する際に受信済みのメッセージの番号と比較するという方法です。

結論

汎用化されたパッケージは前章までの解決方法を利用しやすい形式にラップします。

この記事で紹介した方法はFlexとGrailsを使ったJEEアプリケーションの開発における堅牢な基盤を提供します。これらのツールを使ったJEE開発はより高速に、より俊敏に、そしてひょっとしたら最も重要に、より楽しくなるかもしれません。

著者について

Maarten Winkels氏はJavaとJEEによる開発について5年以上の経験を持つソフトウェア・エンジニア、コンサルタントです。最近オランダからインドに引っ越ししてXebia社が提供する分散アジャイル・プロセスの普及に従事しています。Xebia社はJava技術、アジャイル・オフショア、アジャイル・プロジェクト、アジャイル・コンサルタントそして教育事業、ITアーキテクチャとシステム監査に特化した企業です。http://www.xebia.com/を見て下さい。

この記事に星をつける

おすすめ度
スタイル

BT