OpenLaszloアドバンスド・テクニック
連載:Flexのクライアントサイドをオープンソースで制覇する
(3)

Flexのフレームワーク、Cairngormのアーキテクチャ

ダイヤモンドコンピューターサービス
SI技術部 技術推進グループ
吉田 靖宏
2006/8/22

Controller

 続いて、Controllerを見てみましょう。下のリストは、ログインサンプルのControllerであるDemoControllerです。Controllerの処理のほとんどは、スーパークラスであるFrontControllerで行っているため、個々のControllerで実装する必要があるのは、イベント名の定義とaddCommandメソッドを利用してイベント名とそのイベントが発生したときに呼び出すコマンドを登録することの2つだけです。

 FrontControllerは、J2EEパターンの1つでリクエストの処理を初めに行う単一の入り口を提供します。入り口を1つにすることによって共通する処理などを1カ所にまとめることができ、再利用性や保守性を上げることができます。

 Webアプリケーションでは、サーブレットで行われることが多く、StrutsではAction ServletがこのFrontControllerに相当します。Cairngorm Frameworkでは、リクエストを処理する入り口ではなくイベントを一番初めに処理する入り口として機能します。

リスト 4 DemoControl.as

class org.nevis.cairngorm.samples.login.control.DemoControl extends 
FrontController {

    // イベント名とコマンドを登録
    public function DemoControl() {
        addCommand( DemoControl.EVENT_LOGIN, new LoginCommand() )
    }

    // イベント名の定義(ログイン)
    public static var EVENT_LOGIN = "login";
}

 先ほど、アーキテクチャの全体像のところで、「すべてのイベントはControllerに通知されることになっている」と書きましたが、これは一体どこで決められているのでしょうか。その答えは、FrontControllerクラスにあります。下のリストを見てください。

 これは、FrontControllerクラスの中身です。addCommandメソッド内では、イベント名とコマンドのマッピングを登録し、その後でEventBroadcasterのインスタンス(Singleton)に対して、addEventListenerメソッドでリスナーを登録しています。このメソッドの第1引数にはイベント名、第2引数にはイベントを捕捉するリスナーを指定しています。イベント名は変わっても第2引数に指定するリスナーは常に“this”、すなわちController自身になっています。

 つまり、EventBroadcasterによって発生させられるすべてのイベントのリスナーがControllerになっているため、イベントが発生するとまずControllerに通知されるというわけです (下図参照) 。

図2 イベントの発生とリスナーの関係
ピンクのクラスはフレームワーク、緑のクラスは開発者が作成するもの

リスト 5 FrontController.as(抜粋)

class org.nevis.cairngorm.control.FrontController {

    public function addCommand( commandName:String, commandRef:Command ) :
 Void {
        commands[ commandName ] = commandRef;
        // リスナーを登録(コマンド名が変わってもリスナーは常にController自身)
        EventBroadcaster.getInstance().addEventListener( commandName, this );
    }

    // コマンドを格納しておく配列
    private var commands:Array;
}

Command

 Commandは、J2EEパターンではなくGoFのデザインパターンの1つで、命令をクラスにカプセル化するというものです。下のリストは、ログインサンプルのCommandであるLoginCommandです。

 各Commandは、Cairngorm FrameworkのCommnadインターフェイスとResponderインターフェイスを実装して作成します(Action Script 2.0では、Java言語と同じようにクラスを複数継承することは認められていませんが、インターフェイスを複数実装することは認められています)。

 Commandインターフェイスではexecuteメソッドが、Responderインターフェイスでは、onResultメソッドとonFaultメソッドがそれぞれ定義されているので、各Commandクラスではこれら3つのメソッドを実装する必要があります。

 executeメソッドでは、サーバサイドのサービス呼び出しを行うためにBusinessDelegateを生成して、そのメソッドを呼び出します。executeメソッドの引数であるeventオブジェクトのdataプロパティには、ViewHelperでイベントを発生させたときに渡したValueObjectが設定されています。

 LoginViewHelperでは、ログインイベントを発生させたときにLoginVOのインスタンスを渡したので、dataプロパティをLoginVO型にキャストし、それをBusinessDelegateのloginメソッドの引数に渡しています。

 onResultメソッドでは、サーバサイドのサービス呼び出しに成功した場合のハンドリングを行います。ここでは、ModelLocatorのworkflowStateプロパティにログイン済みである状態をセットし、サービス呼び出しの結果を同様にModelLocatorのloginDateプロパティにセットしています。

 onFaultメソッドでは、反対にサーバサイドのサービス呼び出しに失敗した場合のハンドリングを行います。ここでは、ModelLocatorのstatusMessageプロパティにログインエラーのメッセージをセットしています。

リスト 6 LoginCommand.as

class org.nevis.cairngorm.samples.login.commands.LoginCommand
    implements Command, Responder {

    public function execute( event:Event ) : Void {
        // BusinessDelegateを生成して、loginメソッドを呼び出す
        var delegate: CustomerDelegate = new CustomerDelegate( this );
        var loginVO : LoginVO = LoginVO( event.data );
        delegate.login( loginVO );
    }

    // サーバサイドのサービス呼び出しに成功した場合のハンドリング
    public function onResult( event : Object ) : Void {
        ModelLocator.workflowState = ModelLocator.VIEWING_LOGGED
_IN_SCREEN;

        var loginDate : Date = Date( event.result );
        ModelLocator.loginDate = loginDate;
    }

    // サーバサイドのサービス呼び出しに失敗した場合のハンドリング
    public function onFault( event : Object ) : Void {
        ModelLocator.statusMessage = "Your username or password was wrong,
                                      please try again.";
    }
}

ModelLocator

 ModelLocatorは、Locatorという名前が付いているため、一見J2EEパターンの1つかと思ってしまいますが、これはCairngormオリジナルのパターンです。役割としては、画面に表示するモデルを保持することです。View(MXML)では、このModelLocatorを参照して画面の表示を行います。

 下のリストは、ログインサンプルのModelLocatorです。先ほどのLoginCommandクラスでセットしたプロパティを持っています。また、画面表示用のデータではありませんが、workflowStateというログイン済みかそうでないかを判断するプロパティを持っています。Index.mxmlでは、このworkflowStateがViewStackコンテナのselectedChildプロパティにバインディングされており、この値が変わることで画面遷移が行われるようになっています。

 ModelLocatorは、Cairngorm FrameworkのModelLocatorインターフェイスを実装して作成します。これも先ほどのValueObjectと同様にマーカーインターフェイスのため、何らかのメソッドの実装が強制されることはありません。

リスト 7 ModelLocator.as

class org.nevis.cairngorm.samples.login.model.ModelLocator 
implements org.nevis.cairngorm.model.ModelLocator {
    // ログインに失敗した時に表示するエラーメッセージ
    public static var statusMessage : String;
    // ログインに成功したときに表示するログイン日付
    public static var loginDate : Date;

    // ログイン済みかそうでないかを判断するプロパティとクラス変数
    public static var workflowState : Number;
    public static var VIEWING_LOGIN_SCREEN : Number = 1;
    public static var VIEWING_LOGGED_IN_SCREEN : Number = 2;
}

AS2に定数はないの?

 いま見てもらったModelLocatorのログイン済みかそうでないかを表す1や2の数字やControllerクラスのイベント名は、クラス変数ではなく定数の方がいいのではと思った方はいませんか? まさしくそのとおりなのですが、残念ながらAction Script 2.0では、あらかじめ定義された定数(ENTER、SPACEなど)はありますが、開発者がプログラムで独自に定数を定義することはできません。Action Script 3.0では、プログラムで定数を定義するための“const”キーワードがサポートされ、定数を定義できるようになっています。

BusinessDelegate

 BusinessDelegateは、J2EEパターンの1つでサービスの実装が何なのかをクライアントに対して隠ぺいします。Flex 1.5では、サーバサイドにアクセスする方法が3つ(RemoteObject、HTTPService、WebService)ありますが、サービスを使用する側はこれらサービスの実装を意識することなくサービスにアクセスできます。

 下のリストは、ログインサンプルのBusinessDelegateであるCustomerDelegateです。コンストラクタでは、Cairngorm FrameworkのServiceLocatorクラスを利用して“customerDelegate”という名前のサービスをルックアップしています。

 EJBのホームインターフェイスやデータソースなどであればJNDIからルックアップしますが、Cairngorm FrameworkはブラウザのFlash Player上で動作していますので、このようなJ2EEの仕組みは利用できません。ServiceLocatorは、この後に説明するServicesというMXMLファイルから該当のサービスを探し出してきます。

 loginメソッドでは、取得したサービスのloginメソッドを呼び出し、サーバサイドのサービスにアクセスします。その後で、FlexのクラスライブラリであるDelegateクラスを使用してサービス呼び出し成功時と失敗時のハンドリングをCommandクラスに委譲(Delegate)しています。

リスト 8 CustomerDelegate.as

class org.nevis.cairngorm.samples.login.business.CustomerDelegate {

    public function CustomerDelegate( responder : Responder ) {
        // ServiceLocatorを利用してサービスをルックアップ
        this.service = ServiceLocator.getInstance().getService( "customerDelegate" );
        this.responder = responder;
    }

    public function login( loginVO : LoginVO ): Void {
        // サーバサイドのサービスを呼び出す
        var call = service.login( loginVO );

        // サービス呼び出し成功時と失敗時のハンドリングをCommandに委譲
        call.resultHandler = Delegate.create( responder, responder.onResult );
        call.faultHandler = Delegate.create( responder, responder.onFault );
    }

    private var responder:Responder;
    private var service:Object;
}

Services

 Servicesは、ServiceLocatorによってルックアップされるサービスを一元管理しておくレジストリのようなものです。クライアント側からアクセスしたいサーバサイドのサービスはすべてこのMXMLに定義します。下のリストは、ログインサンプルのServicesであるServices.mxmlです。

 ログインサンプルでは、サービスを1つしか使用しないためcustomerDelegateのみが定義されています。<mx:RemoteObject>タグを使っているので、サーバサイドのサービスとしてはRemote Objectを使用することは分かりますが、サービスの実装クラスまでは分かりません。namedプロパティの“customerServiceImpl”は、具体的にどのクラスを指しているのでしょうか。

リスト 9 Services.mxml

<cairngorm:ServiceLocator xmlns:mx="http://www.macromedia.com/2003/mxml" 
                          xmlns:cairngorm="http://www.iterationtwo.com/cairngorm">

    <!-- サーバサイドのサービスを定義 -->
    <mx:RemoteObject id="customerDelegate" named="customerServiceImpl"
                     protocol="http"
                     showBusyCursor="true"
                     result="event.call.resultHandler( event )"
                     fault="event.call.faultHandler( event )">
    </mx:RemoteObject>

</cairngorm:ServiceLocator>

 Remote Objectを使ったことがある方ならお分かりかと思いますが、Remote Objectを使うにはFlexの設定ファイルであるWEB-INF/flex/flex-config.xmlに定義をしておかなければなりません。

 下のリストは、ログインサンプルのflex-config.xmlです。<remote-objects>タグ内に“customerServiceImpl”という名前でRemote Objectが1つ定義されているのが分かります。<source>タグに定義されているJavaのクラスが、サービス(Remote Object)の実装になります。このJavaクラスでログインの処理を行い、成功した場合はログインの日付をセットするようになっており、失敗した場合は例外をスローするようになっています。

リスト 10 flex-config..xml(抜粋)

<?xml version="1.0" encoding="UTF-8"?>
<flex-config xmlns="http://www.macromedia.com/2003/flex-config">

  <remote-objects>

    <whitelist>
      <!-- whitelist config for named objects -->
      <named>
        <!-- CAIRNGORM LOGIN SAMPLE -->
        <!-- サービスにつける名前 -->
        <object name="customerServiceImpl">
          <!-- サービスの実装クラス -->
          <source>
            org.nevis.cairngorm.samples.login.business.CustomerDelegate
          </source>
          <type>stateless-class</type>
        </object>
      </named>
    </whitelist>
       
  </remote-objects>
</flex-config>

  以上、今回はログインサンプルのソースコードを見ながらCairngorm Frameworkのアーキテクチャについて解説しました。第1回「開発品質を均一化させるCairngorm Frameworkとは?」でCairngorm Frameworkの特徴として挙げたオブジェクト指向、デザインパターンのコラボレーション(マイクロアーキテクチャ)、イベント・リスナモデルが、実践されているのがお分かりいただけたのではないでしょうか。

 次回は、実際にサンプルアプリケーションを作成する予定です。

Cairngorm Store

 Cairngorm Frameworkには、本稿で紹介したログインサンプルのほかにFlex Store(Flexに付属しているショッピングサイトのサンプル)をCairngorm Frameworkを使って作り直したCairngorm Storeというサンプルも付属しています。こちらの方は、より実践的なサンプルになっていますので興味のある方はぜひ、見てみてください。


2/2  

 INDEX

連載:Flexのクライアントサイドをオープンソースで制覇する(3)
 Flexのフレームワーク、Cairngormのアーキテクチャ
  Page1 アーキテクチャの全体像
View(MXML)/ViewHelper/ValueObject
Page2 Controller
Command/ModelLocator/BusinessDelegate/Services




HTML5 + UX フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

HTML5+UX 記事ランキング

本日 月間