Часто у присматривающихся к платформе Google App Engine разработчиков возникает резонный вопрос: как я могу забрать все свои данные? К примеру, это может понадобиться в таких распространенных случаях:
- Имеется Интернет-магазин, который принимает онлайн-заказы и помещает их в хранилище. Чтобы в дальнейшем выставить счета, отследить оплату и произвести отгрузку, необходимо эти данные передать в учетную систему (разновидность какой-нибудь ERP).
- Вы содержите блог. В один прекрасный день захотите составить список всех своих новых статей и разослать подписчикам уведомления о новостях.
- Мне нравится сама концепция Cloud Computing, но я не доверяю ей. Я хочу всегда держать под рукой копию всех данных сайта и в любой момент быть готовым мигрировать в собственный дата-центр.
Ниже описан код, который может помочь в достижении этих целей. Я прошу заранее прощения, что он не претендует на оригинальность, и уже был где-то описан. Чтобы не изобрести колесо, мы будем использовать модель SSN из статей «keeping private data private» и for ways to insult a nose. Мы создадим еще одну модель, которая будет вести учет всех производимых изменений данных:
class DataLog(db.Model):
"""Модель описывает выполненные в хранилище изменения"""
action = db.StringProperty(required=True)
timestamp = db.DateTimeProperty(auto_now_add=True)
data = db.TextProperty()
Модель содержит три свойства: поле action, которое описывает тип производимого действия (put для сохранения данных, delete для удаления), timestamp – описывающее когда были сделаны изменения (внимание: точность этого поля далека от совершенства) и текстовое представление изменений. Последнее легко получается с использованием метода to_xml базового класса Model. Мы также создаем метод patch_for_logging, который принимает в качестве своих аргументов класс модели и заменяет методы put and delete своими собственными:
def patch_for_logging(modelclass):
"""Патч для класса модели, который ведет лог изменений"""
old_put = modelclass.put
old_delete = modelclass.delete
def new_put(self):
result = old_put(self)
DataLog(
action='put',
parent=self.parent(),
data=self.to_xml()).put()
return result
def new_delete(self):
result = old_delete(self)
DataLog(
action='delete',
parent=self.parent(),
data=self.to_xml()).put()
return result
if not getattr(modelclass, 'old_put', None):
setattr(modelclass, 'old_put', old_put)
setattr(modelclass, 'put', new_put)
if not getattr(modelclass, 'old_delete', None):
setattr(modelclass, 'old_delete', old_delete)
setattr(modelclass, 'delete', new_delete)
Следует упомянуть, что мы устанавливаем родительский объект каждой записи об изменении данных в self.parent(). Это необходимо чтобы предотвратить нарушение работоспособности при выполнении программой транзакций. Предположим, что мы помещаем этот код в файл datalog.py, после чего в основном приложении достаточно добавить строчки:
class SSN(db.Model):
user = db.UserProperty()
ssn = db.StringProperty(required=True)
import datalog
datalog.patch_for_logging(SSN)
Теперь мы уверены, что все изменения будут учтены. Осталось лишь определить способ извлечения этих данных. Для этого нам потребуется следующий обработчик:
# Очень простой обработчик запроса, который выдает список изменений в данных хранилища
class MainPage(webapp.RequestHandler):
def get(self):
self.response.out.write('<changes>\n')
for change in DataLog.all():
self.response.out.write(
'<change action="%s" timestamp="%s">\n' % (
change.action, change.timestamp))
self.response.out.write(change.data)
self.response.out.write('</change>\n')
self.response.out.write('</changes>\n')
Следующий пример данных в формате XML показывает, как будет выглядеть вывод этого лога изменений:
<changes>
<change action="put" timestamp="2008-08-10 22:03:09.503340">
<entity kind="SSN" key="agNhZWZyCQsSA1NTThgBDA">
<key>tag:aef.gmail.com,2008-08-10:SSN[agNhZWZyCQsSA1NTThgBDA]</key>
<property name="ssn" type="string">12345679</property>
<property name="user" type="user">test@example.com</property>
</entity>
</change>
<change action="put" timestamp="2008-08-10 22:03:34.460330">
<entity kind="SSN" key="agNhZWZyCQsSA1NTThgBDA">
<key>tag:aef.gmail.com,2008-08-10:SSN[agNhZWZyCQsSA1NTThgBDA]</key>
<property name="ssn" type="string">098765432</property>
<property name="user" type="user">test@example.com</property>
</entity>
</change>
</changes>
Не правда ли, все просто?
Несколько замечаний:
Очевидно, что описанный выше код будет работать только в том случае, если к определению каждой модели будет добавлен этот декоратор. Он также требует, чтобы приложение оперировало данными только через использование моделей, так как работа с низкоуровневыми функциями Datastore API приведет к невозможности учета изменений.
Код обработчика, который я использовал в примере выше не должен быть использован в таком виде в реальном приложении. Существуют следующие серьезные проблемы:
- он небезопасен – любой человек может получить все данные приложения, угадав его URL. Необходимо использовать специальную схему, подобную той, что описана в примере «Вопрос доверия».
- он не масштабируем, так как всегда производит извлечение абсолютно всех данных из хранилища. В реальном приложении необходимо продумать, как лучше обеспечить последовательную загрузку данных порциями и их удаление из хранилища (это уже выходит за рамки этой статьи).
В дополнении вы можете рассмотреть возможность расширения этой функциональности, созданием идентификаторов на манер глобальных счетчиков. В этом случае появится возможность использовать выражение «ORDER BY» в GQL-запросе, который будет проводить сортировку объектов и выдавать только наиболее ранние или поздние записи. Как было описано выше, тип timestamp не гарантирует того, что элементы будут расположены в том же самом порядке, в каком производились действия с объектами – не полагайтесь на него в этом.
Если все это кажется вам слишком сложным, то существует альтернатива в реализации собственного хранилища на подобии CouchDB, к которому приложения будут обращаться через urlfetch. Однако в этом случае существуют свои подводные камни, такие как производительность такого решения, негативно сказывающиеся на работу приложения задержки и низкая надежность, равная надежности самого слабого компонента.