本文へジャンプ

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


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

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

  • 閉じる [x]

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

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

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


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

  • 閉じる [x]

Google App Engine をベースに Eclipse を使用して作成するマッシュアップ: 第 3 回 RESTful な Web サービスを使用する

Michael Galpin, Software architect, eBay
Galpin Michael photo
Michael Galpin は、1998年以来、プロとして Java ソフトウェアを開発してきています。彼は現在 eBay に勤務しています。彼は California Institute of Technology で数学の学位を取得しています。

概要: ソーシャル・ネットワークでは、画期的な新しい Web アプリケーションを作成するためのデータを容易に収集してマッシュアップできるようになっていますが、それでも、スケーラブルな Web アプリケーションを作成する上で常に伴う問題のすべては自分で処理しなければなりません。この部分の負担を軽くしてくれるのが、GAE (Google App Engine) です。GAE を使用すれば、アプリケーション・サーバーのプールの管理について一切忘れ、優れたマッシュアップを作成することに専念できます。3 回連載の「Google App Engine をベースに Eclipse を使用して作成するマッシュアップ」の最終回では、これまでに作成したアプリケーションをさらに拡張していきます。拡張の内容としては、まず、このアプリケーションの他のユーザーを表示して彼らの集約フィードを購読する機能を加えます。次にアプリケーションを Web サービスとして公開し、他のマッシュアップにも使えるようにすることで、マッシュアップの輪を完成させます。

このシリーズの他の記事を見る

日付:  2008年 8月 19日
レベル:  中級 この記事の原文:  英語
アクティビティー: 2909 ビュー
お気軽にご意見・ご感想をお寄せください: 


この連載について

この連載では、GAE (Google App Engine) を使い始める方法を紹介します。第 1 回で焦点としたのは、GAE で実行するアプリケーションの作成を開始できるように開発環境をセットアップすることでした。さらに、Eclipse を利用してアプリケーションの開発とデバッグを容易にする方法も説明しました。第 2 回では、Ajax 機能を追加してアプリケーションの機能を強化し、さらにアプリケーションを GAE にデプロイしてから監視する方法を説明しました。第 3 回となる今回の記事では、RESTful な Web サービスを作成してアプリケーションに追加することによってエコシステムに還元し、他の開発者もこのアプリケーションを使ってそれぞれ独自のマッシュアップを作成できるようにします。

GAE は、Web アプリケーションを作成するためのプラットフォームです。GAE で使用するのは Python (現在、Python V2.5.2) であることから、GAE を使うにはこのプログラミング言語についての知識があることが最大の前提条件となります。この連載を読むにあたっては、ある程度の一般的な Web 開発スキル (HTML、JavaScript、CSS の知識) があると役立ちます。また App Engine 向けに開発するには、App Engine SDK をダウンロードする必要があります (「参考文献」を参照)。この連載では、GAE 開発を支援するために Eclipse V3.3.2 も使用しているので (「参考文献」を参照)、Eclipse を Python IDE に変換する PyDev プラグインも必要になります。


購読

これまでの作業で、サンプル・アプリケーションの aggroGator では、ユーザーが複数の人気 Web サービスをマッシュアップして集約フィードを作成できるようになっています。今度はさらに面白みを加えるため、ユーザーが他のユーザーのフィード (他のそれぞれのユーザーのフィード自体も集約フィードである可能性が大です) を購読できるようにします。例えば Twitter、last.fm、del.icio.us で aggroGator フィードを購読するアカウントをセットアップし、他のユーザーがこの aggroGator フィードを購読して、これらのサービスのアクティビティーをすべて表示できるようにします。このような機能に対処するには、データ・モデルをもう一度検討する必要があります。

モデリング

上述のような購読を可能にするには、あるユーザー (アカウント) が他のアカウントの購読者リストのフィードを購読できるようにしなければなりません。1 つの方法として考えられるのは、それぞれのアカウントに対するユーザーのリストを用意することです。新たなユーザーがこのアカウントの購読を開始すると、このリストにそのユーザーが追加されます。そのためのコードは、リスト 1 のようになるはずです。


リスト 1. user リストを使用した Account モデルuser list

class Account(db.Model):
    user = db.UserProperty(required=True)
    subscriptions = db.ListProperty(Account)

この手法の利点は、1 つのアカウントを取得すれば、そのアカウントを購読している他のすべてのアカウントも取得できることです。これは GAE の Bigtable のような非リレーショナル・データ・ストアでよく使われる方法で、正規化といったことは気にせずに、すべての関連データをひとまとめにするというものですが、これには欠点があります。特定のユーザーを購読しているユーザー全員を表示しなければならない場合を考えてみてください。そのための方法としては、すべての Account モデルを取得し、すべての購読を調べ、特定のユーザーがリストに載っているかどうかを確かめるしかありません。代わりに、Account モデルごとに subscriptions (購読) 用のリストと、subscribers (購読者) のリストの 2 つを維持するという方法も考えられますが、ここではその方法は使いません。それよりも従来からの、多対多のモデルを採用することにします (リスト 2 を参照)。


リスト 2. Subscribe モデル

class Subscribe(db.Model):
    subscriber = db.ReferenceProperty(Account, required=True, 
collection_name='subscriptions')
    subscribee = db.ReferenceProperty(Account, required=True, 
collection_name='subscribers')

見てのとおり、このモデルはリレーショナル・データベースで使用する結合テーブルに似ています。GAE では非リレーショナル・データ・ストア (Bigtable) を使うからといって、リレーショナル・データベースで使用する手法を利用できないことにはなりません。これで、データ・モデルの準備は完了です。次は、これらの多対多の関係をどのように作成するのかを、エンド・ユーザーの観点からトップダウン方式で検討していきましょう。

購読の管理

このサンプル・アプリケーションは購読を保存することができるため、ここで必要となるのはユーザーが購読を作成するための方法だけです。その方法として、ユーザーが購読を追加できるページを作成します (リスト 3 を参照)。


リスト 3. アカウント・ページ

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/
    xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Aggrogator Accounts</title>
    <link rel="stylesheet" href="/css/aggrogator.css" type="text/css" />
    <script type="text/javascript" src="/js/prototype.js"></script>
    <script type="text/javascript" src="/js/builder.js"></script>
    <script type="text/javascript" src="/js/effects.js"></script>
    <script type="text/javascript" src="/js/aggrogator.js"></script>
  </head>
  <body>

    <img id="spinner" alt="spinner" src="/gfx/spinner.gif" style="display: none; 
position: fixed;" />

    <div id="logout">
      {{ account.user.nickname }}
      <a href="{{ logout_url }}">Logout</a>
    </div>

    <div class="clearboth"></div>

    <ol>
      {% for acc in all_accounts %}
        <li>
          <a href="" onclick="subscribe('{{ acc.user.email }}'); return false;">
{{ acc.user.email }}</a>
        </li>
      {% endfor %}
    </ol>

  </body>
</html>

ご覧のように、このページは単にシステムのすべてのアカウントを表示するに過ぎません。このページで、ユーザーは購読対象のアカウントをクリックして選択します。皆さんはこれよりも高度なインターフェースを想像できるに違いありません。例えば、このページはユーザーの数が多いと扱いにくくなるため、検索ベースのシステムのほうが好ましいはずです。あるいは、ユーザーが自分のアドレス帳をインポートしたり、OpenSocial のような API を使ったりするなどして、アプリケーションにすでに参加している友人を検索できるシステムにすることも考えられます。上記のテンプレートにはユーザーのリストが必要なので、このページのモデルを作成するコントローラーについて簡単に説明します (リスト 4 を参照)。


リスト 4. アカウント・ページのコントローラー

#Accounts Module
class MainPage(webapp.RequestHandler):
    def get(self):
        # get the current user
        user = users.get_current_user()

        # is user an admin?
        admin = users.is_current_user_admin();

        # create user account if haven't already
        account = aggrogator.DB.getAccount(user)
        if account is None:
            account = aggrogator.Account(user=user)
            account.put()

        # create logout url
        logout_url = users.create_logout_url(self.request.uri)

        all_accounts = aggrogator.Account.all()

        template_values = {
            'account': account,
            'admin': admin,
            'logout_url': logout_url,
            'all_accounts': all_accounts,
            }
        path = os.path.join(os.path.dirname(__file__), 'accounts.html')
        self.response.out.write(template.render(path, template_values))

このコントローラーはアカウント・ページに表示可能なすべてのデータを取得します。リスト 3 のアカウント・ページをもう一度見てみると、アカウントがクリックされると呼び出される JavaScript があることがわかります。


リスト 5. 購読用の JavaScript

function subscribe(email) {
  new Ajax.Request("/accounts/subscribe", {
    method: "post",
    parameters: {'email': email},
    onSuccess: alert('subscribed to ' + email)
  });
}

この JavaScript コードでも、サーバーに対する Ajax リクエストを行うために使用しているのは Prototype ライブラリーです。呼び出している URL は /accounts/subscribe ですが、この URL はどこにマッピングされているのでしょうか?マッピングを作成するコードは、以下に示すように、新しいアカウント・モジュールの main 関数に含まれています。


リスト 6. アカウント・モジュールの URL マッピング

def main():
    app = webapp.WSGIApplication([
        ('/accounts/', MainPage),
        ('/accounts/subscribe', Subscribe),
        ], debug=True)
    util.run_wsgi_app(app)

if __name__ == '__main__':
    main()

main 関数を見るとわかるように、/accounts/subscribe の呼び出しは Subscribe コントローラー・クラスによって処理されます。このクラスはリスト 7 のとおりです。


リスト 7. Subscribe コントローラー・クラス

class Subscribe(webapp.RequestHandler):
    def post(self):
        # get the current user
        user = users.get_current_user()
        email = self.request.get('email')

        aggrogator.DB.create_subscription(user, email)

このコントローラーは単純なもので、まず現行のユーザー (購読者) と、新たに購読を開始するユーザーの E メール・アドレスを取得します。次に、以前にも使用した DB ユーティリティー・クラスの新しいメソッドを呼び出します。Bigtable 関連の呼び出しはすべて、このクラスによって処理されます。新しい create_subscription 関数を以下に記載します。


Lリスト 8. create_subscription の DB 関数

class DB:
    @staticmethod
    def create_subscription(user, email):
        subscriber = DB.getAccount(user)
        subscribee = DB.getAccountForEmail(email)
        subscription = Subscribe.gql("WHERE subscriber = :1 AND subscribee = :2", 
subscriber, subscribee).get()
        if subscription is None:
            Subscribe(subscriber=subscriber, subscribee=subscribee).put()

    @classmethod
    def getAccountForEmail(cls, email):
        user = users.User(email)
        return cls.getAccount(user)

この関数はまず、ユーザーと購読用 E メールそれぞれに対応する Account モデルを検索します。後者の検索で使用するのは、新しい関数、getAccountForEmail です。この関数は GAE のユーザー API を利用して、E メールを基準に User オブジェクトを検索し、それから Bigtable に対してアカウントを問い合わせます。両方のアカウントを取得したら、該当する購読がすでに存在しているかどうかを確認します。既存のものがない場合は、新しい購読を作成します。

これで購読の用意はできたので、次は当然、これらの購読をメインのアプリケーションで利用することにします。しかしここでは現行ユーザーのサービスを表示するだけでなく、集約フィード (ユーザーのサービスとそのユーザーの購読によるサービス) も表示する必要があります。それには、これまでの記事で開発したメイン・モジュールに含まれる GetUserServices コントローラーに多少の変更を加えてください。変更内容は以下のとおりです。


リスト 9. 変更後の GetUserServices コントローラー

class GetUserServices(webapp.RequestHandler):
    def get(self):
        user = users.get_current_user()

        # get the user's services from the cache
        #userServices = aggrogator.Cache.getUserServices(user)
        userServices = aggrogator.Aggrogator.get_services(user.email())

        stats = memcache.get_stats()
        self.response.headers['content-type']  = 'application/json'
        self.response.out.write(simplejson.dumps({'stats': stats, 'userServices': 
userServices}))

変更したのは新しいライブラリー・クラスの呼び出しだけです。ユーザーのサービスだけではなく、aggrogator というその名も相応しいクラスを呼び出して集約サービスを取得しています。このライブラリー・コードを以下に記載します。


リスト 10. aggrogator ライブラリー: 集約サービスの取得

class aggrogator:
    @staticmethod
    def get_services(username):
        accounts = []
        
        primary = DB.getAccountForEmail(username)
        accounts.append(primary)

        for subscription in primary.subscriptions:
            accounts.append(subscription.subscribee)
        services = []
        for account in accounts:
            services.extend(Cache.getUserServices(account.user))   
        return services   

ここで再び、新しく作成した Subscribe モデルの活躍を目にすることができます。上記のコードでは、ユーザー名に対応するアカウントを取得し (前に説明した getAccountForEmail 関数を使用)、それからその購読プロパティーを呼び出します。この場合、キャッシュからすべてのサービスを取得しているだけですが、この後、これらのサービスが集約フィードを作成するために使用されることになります。

新しいアカウント・ページをテストする段階まで至るには、あと 1 つ、必要な変更があります。それは、特定の URL リクエストを新しいアカウント・モジュールに送信するようにアプリケーションを構成することです。そこで、app.yaml ファイルを編集して新しいセクションを追加します。


リスト 11. app.yaml への追加内容

- url: /accounts/.*
  script: accounts.py
  login: required

上記はファイルから新しいセクションだけを抜粋したものです。このコードは、/accounts/ が設定されたリクエストをアカウント・モジュールにマッピングします。そのため、以前使用した catch-all ハンドラー (url: /.*) の前に配置して優先させなければなりません。これで、アプリケーションをテストする準備はすべて完了しました。前と同じく Eclipse と PyDev を使用して、http://localhost:8080/acounts/ にアクセスしてください。テストが面白いものになるように、複数のアカウントを作成することをお忘れなく。


aggroGator Web サービス:

ソーシャル Web サービスでは、aggroGator のような興味深いアプリケーションを極めて簡単に作成できるようになっています。さらに GAE を使えば、非常にスケーラブルなマッシュアップを作成することができます。つまり、マッシュアップを中心とした API/Web サービスを作成し、他のユーザーがそのサービスを使ってそれぞれ独自のマッシュアップを作成できるようにするのは、当然の道理と言えます。しかもこの後わかるように、それは至って簡単に実現できます。

私たちの Web サービスの場合、まずは読み取り可能なサービスにするところから始めます。このサービスは、ユーザーに集約フィードを提供するだけのものにします (つまり、aggroGator UI の場合と同じことです)。URL には、/api?username=my@email.address のような単純な REST スタイルを使用します。今回はボトムアップ方式で取り掛かり、まずはこのような URL を扱うためのセクションを追加します。追加先は、この場合も同じく app.yaml ファイルです。


リスト 12. app.yaml への追加内容

- url: /api
  script: main.py

/api リクエストは依然としてメイン・モジュールに送信されることになっている点に注意してください。app.yaml に新しいマッピングを追加するわけは、aggroGator API に対して認証しなくても済むようにするためです。app.yaml に新しいルールが必要な理由はそれ以外にありません。一方、メイン・モジュールを利用するには、このモジュールを変更する必要があります。


リスト 13. メイン・モジュールの新しいマッピング規則

def main():
    app = webapp.WSGIApplication([
        ('/', MainPage),
        ('/addService', AddService),
        ('/getEntries', GetEntries),
        ('/api', AggroWebService),
        ('/getUserServices', GetUserServices),
        ], debug=True)
    util.run_wsgi_app(app)

if __name__ == '__main__':
    main()

この関数に対して行った変更は、1 つのエントリーをマッピングのリストに追加しただけです。/api は AggroWebService コントローラー・クラスにマッピングしています。このクラスを以下に記載します。


リスト 14. AggroWebService コントローラー・クラス

class AggroWebService(webapp.RequestHandler):
    def get(self):
        self.response.headers['content-type'] = 'text/xml'
        username = self.request.get('username')
        entries = aggrogator.Aggrogator.get_feed(username)
        str = u"""<?xml version="1.0" encoding="utf-8"?><entries>"""
        for entry in entries:
            str += entry.to_xml()
        str += "</entries>"
        self.response.out.write(str)

このサービスはまず、リクエスト・パラメーター username を取得するところから開始します。次に前にも使用した aggroGator ライブラリーを使用しますが、この場合には別のメソッド、get_feed を使って集約エントリーを取得します。このライブラリー関数のコードを以下に記載します。


リスト 15. aggroGator get_feed

class Aggrogator:
    @staticmethod
    def get_feed(username):
        services = Aggrogator.get_services(username)
        entries = []
        for svc_tuple in set((svc['service'], svc['username']) for svc in services):
            entries.extend(Cache.getEntries(*svc_tuple))
        entries.sort(key=operator.attrgetter('timestamp'), reverse=True)
        return entries
		

このライブラリー関数はリスト 10 に記載された get_services 関数を使用して集約サービスを取得し、続いてこれらのサービスを繰り返し処理します。このコードはセットを使用してサービスが固有であること (つまり、そのユーザーが購読している他 2 人のユーザーが、同じサービスを使用していないかどうか) を確認します。タプルを使用しなければならないのは、セットを使用したために不変オブジェクトしか使えなくなっているためです。最後に、すべてのエントリーをそれぞれのタイムスタンプに応じて降順にソートします (最新エントリーが先頭に記載されます)。

リスト 14 をもう一度見てください。ここではエントリーのリストを取得した後、単純なストリング連結を使って XML 文書を作成しています。それぞれの Entry インスタンスでは、to_xml() メソッドを使用します。これは新しいメソッドです (以下を参照)。


リスト 16. Entry クラス

class Entry:
    def __init__(self, service=None, username=None, title=None, 
link=None, content=None, timestamp=None):
        self.service = service
        self.username = username
        self.title = title
        self.link = link
        self.content = content
        self.timestamp = timestamp
    def to_dict(self):
        return self.__dict__
    def to_xml(self):
        str = """<entry>
                    <service>%s</service>
                    <username>%s</username>
                    <title>%s</title>
                    <link>%s</link>
                    <content><![CDATA[%s]]></content>
                    <timestamp>%s</timestamp>
                </entry>"""
        return str % (self.service, self.username, self.title, self.link, self.content, 
self.timestamp) 

上記からわかるように、to_xml() メソッドはストリング・テンプレートとストリング置換だけを使用して XML ノードを作成します。リスト 14 では、XML 文書をストリングとして作成した後、コンテンツ・タイプにレスポンス・ヘッダーを設定してから XML ストリングをリクエスターに送り返しています。必要な作業はこれだけです。この簡単な作業で、他のマッシュアップからも使用できる Web サービスを作成することができました。


まとめ

Google App Engine を話題にした連載「Google App Engine をベースに Eclipse を使用して作成するマッシュアップ」の第 3 回はこれで終了です。この記事では新たに購読を開始するための UI を追加しました。次に、購読を利用するように既存のアプリケーションを変更すると同時に、REST スタイルの Web サービスを作成して、aggroGator から他のマッシュアップを作成できるようにしました。ここから先はさまざまな拡張が可能です。例えば Entry クラスにコメントを追加したり、UI を追加してユーザーがエントリーにコメントを付けられるようにしたり、購読ビューとパーソナル・ビューを提供したりするのも一考です。さらにこの Web サービスを拡張して、ユーザーが直接フィードを追加できるようにすることもできます。このような拡張はすべて、Google App Engine と併せて Eclipse と PyDev などのツールを使用することによって簡単に実現することができます。



ダウンロード

内容ファイル名サイズダウンロード形式
Sample codeos-eclipse-mashup-google-pt3.zip238KBHTTP

ダウンロード形式について


参考文献

学ぶために

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

議論するために

  • Eclipse に関する質問を投じる最初の場所として、Eclipse Platform newsgroups があります (このリンクをクリックすると、デフォルト Usenet ニュース・リーダー・アプリケーションが起動され、eclipse.platform が開きます)。

  • Eclipse newsgroups には Eclipse を利用し、拡張することに関心を持つ人達のために、さまざまなリソースが用意されています。

  • developerWorks blogs から developerWorks コミュニティーに加わってください。

著者について

Galpin Michael photo

Michael Galpin は、1998年以来、プロとして Java ソフトウェアを開発してきています。彼は現在 eBay に勤務しています。彼は California Institute of Technology で数学の学位を取得しています。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


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=Open source
ArticleID=341580
ArticleTitle=Google App Engine をベースに Eclipse を使用して作成するマッシュアップ: 第 3 回 RESTful な Web サービスを使用する
publish-date=08192008
author1-email=mike.sr@gmail.com
author1-email-cc=