この連載では、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/ にアクセスしてください。テストが面白いものになるように、複数のアカウントを作成することをお忘れなく。
ソーシャル 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 code | os-eclipse-mashup-google-pt3.zip | 238KB | HTTP |
学ぶために
- この連載はまず、「Google App Engine をベースに Eclipse を使用して作成するマッシュアップ: 第 1 回 アプリケーションを作成する」から読んでください。
- 「Google App Engine をベースに Eclipse を使用して作成するマッシュアップ: 第 2 回 Ajax マッシュアップを構築する」も必ず読んでください。
- 「Charming Python: Python elegance and warts, Part 1」を読んで、Python の優れた新機能を学んでください。
- developerWorks の連載「魅力的な Python」のその他の記事も読んでください。
- SDK は Django に非常によく似た Web アプリケーション・フレームワークを使用します。実際、Django を使用することもできるので、その場合には「Python Web フレームワーク、第 1 回: Django と Python を使って Web 開発を行う」を読んで Django について学んでください。
- 「Get started with open source CMS, Part 6: Build a Python WebDAV client for Jakarta Slide」を呼んで、PyDev の実際の動作を確認してください。
- 「Bigtable: A Distributed Storage System for Structured Data」を読んでください。この研究報告では、Google の Bigtable について詳細に説明しています。
- Python のような動的言語を使用するときには、公式 Python マニュアルをすぐに参照できるようにしておくと必ず役立ちます。
- 「プロのように Ajax アプリケーションを開発する: 第 1 回」と「第 2 回」を読んで、Prototype と script.aculo.us について学んでください。
- App Engine の Memcache API は memcached から発想を得ています。「Make PHP apps fast, faster, and fastest, Part 3」を読んで、この API が PHP でのパフォーマンスを改善するために一般にどのように使用されているのかを理解してください。
- Eclipse を使用して Web 開発を行う場合は、「Eclipse のための Ajax Toolkit Framework を知る」を読むと役立ちます。
- Eclipse コミュニティーの最新情報を探すには、PlanetEclipse にアクセスしてください。
- Eclipse plugin central で入手可能な Eclipse プラグインを調べてください。
- script.aculo.us にアクセスして、この JavaScript ライブラリーについての情報を入手してください。
- Prototype Framework について学ぶには、PrototypeJS.org にアクセスしてください。
- EclipseLive には、さまざまな Eclipse 技術に焦点を当てた Web セミナーが用意されています。
- 「Eclipse の推奨読み物リスト」を読んでください。
- developerWorks ですべての Eclipse 関連記事を調べてください。
- Eclipse の初心者は、developerWorks の記事「Eclipse Platform 入門」を読んでください。Eclipse の起源とアーキテクチャー、そしてプラグインで Eclipse を拡張する方法を学べます。
- IBM developerWorks の Eclipse project resources を調べて、Eclipse のスキルを磨いてください。
- ソフトウェア開発者を対象とした興味深いインタービューや討論については、developerWorks ポッドキャストをチェックしてください。
- developerWorks の Technical events and webcasts で最新情報を入手してください。
- 無料の developerWorks On demand demos で、IBM およびオープンン・ソースの技術と製品機能を調べて試してみてください。
- 世界中で近日中に予定されている IBM オープンソース開発者を対象とした会議、見本市、ウェブ放送やその他のイベントをチェックしてください。
- オープンソース技術を使用して開発し、IBM の製品と併用するときに役立つ広範囲のハウツー情報、ツール、およびプロジェクト・アップデートについては、developerWorks Open source ゾーンを参照してください。
製品や技術を入手するために
- App Engine SDK をダウンロードしてください。Google App Engine サイトには公式マニュアルも用意されています。
- この記事で作成したアプリケーションでは、Mark Pilgrim の Universal Feed Parser を使用しました。この優れたライブラリーでは、RSS、Atom など、あらゆるフィードを構文解析できます。
- DjangoProject.com は、Django フレームワークのホーム・ページです。
- この記事では Eclipse Classic V3.3.2 を使用しています。
- PyDev プラグインは、http://pydev.sourceforge.net/updates/ から入手できます。このプラグインは Eclipse のこの更新サイトからインストールできます。
- IBM alphaWorks で Eclipse 技術の最新ダウンロードを調べてください。
- Eclipse Foundation から Eclipse Platform およびその他のプロジェクトをダウンロードしてください。
- IBM 製品の評価版をダウンロードして、DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® のアプリケーション開発ツールとミドルウェア製品を使ってみてください。
- IBM ソフトウェアの試用版を使用して、次のオープンソース開発プロジェクトを革新してください。ダウンロード、あるいは DVD で入手できます。
議論するために
- Eclipse に関する質問を投じる最初の場所として、Eclipse Platform newsgroups があります (このリンクをクリックすると、デフォルト Usenet ニュース・リーダー・アプリケーションが起動され、eclipse.platform が開きます)。
- Eclipse newsgroups には Eclipse を利用し、拡張することに関心を持つ人達のために、さまざまなリソースが用意されています。
- developerWorks blogs から developerWorks コミュニティーに加わってください。
