Grails によるリッチ・インターネット・アプリケーション: 第 1 回 Grails と Flex を使って Web アプリケーションを構築する

リッチ・インターネット・アプリケーション (RIA) では、デスクトップ・アプリケーションが持つダイナミズムと機能を、ブラウザーを介して実現します。その重要な特徴の 1 つは、プレゼンテーション層をクライアントに移し、そのプレゼンテーション層をサーバー・サイドの堅牢な RESTful サービス層が支えることです。この概念は現在、SOUI (Service Oriented User Interface)、SOFEA (Service Oriented Front End Architecture) といった用語で広まってきています。2 回連載の第 1 回目となるこの記事では、Groovy の Grails Web アプリケーション・フレームワークを使うと、いかに容易に Web サービスのバックエンドを作成できるかを明らかにします。そしてこのバックエンドを、Adobe の Flex フレームワークで開発した RIA に接続します。

Michael Galpin, Software architect, eBay

Galpin Michael photoMichael Galpin は 90年代後半から Web アプリケーションの開発に従事しています。彼は California Institute of Technology で数学の学位を取得しており、現在は、カリフォルニア州サンノゼにある eBay にアーキテクトとして勤務しています。



2009年 2月 24日

この連載について

この連載では、Grails フレームワークで実装したバックエンドにおいてサービス指向アーキテクチャー (SOA) を使用するアプリケーション・アーキテクチャーを取り上げ、Grails によって、一般的な Web アプリケーションおよび特定の Web サービスを作成する過程がいかに簡易化されるかを探っていきます。Grails で実装したバックエンドは、純粋なあらゆるクライアント・サイド・アプリケーションに簡単に接続することができます。第 1 回では Adobe Flex を使用して、Flash Player を利用するアプリケーションを作成します。続く第 2 回では Google Web Toolkit を使用して、純粋な JavaScript でフロントエンドを作成します。

前提条件

この記事では、Grails と Flex を使用して Web アプリケーションを構築します。Grails フレームワークがベースとしているのは、Java™ プラットフォーム用の動的言語である Groovy プログラミング言語です。この記事を読む上では、Groovy に関する十分な知識があれば理想的ですが、絶対に必要というわけではありません。代わりに Java に関する知識、あるいは Ruby や Python などの他の動的言語の知識があれば十分です。この記事では、Grails 1.0.3 を使用しました (「参考文献」を参照)。Grails はいくつものデータベースやアプリケーション・サーバーと連動することができますが、この記事ではデータベースもアプリケーション・サーバーも用意する必要はありません。Grails にはその両方が備わっているからです。フロントエンドの作成に使用する Flex は、ActionScript プログラミング言語を使用して Flash Player 上で稼働するアプリケーション・フレームワークです。この場合も同じく、Flex と ActionScript に関する十分な知識がなくても心配は要りません。Java および JavaScript を使い慣れていれば、Flex についても理解できるはずです。この記事のコードをコンパイルするには、Flex SDK 3.2 以降が必要です (「参考文献」を参照)。また、アプリケーションを実行するには Flash Player Version 10.0 以降が必要になります。

アーキテクチャー

現在、多くの組織がサービス指向アーキテクチャー (SOA) を実現しようとしています。その目的は大抵の場合、アーキテクチャーをよりアジャイルなものにすることで、ビジネスを素早く展開できるようにするためです。もちろん、組織にはこの他にももう 1 つ、早急に実行に移さなければならない構想があります。それは、ユーザー・インターフェースを今風のリッチ・インターネット・アプリケーション (RIA) にすることです。この流行の SOA と RIA の両方を扱うのは容易なことではありませんが、実は、この 2 つはとても効果的に連動します。つまり、SOA 設計を使用してサービスをアプリケーション・サーバーにデプロイし、プレゼンテーション・ロジックをまるごとクライアントに移せば、Flex などの強力なフロントエンド技術を利用して RIA を作成することができます。これがまさに、この連載で行おうとしている内容です。その最初のステップとして、まずは Grails を使用して Web サービスを作成します。

Web サービス

多くの開発者は Web サービスという言葉を聞くと、真っ先に SOAP (Simple Object Access Protocol) のことを考えます。彼らにとって、これはネガティブな意味を持ちます。SOAP は重くて複雑な技術だと考えられているからです。しかし、Web サービスには何も SOAP を使用する必要はありません。最近では REST (Representational State Transfer) スタイルの Web サービスが、その単純な動作によって大きな支持を得るようになってきました。RESTful Web サービスは、作成するのも使用するのも簡単です。また、RESTful Web サービスでは SOAP サービスと同じく XML を使用できますが、この場合の XML は、SOAP のような入り組んだラッパーやヘッダーが一切ない POX (Plain Old XML) にすることができます。そして Grails フレームワークが、このような Web サービスを極めて簡単に作成できるようにします。その仕組みを理解してもらうため、まずは Grails のドメイン・モデルから説明します。

Grails のドメイン・モデル

Grails は、汎用の Web 開発フレームワークです。ほとんどの Web アプリケーションはアプリケーションで使用するデータの保存や取得にリレーショナル・データベースを利用するため、Grails には GORM と呼ばれる強力なオブジェクト・リレーショナル・モデリング (ORM: Object Relational Modeling) 技術が備わっています。GORM を使用すると、簡単にドメイン・オブジェクトをモデル化することができ、SQL を一切扱わずにそのモデルを任意のリレーショナル・データベースに保存することができます。GORM はよく使われている Hibernate ライブラリーを使用して、それぞれのデータベースに合わせて最適化した SQL を生成するとともに、ドメイン・オブジェクトのライフサイクルを管理します。ここで、GORM を使用したサービスの作成に入る前に、これから作成するアプリケーションと、GORM でモデル化する必要がある内容について簡単に説明しておきます。

このサンプル・アプリケーションでは、人気の高いサイト、Digg (「参考文献」を参照) の機能を模倣した Web アプリケーションを作成します。Digg では、ユーザーがニュースやブログなどの記事 (Web ページ) へのリンクを投稿することができます。すると、他のユーザーがこれらの記事を読んで、賛成票または反対票を投じることができるようになっています。サンプル・アプリケーションには、この基本的な機能をすべて盛り込みます。このアプリケーションでは、ユーザーが匿名で記事を投稿したり、記事に投票したりできるようにするので、ユーザーをモデル化する必要はありません。モデル化が必要なのは、記事だけです。この点を念頭に置いて、リスト 1 に記載する、サンプル・アプリケーションの記事 (Story) の GORM モデルを見てください。

リスト 1. Story モデル
class Story {
    String link
    String title
    String description
    String tags
    String category
    int votesFor
    int votesAgainst
}

ドメイン・オブジェクトのモデル化に必要なコードは、これだけです。オブジェクトのプロパティーとそれぞれのプロパティーの型を宣言すると、Grails が自動的にテーブルを作成し、そのテーブルからデータを読み取るためのメソッド、そしてテーブルにデータを書き込むためのメソッドを動的に作成します。これは、Grails ならではの大きな利点の 1 つです。私たちに必要な作業は、データをモデル化するコードを 1 箇所に集めることだけで、単純な読み取り操作と書き込み操作のためのボイラープレート・コードを作成する必要は一切ありません。これでドメイン・モデルは用意できたので、次は、このドメイン・モデルを使用するビジネス・サービスの作成に取り掛かります。

ビジネス・サービス

SOA の利点の 1 つは、システムを極めて自然な形でモデル化できることです。このアプリケーションでは、どのような操作を実行する必要があるかを考えてみてください。それがつまり、アプリケーションのビジネス・サービスをどのように定義するかを決めることになります。例えば、記事を参照して検索できるようにしたいとしたら、そのためのサービスを作成します (リスト 2 を参照)。

リスト 2. 検索サービス
class SearchService {

    boolean transactional = false

    def list() {
        Story.list()
    }

    def listCategory(catName){
        Story.findAllWhere(category:catName)
    }
    
    def searchTag(tag){
        Story.findAllByTagsIlike("%"+tag+"%")
    }
}

上記でまず目に留まるのは、ブール値フラグ transactional でしょう。Grails はいくつもの実証済み技術の上に構築されていますが、その中には Spring も含まれています。Grails は、サービスにトランザクションを適用する際に Spring の宣言型トランザクションを使用します。データの更新操作を実行中であったり、このサービスを他のトランザクション・サービスで使えるようにしたりする場合には、トランザクションを有効にしなければなりませんが、このサービスは読み取り専用サービスなのでその必要はありません。また、このコードがデータ・アクセスに取って代わることはありません。すでに、データ・アクセスのすべてを処理するドメイン・モデルがあるからです。

上記の最初のサービス操作では、すべての記事を取得します。2 番目の操作では、特定のカテゴリーに対応する記事を取得しますが、そのために使用するのは、GORM の where スタイルのファインダーです。このファインダーを使うことによって、WHERE 節を構成する名前と値のペアのマップを SQL クエリーに渡すことができます。3 番目の操作では、特定のタグが設定された記事を検索することができます。この場合に使用するのは、GORM の動的ファインダーです。こうすると、ファインダー・メソッドの名前の一部としてクエリー節を組み込めるようになります。したがって、tags 列に対するクエリーでは findAllByTags を使用します。また、like サフィックスにも注目してください。これは、大文字と小文字を区別しない SQL LIKE 節です。このサフィックスを使用することによって、tags 属性のサブストリングとして tag パラメーターが含まれているすべての記事を検索することができます。例えば、記事に「BarackObama」というタグが設定されていれば、「obama」を検索した場合でも、この記事が検出されます。

これで、記事を検索するための単純ながらも強力な 3 つの手段を作成できました。注目すべき点は、この構文がいかに簡潔であるかです。Java プログラマーがこれと同じものを作成するとしたら、どれだけ大きなコードになるかを想像してみてください。この検索サービスは強力ですが、検索する対象がまだありません。そこでもう 1 つ必要になってくるのが、個々の記事を管理するサービスです。このサービスには記事サービスという名前を付けました (リスト 3 を参照)。

リスト 3. 記事サービス
class StoryService {

    boolean transactional = true

    def create(story) {
        story.votesFor = 0
        story.votesAgainst = 0
        log.info("Creating story="+story.title)
        if(!story.save(flush:true) ) {
            story.errors.each {
                log.error(it)
            }
        }
        log.info("Saved story="+story.title + " id=" + story.id)
        story
    }
    def voteFor(storyId){
        log.info("Getting story for id="+storyId)
        def story = Story.get(storyId)
        log.info("Story found title="+story.title + " 
votesFor="+story.votesFor)
        story.votesFor += 1
        if(!story.save(flush:true) ) {
            story.errors.each {
                log.error(it)
            }
        }
        story
    }
    def voteAgainst(storyId){
        log.info("Getting story for id="+storyId)
        def story = Story.get(storyId)
        log.info("Story found title="+story.title + " 
votesAgainst="+story.votesAgainst)
        story.votesAgainst += 1
        if(!story.save(flush:true) ) {
            story.errors.each {
                log.error(it)
            }
        }
        story        
    }
}

記事サービスには 3 つの操作があります。1 つは、記事を新規に作成する操作です。他の 2 つはそれぞれ、記事の ID を使用して特定の記事に対する賛成票、反対票を投じるための操作です。賛成の場合も反対の場合も、ロギングを行っていることに注目してください。Grails ではロギングが簡易化されていて、単に暗黙的ログ・オブジェクトと併せ、典型的な log4j スタイルのメソッド、log.debuglog.infolog.error などを使用するだけです。記事は、story インスタンスの save メソッドを使って保存 (挿入または削除されるかのどちらか) します。ここで注目する点は、story インスタンスの errors プロパティーを確認することによってエラーをチェックする方法です。例えば、記事の必須フィールドに値が欠けていたりすると、story.errors の一部として示されます。最後にもう 1 点、このサービスにトランザクションが適用されていることに注目してください。これにより、いずれかの操作が呼び出されたときに、既存のトランザクションを使用するのか (すでにトランザクションがある場合)、あるいは新しいトランザクションを作成するのかが必ず Grails (つまり、Spring) に伝えられます。投票操作の場合、まずデータベースから記事を読み取り、それから列を更新する必要があるため、この点は特に重要です。基本的なビジネス・サービスが揃ったところで、これから、これらのサービスを中心とした Web サービスを作成してビジネス・サービスを API として公開する作業に入ります。

API の公開

開発者はAPI を、他の開発者が呼び出せるように作成するコードとして考えがちです。SOA の場合、この考えはまだ当てはまるとは言え、ここでの API は Java インターフェースやそれに代わるものではありません。この API は Web サービスのシグニチャーです。Web サービスが、API を公開して他の開発者がこの API を呼び出せるようにします。Web サービスを簡単に作成できるようにする Grails では、Web サービスは Grails アプリケーション内に含まれる 1 つのコントローラーに過ぎません。リスト 4 に記載する、このサンプル・アプリケーションの API を見てください。

リスト 4. API コントローラー
import grails.converters.*

class ApiController {
    // injected services
    def searchService
    def storyService
    
    // set the default action
    def defaultAction = "stories"

    def stories = {
        stories = searchService.list()
        render stories as XML
    }
    
    def submit = {
        def story = new Story(params)
        story = storyService.create(story)
        log.info("Story saved story="+story)
        render story as XML
    }
    
    def digg = {
        def story = storyService.voteFor(params.id)
        render story as XML
    }
    
    def bury = {
        def story = storyService.voteAgainst(params.id)
        render story as XML
    }
}

上記でまず気付くことは、前のセクションで作成したビジネス・サービスの存在でしょう。これらのサービスは、Grails によって自動的に注入されることになります。実際には、Grails がベースとする技術の 1 つ、Spring フレームワークがビジネス・サービスを注入したことになります。命名規則に従うだけで (SearchService クラスのインスタンスを参照するために使用するインスタンス変数には searchService を使うなど)、後は Grails がすべてを引き受けてくれます。

この API には、storiessubmitdigg、そして bury という 4 つの呼び出し可能な操作 (アクション) があります。いずれの操作を呼び出す場合も、ビジネス・サービスに呼び出しを委任し、呼び出しの結果を取得して、その結果をクライアントに送信するための XML にシリアライズします。Grails ではこの処理は容易に行うことができ、render 関数と XML コンバーターを使用するだけでよくなっています。最後に、submitdigg、そして bury アクションのすべてが params オブジェクトを使用していることに注目してください。params オブジェクトは、HTTP リクエストのパラメーターとそれぞれの値からなるハッシュ・テーブルです。diggbury の場合は、このオブジェクトから id パラメーターだけを取得します。submit アクションの場合には、params オブジェクト全体を Story クラスのコンストラクターに渡しています。そこで使用されるのは、Grails のデータ・バインディングです。パラメーター名がクラスのプロパティー名と一致する限り、Grails は自動的にデータ・バインディングを設定します。これは、Grails が開発を容易にする単なる一例に過ぎません。作成したのはほんのわずかなコードですが、この最小限のコードだけで、サービスを作成して Web サービスとして公開することができます。次は、これらのサービスが使用するリッチなプレゼンテーション層を作成します。


プレゼンテーション

アプリケーションのバックエンドに SOA を使用したことから、これをベースに、さまざまな種類のプレゼンテーションを作成することができます。この記事ではまず、Flex フレームワークを使用して Flash ベースのユーザー・インターフェースを作成しますが、Flex 以外のクライアント・サイドのプレゼンテーション技術を使用することもでき、Flex 同様に簡単です。さらに、「シック」デスクトップ・クライアントを作成することも可能ですが、Web クライアントがリッチなユーザー・エクスペリエンスをもたらしてくれるため、その必要はありません。ここでは以下の単純な使用ケースのそれぞれに、Flex を使ってユーザー・インターフェースを作成します。まずは、すべての記事をリストアップするところから取り掛かります。

記事のリストアップ

記事をリストアップするには、どの API をバックエンドから呼び出すかがわかっていなければなりません。先ほどバックエンドを作成したときに、特定の URL を API コントローラーとそのメソッドにマッピングしたでしょうか?明らかにその作業はしていません。しかしここでも Grails が作業を楽にしてくれます。Grails は Convention over Configuration (設定より規約) の原則に従うので、digg アプリケーションに対して API コントローラーの stories アクションを呼び出す場合の URL は、http://<root>/digg/api/stories となります。また、これは RESTful Web サービスであることから、呼び出すときは HTTP GET が使われます。ブラウザーで直接、この URL を呼び出すと、リスト 5 に記載するような内容の XML が返されます。

リスト 5. XML レスポンスの例
<?xml version="1.0" encoding="UTF-8"?><list>
  <story id="12">
    <category>technology</category>
    <description>This session discusses approaches and techniques to 
implementing Data Visualization and Dashboards within Flex 3.</description>
    <link>http://onflash.org/ted/2008/10/360flex-sj-
2008-data-visualization-and.php</link>
    <tags>flash, flex</tags>
    <title>Data Visualization and Dashboards</title>
    <votesAgainst>0</votesAgainst>
    <votesFor>0</votesFor>
  </story>
  <story id="13">
    <category>technology</category>
    <description>Make your code snippets like really nice when you blog about 
programming.</description>
    <link>http://bc-squared.blogspot.com/2008/07/
syntax-highlighting-and-code-snippets.html</link>
    <tags>programming, blogging, javascript</tags>
    <title>Syntax Highlighting and Code Snippets in a blog</title>
    <votesAgainst>0</votesAgainst>
    <votesFor>0</votesFor>
  </story>
  <story id="14">
    <category>miscellaneous</category>
    <description>You need a get notebook if you are going to take 
good notes.</description>
    <link>http://www.randsinrepose.com/archives/2008/06/01/
sweet_decay.html</link>
    <tags>notebooks</tags>
    <title>Sweet Decay</title>
    <votesAgainst>0</votesAgainst>
    <votesFor>0</votesFor>
  </story>
  <story id="16">
    <category>technology</category>
    <description>If there was one thing I could teach every engineer, it 
would be how to market. </description>
    <link>http://www.codinghorror.com/blog/archives/001177.html</link>
    <tags>programming, funny</tags>
    <title>The One Thing Every Software Engineer Should Know</title>
    <votesAgainst>0</votesAgainst>
    <votesFor>0</votesFor>
  </story>
</list>

言うまでもなく、レスポンスの内容はデータベースに保存されているデータによって多少異なりますが、ここで注目すべき重要な点は、Grails が Groovy オブジェクトをシリアライズすることで生成される XML の構造です。この構造がわかったところで、今度はサービスを操作するための ActionScript コードを作成します。

データ・アクセス

プレゼンテーション層をまるごとクライアントに移すことによってもたらされるメリットの 1 つは、アーキテクチャーが大幅に簡潔になることです。サーバーとクライアント間にまたがるものが何もなくなるため、従来のモデル・ビュー・コントローラー (MVC) パラダイムに従いやすくなります。そこでこのパラダイムに従って、まずはデータ構造を表現するためのモデルに取り掛かり、その上で、データへのアクセスをカプセル化します。リスト 6 の Story クラスには、モデルの部分が示されています。

リスト 6. Story クラス
public class Story extends EventDispatcher
{
    private static const LIST_URL:String = 
    "http://localhost:8080/digg/api/stories";    
                    
    [Bindable] public var id:Number;        
    [Bindable] public var title:String;
    [Bindable] public var link:String;
    [Bindable] public var category:String;
    [Bindable] public var description:String;
    [Bindable] public var tags:String;
    [Bindable] public var votesFor:int;
    [Bindable] public var votesAgainst:int;
        
    private static var listStoriesLoader:URLLoader;
    private static var dispatcher:Story = new Story();
        
        public function Story(data:XML=null)
        {
            if (data)
            {
                id = data.@id;
                title = data.title;
                link = data.link;
                category = data.category;
                description = data.description;
                tags = data.tags;
                votesFor = Number(data.votesFor);
                votesAgainst = Number(data.votesAgainst);
            }
        }

}

リスト 6 には、Story クラスの基本が示されています。このクラスに含まれる複数のフィールドは、サービスから取得するデータ構造に対応します。コンストラクターはオプションの XML オブジェクトを引数に取り、そのオブジェクトのデータをフィールドに設定します。XML データにアクセスするための構文が、いかに簡潔であるかに注目してください。これは、ActionScript が XML を操作する簡単な手段として、E4X 標準を実装しているからです。E4X 標準は XPath と似ていますが、XPath よりもオブジェクト指向プログラミング言語に適した自然な構文を使用します。また、それぞれのプロパティーが [Bindable] で修飾されていることにも注目してください。これは ActionScript のアノテーションで、UI コンポーネントをフィールドにバインドすることで、フィールドが変更された場合に UI が自動的に更新されるようにします。最後に注目してもらいたいのは、静的変数 listStoriesLoader です。これは、HTTP リクエストを送信するために使用する ActionScript クラス URLLoader のインスタンスです。Story クラスの静的メソッドは API を介してすべての記事をロードする際に、このインスタンスを使用します。リスト 7 に、このメソッドを記載します。

リスト 7. 記事をリストアップするメソッド
public class Story extends EventDispatcher
{
    public static function list(loadHandler:Function, errHandler:Function=null):void
    {
        var req:URLRequest = new URLRequest(LIST_URL);
        listStoriesLoader = new URLLoader(req);
        dispatcher.addEventListener(DiggEvent.ON_LIST_SUCCESS, loadHandler);
        if (errHandler != null)
        {
            dispatcher.addEventListener(DiggEvent.ON_LIST_FAILURE, errHandler);
        }
        listStoriesLoader.addEventListener(Event.COMPLETE, listHandler);
        listStoriesLoader.addEventListener(IOErrorEvent.IO_ERROR, listErrorHandler);
        listStoriesLoader.load(req);
    }
    private static function listHandler(e:Event):void
    {
        var event:DiggEvent = new DiggEvent(DiggEvent.ON_LIST_SUCCESS);
        var data:Array = [];
        var storiesXml:XML = XML(listStoriesLoader.data);
        for (var i:int=0;i<storiesXml.children().length();i++)
        {
            var storyXml:XML = storiesXml.story[i];
            var story:Story = new Story(storyXml);
            data[data.length] = story;
        }
        event.data = data;
        dispatcher.dispatchEvent(event);
    }
}

この list メソッドはコントローラーから呼び出されるようになるメソッドで、HTTP リクエストを送信し、リクエストの完了イベントを捕捉するイベント・リスナーを登録します。このようなイベント・リスナーが必要な理由は、Flash では、HTTP リクエストが非同期で行われるためです。リクエストが完了すると listHandler メソッドが呼び出されます。このメソッドは、同じく E4X を使用して、サービスから返された XML データ全体を構文解析し、Story インスタンスの配列を作成してカスタム・イベントにデータとして付加します。その上で、カスタム・イベントがディスパッチされるという仕組みです。このカスタム・イベントをリスト 8 に記載します。

リスト 8. DiggEvent
public class DiggEvent extends Event
{
    public static const ON_STORY_SUBMIT_SUCCESS:String = "onStorySubmitSuccess";
    public static const ON_STORY_SUBMIT_FAILURE:String = "onStorySubmitFailure";
    public static const ON_LIST_SUCCESS:String = "onListSuccess";
    public static const ON_LIST_FAILURE:String = "onListFailure";
    public static const ON_STORY_VOTE_SUCCESS:String = "onStoryVoteSuccess";
    public static const ON_STORY_VOTE_FAILURE:String = "onStoryVoteFailure";
        
    public var data:Object = {};
    public function DiggEvent(type:String, bubbles:Boolean=false, 
cancelable:Boolean=false)
    {
        super(type, bubbles, cancelable);
    }
    
}

サーバーとのやりとりはすべて非同期で行わなければならないため、カスタム・イベント・クラスの使用は ActionScript 開発では共通のパラダイムとなります。カスタム・イベント・クラスを使用することで、コントローラーはモデル・クラスのメソッドを呼び出し、カスタム・イベントを対象とした固有のイベント・ハンドラーを登録することができます。カスタム・イベントには、さらにフィールドを追加することもできますが、この例では、汎用的で再利用可能なデータ・フィールドを追加するだけにとどめました。ここまでプレゼンテーション層のモデルについて説明したので、今度はこのモデルがどのようにコントローラーに使用されるかを説明します。

アプリケーション・コントローラー

コントローラーの役割は、モデルへの呼び出しを調整し、モデルをビューに提供し、そしてビューからのイベントに応答することです。リスト 9 に記載するコントローラーのコードを見てください。

リスト 9. DiggController
public class DiggController extends Application
{
    [Bindable]
    public var stories:ArrayCollection;
    [Bindable]
    public var subBtnLabel:String = 'Submit a new Story';
    
    public function DiggController()
    {
        super();
        init();
    }
    
    private function init():void
    {
        Story.list(function(e:DiggEvent):void{  
            stories = new ArrayCollection(e.data as Array);});            
    }
}

このコントローラーは、コアとなる Flex クラス Application を継承します。Flex に慣用的なこの手法によって、コントローラーとそのビューが簡単に関連付けられるようになります。これについては、次のセクションで説明します。コントローラーにはバインド可能な記事のコレクションがあるため、UI コントロールにはこのコレクションをバインドすることができます。アプリケーションがコントローラーのロードを完了すると、コントローラーはすぐに Story.list メソッドを呼び出し、匿名関数 (ラムダ) を Story.list メソッドのハンドラーとして渡します。ラムダ式は単に、カスタム・イベントからのデータを記事のコレクションにダンプするだけです。次は、このコントローラーがビューでどのように使用されるのかを見てみましょう。

ビュー

前述したように、コントローラーは Flex の Application クラスを継承しています。このクラスは、すべての Flex アプリケーションの基底クラスです。Flex では、MXML (XML の方言) を使用して宣言型で UI を作成できるため、コントローラーはこの構文を利用することができます (リスト 10 を参照)。

リスト 10. ユーザー・インターフェース
<?xml version="1.0" encoding="utf-8"?>
<ctrl:DiggController xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" 
    xmlns:works="components.*" xmlns:ctrl="controllers.*">
    <mx:Script>
        <![CDATA[
            import org.developerworks.digg.Story;

            private function digg():void
            {
                this.diggStory(results.selectedItem as Story);
            }
            private function bury():void
            {
                this.buryStory(results.selectedItem as Story);
            }
        ]]>
    </mx:Script>
    <ctrl:states>
        <mx:State name="SubmitStory">
            <mx:AddChild relativeTo="{buttons}" position="after">
                <works:StoryEditor successHandler="{this.submissionHandler}"/>
            </mx:AddChild>
        </mx:State>
    </ctrl:states>
    <mx:DataGrid id="results" dataProvider="{stories}" doubleClickEnabled="true" 
        doubleClick="openStory(results.selectedItem as Story)"/>
    <mx:HBox id="buttons">
        <mx:Button label="Digg the Story!" click="digg()"/>
        <mx:Button label="Bury the Story!" click="bury()"/>
        <mx:Button label="{this.subBtnLabel}" click="toggleSubmitStory()"/>
    </mx:HBox>
</ctrl:DiggController>

ルート文書が ctrl 名前空間を使用している点に注意してください。これは、コントローラー・クラスを配置する controllers フォルダーを指すために宣言されています。したがって、コントローラー・クラスから返されるあらゆるものが、UI コードの中で使用できるようになります。例えば、dataProvider プロパティーが stories に設定されたデータ・グリッドがありますが、これはコントローラー・クラスからの stories 変数で、DataGrid コンポーネントに直接バインドします。Flex コードを SWF ファイルにコンパイルする準備は、これで整いました。後は SWF を組み込むための静的 HTML ファイルが必要なだけです。上記のコードを実行すると、図 1 のように表示されます。

図 1. Digg アプリケーション
Digg アプリケーション

上記は、Flex アプリケーションのデフォルトのルック・アンド・フィールです。色のスタイル設定は、HTML アプリケーションの場合と同じように標準 CSS を使って簡単に行えます。また、DataGrid コンポーネントをカスタマイズして、列、列の順序などを指定することも可能です。「Digg the Story!」ボタンと「Bury the Story!」ボタンはどちらもコントローラー・クラスの関数を呼び出します。もう 1 つ注意する点として、コントローラーの関数は DataGrid のダブルクリック・イベントに接続されています。コントローラーのすべてのメソッドは、MVC アーキテクチャーでは当然のことながら、モデルを使用します。最後のボタン、「Submit a new Story」は Flex の重要な機能をいくつか使用します。このボタンがどのように機能するかについて、これから説明します。


記事の投稿

リスト 10 を見るとわかるように、「Submit a new Story」ボタンをクリックすると、コントローラー・クラスの toggleSubmitStory メソッドが呼び出されます。このメソッドのコードは、リスト 11 のとおりです。

リスト 11. toggleSubmitStory メソッド
public class DiggController extends Application
{

    public function toggleSubmitStory():void
    {
        if (this.currentState != 'SubmitStory')
        {
            this.currentState = 'SubmitStory';
            subBtnLabel = 'Nevermind';
        }
        else
        {
            this.currentState = '';
            subBtnLabel = 'Submit a new Story';
        }
    }        
}

この関数はアプリケーションの currentState プロパティーを SubmitStory に変更します。リスト 10 をもう一度見てみると、この状態がどこに定義されているのかがわかるはずです。これらの状態を使用して、コンポーネントを追加または削除したり、あるいは既存のコンポーネントでプロパティーを設定したりすることができます。ここでは、UI に新規コンポーネントを追加します。この新規コンポーネントが、記事を投稿するためのカスタム・コンポーネントとなります。リスト 12 に、このコンポーネントを記載します。

リスト 12. StoryEditor コンポーネント
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%">
    <mx:Form>
        <mx:FormHeading label="Submit a New Story"/>
        <mx:FormItem label="What's the URL?" required="true">
            <mx:TextInput id="linkBox" toolTip="Keep it Short and Sweet" 
                text="{story.link}"/>
        </mx:FormItem>
        <mx:FormItem label="Give it a Title" required="true">
            <mx:TextInput id="titleBox" text="{story.title}"/>
        </mx:FormItem>
        <mx:FormItem label="Pick a Category" required="true">
            <mx:ComboBox dataProvider="{Story.CATEGORIES}" id="categoryBox" 
selectedIndex="0"/>
        </mx:FormItem>
        <mx:FormItem label="Give a Short Description">
            <mx:TextArea height="60" width="320" id="descripBox" 
                text="{story.description}"/>
        </mx:FormItem>
        <mx:FormItem label="Tag It">
            <mx:TextInput id="tagBox" text="{story.tags}"/>
        </mx:FormItem>
        <mx:Button label="Submit It!" click="submitStory()"/>
    </mx:Form>
    <mx:Binding source="linkBox.text" destination="story.link"/>
    <mx:Binding source="titleBox.text" destination="story.title"/>
    <mx:Binding source="categoryBox.selectedItem.data" 
destination="story.category"/>
    <mx:Binding source="descripBox.text" destination="story.description"/>
    <mx:Binding source="tagBox.text" destination="story.tags"/>    
</mx:VBox>

リスト 12 には、カスタム・コンポーネントの UI 要素だけを記載しています。これは単純なフォームですが、ここに含まれるバインディング宣言に注目してください。バインディング宣言をフォームに含めることによって、UI フォームの要素を Story インスタンス、つまり記事に直接バインドすることが可能になります。それが宣言されているのが、リスト 13 のスクリプト・ブロックです。

リスト 13. StoryEditor のスクリプト
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%">
    <mx:Script>
        <![CDATA[
            import mx.controls.Alert;
            import org.developerworks.digg.*;
            
            [Bindable]
            private var story:Story = new Story();
            public var successHandler:Function;
            
            private function submitStory():void
            {
                story.addEventListener(DiggEvent.ON_STORY_SUBMIT_SUCCESS, 
successHandler);
                story.addEventListener(DiggEvent.ON_STORY_SUBMIT_FAILURE, 
errorHandler);
                story.save();
                // reset
                story = new Story();
            }
                        
            private function errorHandler(e:Event):void
            {
                Alert.show("Fail! : " + e.toString());                
            }
        ]]>
    </mx:Script>
</mx:VBox>

上記のスクリプト・ブロックは、コンポーネントのコントローラー・コードとして動作します。Flex では、フォームとスクリプトのスタイルは共通しているので、コンポーネント用の 1 つのコントローラー・クラスにフォームとスタイルを両方とも配置するのも簡単です。図 1 のページで「Submit new Story」ボタンをクリックすると、図 2 に示すカスタム・コンポーネントが表示されます。

図 2. カスタム・コンポーネントの表示
カスタム・コンポーネントの表示

このコンポーネントを使って新しい記事を追加すると、バックエンドのサービスが呼び出されて新規記事の XML が返されます。つまり、Story インスタンスに再び変換されて、DataGrid にバインドされた記事のコレクションに追加されることにより、自動的に UI に表示されるというわけです。これでプレゼンテーション・コードは完成し、バックエンドのサービスに接続されました。


まとめ

この記事を読んで、Grails では簡単に Web サービスを作成できることがわかったはずです。データベースを作成するための SQL や、データベースとの読み取り/書き込み操作を行うための SQL を作成する必要はまったくありません。Grails では、URL を Groovy コードにマッピングするのも、サービスを呼び出して Web サービスの XML を作成するのも簡単です。作成された XML は、Flex フロントエンドが容易に使用できます。この記事では、フロントエンドに簡潔な MVC アーキテクチャーを作成する方法、そして E4X、データ・バインディング、状態、カスタム・コンポーネントなどの Flex が持つ多数の高度な機能を使用する方法も説明しました。

この連載の第 2 回では、Google Web Toolkit を使用して、サービスに JavaScript ベースの UI を使用する方法を説明します。


ダウンロード

内容ファイル名サイズ
Example Grails applicationdigg.zip504KB
Example Flex app sourcedigg-flex-src.zip10KB

参考文献

学ぶために

製品や技術を入手するために

  • Grails: この記事では、Grails バージョン 1.0.3 を使用しています。
  • Flex 3 SDK を入手してください。
  • Adobe Flash Player Version 10 以降を入手してください。
  • Java SDK: この記事では Java SE 1.6_05 を使用しています。
  • IBM ソフトウェアの試用版を使用して、次のオープンソース開発プロジェクトを革新してください。ダウンロード、あるいは DVD で入手できます。
  • IBM 製品の評価版をダウンロードして、DB2、Lotus、Rational、Tivoli、および WebSphere のアプリケーション開発ツールとミドルウェア製品を使ってみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source
ArticleID=379495
ArticleTitle=Grails によるリッチ・インターネット・アプリケーション: 第 1 回 Grails と Flex を使って Web アプリケーションを構築する
publish-date=02242009