Избранное | Русский | Войти

Использование хранилища данных

Хранение данных в масштабируемом веб-приложении может быть непростой задачей. В определенный момент времени пользователь может взаимодействовать с любым из десятков веб-серверов, а его следующий запрос может отправиться уже не на тот веб-сервер, который обрабатывал предыдущий. Все веб-серверы должны взаимодействовать с данными, которые также распределены по десяткам компьютеров, возможно расположенным в разных точках планеты.

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 включает API моделирования данных для Python. Он похож на API моделирования данных Django, но использует масштабируемое хранилище данных 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)

Это определяет модель 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 автоматически устанавливается на "сейчас" согласно конфигурации нашей модели.

Наконец, метод greeting.put() сохраняет наш новый объект в хранилище данных. Если этот объект был получен через запрос, метод put() обновит существующий объект. Так как мы создали этот объект с помощью конструктора модели, метод put() добавит новый объект в хранилище данных.

Получение сохраненных приветствий с помощью GQL

Хранилище данных App Engine имеет сложный механизм запросов для моделей данных. Поскольку хранилище данных App Engine не является обычной реляционной базой данных, запросы определяются не с помощью SQL. Вместо этого запросы можно писать на языке, подобном SQL, который мы называем GQL. GQL дает доступ к функциям механизма запросов хранилища данных App 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")

Можно также вызвать метод gql(...) для класса Greeting и убрать из запроса текст SELECT * FROM Greeting:

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

Как и в SQL, ключевые слова (например, SELECT) не зависят от регистра. Однако названия от регистра зависят.

Запрос возвращает полные объекты данных, поэтому нет смысла выбирать из модели определенные свойства. Все запросы GQL начинаются с текста SELECT * FROM model (или это подразумевается методом gql(...) модели) ради сходства со своими SQL-эквивалентами.

Запрос GQL может иметь условие WHERE, фильтрующее набор результатов в зависимости от значений одного или нескольких свойств. В отличие от 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())

Кроме GQL, API хранилища данных предоставляет еще один механизм построения объектов запросов, использующий методы. Приведенный выше запрос также можно создать следующим образом:

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

Полное описание GQL и API запросов приведено в справочном руководстве хранилища данных.

Очистка хранилища данных сервера разработки

Веб-сервер разработки использует локальную версию хранилища данных для тестирования приложения, использующую временные файлы. Данные хранятся, пока существуют временные файлы, а веб-сервер не удаляет их, пока вы не укажете ему сделать это.

Если вам нужно, чтобы сервер разработки очистил хранилище перед началом работы, используйте при запуске сервера параметр --clear_datastore:

dev_appserver.py --clear_datastore helloworld/

Далее...

Теперь у нас есть работающее приложение гостевой книги, которое аутентифицирует пользователей с помощью аккаунтов Google, позволяет им отправлять сообщения и отображает сообщения, оставленные другими. Поскольку App Engine автоматически выполняет масштабирование, менять код по мере роста популярности приложения не нужно.

В этой последней версии HTML-содержание смешано с кодом обработчика MainPage. Это затруднит изменение внешнего вида приложения, особенно когда оно станет больше и сложнее. Для управления внешним видом можно воспользоваться шаблонами, а также ввести статические файлы для таблиц CSS-стилей.

Переходите к разделу Использование шаблонов.