お気に入り | 日本語 | ログイン

データストアの使用

スケーラブルな Web アプリケーションでデータを保存するときは注意が必要です。ユーザーはある時点で不特定多数の Web サーバーのいずれかと通信していたとしても、次のリクエストは、前のリクエストを処理したのとは別の Web サーバーに送られる可能性があります。すべての Web サーバーは、(おそらく世界中の地理的に異なる場所にある)多数のマシンに拡散したデータを送受信する必要があるのです。

Google App Engine の場合、そのようなことを気にする必要はありません。App Engine のインフラは、シンプルな API を使って、データの配布、複製、負荷分散を一手に引き受けます。ユーザーは強力なクエリ エンジンとトランザクションのメリットを享受できます。

データストアを使用したサンプルの全体像

次のサンプル コードは、データストアにグリーティング メッセージを格納する helloworld/helloworld.py の新バージョンです。以降では、このサンプルの変更された点について説明します。

import cgi

from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db

class Greeting(db.Model):
  author = db.UserProperty()
  content = db.StringProperty(multiline=True)
  date = db.DateTimeProperty(auto_now_add=True)

class MainPage(webapp.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>')

    greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")

    for greeting in greetings:
      if greeting.author:
        self.response.out.write('<b>%s</b> wrote:' % greeting.author.nickname())
      else:
        self.response.out.write('An anonymous person wrote:')
      self.response.out.write('<blockquote>%s</blockquote>' %
                              cgi.escape(greeting.content))

    # Write the submission form and the footer of the page
    self.response.out.write("""
          <form action="/sign" method="post">
            <div><textarea name="content" rows="3" cols="60"></textarea></div>
            <div><input type="submit" value="Sign Guestbook"></div>
          </form>
        </body>
      </html>""")

class Guestbook(webapp.RequestHandler):
  def post(self):
    greeting = Greeting()

    if users.get_current_user():
      greeting.author = users.get_current_user()

    greeting.content = self.request.get('content')
    greeting.put()
    self.redirect('/')

application = webapp.WSGIApplication(
                                     [('/', MainPage),
                                      ('/sign', Guestbook)],
                                     debug=True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()

helloworld/helloworld.py をこのコードで置き換え、ブラウザで http://localhost:8080/ をリロードします。メッセージをいくつか投稿し、正しく格納および表示されることを確認します。

送信されたメッセージの格納

App Engine は、Python 用のデータ モデリング API を備えています。これは Django のデータ モデリング API と似ていますが、内部では App Engine のスケーラブルなデータストアを使用しています。

ゲストブック アプリケーションに必要なのは、ユーザーが投稿したグリーティング メッセージを保存できることです。各グリーティング メッセージには、投稿者の名前、メッセージの内容の他に、メッセージが投稿された日付と時刻が含まれているため、メッセージを投稿順に表示することができます。

データ モデリング API を使用するには、google.appengine.ext.db モジュールをインポートします。

from google.appengine.ext import db

グリーティング メッセージのデータ モデルを定義するには次のようにします:

class Greeting(db.Model):
  author = db.UserProperty()
  content = db.StringProperty(multiline=True)
  date = db.DateTimeProperty(auto_now_add=True)

これは、次の 3 つのプロパティを持つ Greeting モデルを定義します。ここで、author の値は User オブジェクト、content の値は文字列、date の値は datetime.datetime です。

一部のプロパティ コンストラクタでは、詳細な動作を設定するためにパラメータを取ります。db.StringProperty コンストラクタに multiline=True パラメータを与えることで、このプロパティの該当の値に改行文字を含めることができます。db.DateTimeProperty コンストラクタに auto_now_add=True パラメータを与えると、アプリケーションによって値が提供されない限り、オブジェクトが作成された時刻の新しいオブジェクト date を自動的に付与するようモデルを設定します。プロパティの種別とオプションの完全リストは、データストア リファレンスをご覧ください。

グリーティング メッセージ用のデータ モデルはこれで完了です。アプリケーションはモデルを使用して新しい Greeting オブジェクトを作成し、データストアに配置できるようになりました。次に示す変更後の Guestbook ハンドラでは、新しいグリーティング メッセージが作成されてデータストアに保存されます:

class Guestbook(webapp.RequestHandler):
  def post(self):
    greeting = Greeting()

    if users.get_current_user():
      greeting.author = users.get_current_user()

    greeting.content = self.request.get('content')
    greeting.put()
    self.redirect('/')

この新しい Guestbook ハンドラは新しい Greeting オブジェクトを作成します。また、ユーザーが投稿したデータを使って author プロパティと content プロパティを設定します。ここでは、date プロパティを設定していません。このため、モデルに指定したとおり、date は自動的に「now」に設定されます。

最後に、greeting.put() が新しいオブジェクトをデータストアに保存します。 このオブジェクトがクエリによって取得したものだった場合、put() は既存のオブジェクトを更新します。このオブジェクトはモデル コンストラクタを使って作成されたものであるため、put() は新しいオブジェクトをデータストアに追加します。

保存されたメッセージを GQL で取り出す

App Engine のデータストアは、データ モデル用の洗練されたクエリ Engine を備えています。App Engine のデータストアは、従来のリレーショナル データベースではないため、SQL によるクエリの指定を行いません。代わりに、GQL と呼ばれる SQL に似たクエリ言語を使用します。GQL では、慣れ親しんだ構文を使用して、App Engine データストアのクエリ Engine 機能にアクセスできます。

次に示す変更後の MainPage ハンドラでは、グリーティング メッセージのクエリが実行されます:

class MainPage(webapp.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>')

    greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")

    for greeting in greetings:
      if greeting.author:
        self.response.out.write('<b>%s</b> wrote:' % greeting.author.nickname())
      else:
        self.response.out.write('An anonymous person wrote:')
      self.response.out.write('<blockquote>%s</blockquote>' %
                              cgi.escape(greeting.content))

    # Write the submission form and the footer of the page
    self.response.out.write("""
          <form action="/sign" method="post">
            <div><textarea name="content" rows="3" cols="60"></textarea></div>
            <div><input type="submit" value="Sign Guestbook"></div>
          </form>
        </body>
      </html>""")

クエリはこの行で発生しています。

    greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")

または、Greeting クラスで gql(...) メソッドを呼び出し、クエリの SELECT * FROM Greeting を省略することもできます。

    greetings = Greeting.gql("ORDER BY date DESC LIMIT 10")

SQL の場合と同様、キーワード(SELECT など)は大文字小文字を区別しません。ただし、名前については大文字小文字を区別します。

クエリはデータ オブジェクト全体を返すため、モデルの特定のプロパティを選択しても意味がありません。すべての GQL クエリは、等価の SQL と共通化するため SELECT * FROM model で始まります(モデルの gql(...) メソッドは見てわかるとおり例外です)。

GQL クエリに WHERE クローズを含め、プロパティ値に基づく 1 つまたは複数の条件で設定した結果をフィルタリングすることもできます。SQL と異なり、GQL クエリは定数を指定することができません。代わりに、GQL では、すべての値にパラメータ バインディングを使用します。たとえば、現在のユーザーが投稿したグリーティング メッセージのみを取得するには、次のようにします。

    if users.get_current_user():
      greetings = Greeting.gql("WHERE author = :1 ORDER BY date DESC",
                               users.get_current_user())

位置付きパラメータの代わりに名前付きパラメータを使用することもできます。

      greetings = Greeting.gql("WHERE author = :author ORDER BY date DESC",
                               author=users.get_current_user())

Datastore API では、GQL の他に、メソッドを使用してクエリ オブジェクトを作成する機構がもう 1 つ用意されています。上のクエリは、次のように構成することもできます。

      greetings = Greeting.all()
      greetings.filter("author =", users.get_current_user())
      greetings.order("-date")

GQL とクエリ API に関する詳しい情報は、データストア リファレンスをご覧ください。

開発サーバーのデータストアをクリアする

開発用 Web サーバーは、アプリケーションをテストするため、一時ファイルを使用したローカル バージョンのデータストアを備えています。この一時ファイルが存在する間は、データは永続的に保存されます。ユーザーが指定しない限り、Web サーバーによってファイルがリセットされることはありません。

ウェブ サーバーを開始する前にこのデータストアを消去したい場合は、サーバーを起動するときに --clear_datastore オプションを使用します。

dev_appserver.py --clear_datastore helloworld/

次のステップ

これで、Google アカウントを使用してユーザーを認証し、メッセージを送信し、他のユーザーが残したメッセージを表示できるゲストブック アプリケーションの完成です。App Engine は自動的にスケーリングを行うため、アプリケーションが人気になっても、このコードを変更する必要はありません。

最新のバージョンでは、HTML コンテンツが MainPage ハンドラのコードと混在しています。これにより、特にアプリケーションを大きくしたり複雑にしたりするとき、アプリケーションの外観を変えるのが難しくなります。テンプレートを使用して外観を管理し、CSS スタイルシート用に静的ファイルを導入しましょう。

テンプレートの使用に進みます。