Моделирование Отношений В App Engine

Одна из проблем, с которой сталкиваются разработчики GAE, привыкшие работать с реляционными СУБД и ORM, — это ссылки и отношения в App Engine. В этом руководстве рассматриваются два вопроса: во-первых, что такое отношения в СУБД?; во-вторых, как их использовать в GAE?



Типы отношений

СУБД работают с несколькими типами отношений: «один-к-одному», «один-ко-многим» и «многие-ко-многим».

Несмотря на различия в терминологии, отношения работают по тем же принципам, что и ссылки.

Ссылка — это поле сущности, содержащее ключ другой сущности — например, если «питомец» относится к «владельцу», то это означает, что у сущности «питомец» есть поле, содержащее ключ сущности «владелец».

Все типы отношений можно представить в виде связей.

В своей простейшей форме связь «один ко многим» — это ссылка: у каждого питомца свой владелец, поэтому у владельца может быть несколько питомцев, ссылающихся на него.

При этом сам «хозяин» не меняется – отдельные «питомцы» полагаются на него, называя своим хозяином.

Отношения «один-к-одному» — это отношения «один-ко-многим» с дополнительным ограничением, согласно которому на «владельца» ссылается только одно «домашнее животное».

Это ограничение можно усилить, сохраняя перекрестные ссылки (поля, которые ссылаются друг на друга в каждой сущности).

Отношения «многие ко многим» немного сложнее.

Их можно реализовать несколькими способами, но все они сводятся к списку пар ссылок.

В качестве примера возьмем веб-страницу.

Каждая страница имеет множество входящих и исходящих ссылок.

Их можно представить в виде списка пар вида (from_url, to_url).

В реляционных СУБД такие совпадения хранятся в отдельных таблицах, которые объединяются в запросы для поиска связанных записей.

Теперь давайте посмотрим, как вышеуказанные типы отношений работают в App Engine. В общем, часто бывает полезно избавиться от терминологии «один-ко-многим» и т. д. и рассматривать сущности с объектно-ориентированной точки зрения.

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

Отношения в App Engine



Один ко многим

Этот тип отношений легко реализуется в любой системе.

Платформа App Engine гарантирует, что ключ «одной» стороны хранится в объекте «многих» сторон.

В Python для этого используется поле ReferenceProperty:

  
  
  
  
  
  
  
  
  
   

class Owner(db.Model): name = db.StringProperty() class Pet(db.Model): name = db.StringProperty() owner = db.ReferenceProperty(Owner)

Чтобы найти «владельца» «питомца», мы обращаемся к атрибуту pet.owner, и App Engine автоматически загружает объект, на который мы ссылаемся.

Чтобы найти всех «домашних животных», которые относятся к конкретному «владельцу», просто выполните следующий запрос:

pets = Pet.all().

filter('owner =', owner).

fetch(100)

Аналогичного результата можно добиться и проще: ReferenceProperty автоматически создает свойство в классе Owner для быстрого и легкого доступа к связанным данным, поэтому вы можете получить список «домашних животных» следующим образом:

pets = Owner.owner_set.fetch(100)

По умолчанию App Engine называет это свойство как имя поля + «_set», но вы можете задать свое собственное:

class Pet(db.Model): name = db.StringProperty() owner = db.ReferenceProperty(Owner, collection_name='pets') pets = owner.pets.fetch(100)

Другой способ смоделировать связь «один-ко-многим» — привязать сущность к родительскому элементу.

Когда сущность создается, ей можно назначить родителя.

В этом случае ключ родительской сущности становится частью дочернего ключа и не может быть изменен в будущем.

Вот как это выглядит в нашем примере:

class Owner(db.Model): name = db.StringProperty() class Pet(db.Model): name = db.StringProperty() bob = Owner(name='Bob') felix = Pet(name='Felix', parent=bob) owner_of_felix = felix.parent

Далее мы нигде явно не указываем связь между сущностями — она следует из указания родителя в момент создания.

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

набором сущностей с родителем из той же группы.

Если вы хотите, чтобы транзакция исключала связанные сущности, используйте поле ссылки.

Также помните, что у сущности может быть только один непосредственный родитель, и ее ключ нельзя изменить после создания.



Один к одному

Отношения «один-к-одному» являются частным случаем отношений «один-ко-многим».

Они осуществляются путем хранения на «одной» стороне поля, которое является ссылкой на другую сущность.



Многие ко многим

Схема «многие ко многим» наиболее сложна в реализации.

Существует несколько решений для их создания для App Engine. Наиболее очевидным подходом является таблица отношений, подобная реляционной базе данных, которая содержит пары ключей для обеих сторон отношений.

В нашем примере с «домашними животными/владельцами» это будет выглядеть так:

class Owner(db.Model): name = db.StringProperty() class Pet(db.Model): name = db.StringProperty() class PetOwner(db.Model): pet = db.ReferenceProperty(Pet, collection_name='owners') owner = db.ReferenceProperty(Owner, collection_name='pets')

Преимущество этого метода в том, что к связи можно добавить дополнительные свойства — например, при моделировании связей ссылок страниц в связь можно добавить поле с текстом ссылки.

Доступ к данным осуществляется поэтапно: находятся связанные пары, из которых затем извлекаются нужные сущности.

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

статья * :

petowners = felix.owners.fetch(100) prefetch_refprops(owners, 'owner') owners = [x.owner for x in petowners]

Получение сущностей в обратном направлении (от «хозяина» к «питомцам») осуществляется аналогично.

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

Это полезно, когда количество сохраняемых элементов ограничено (скажем, несколько сотен или меньше).

С таким списком удобно выполнять пакетные операции.

Например:

class Pet(db.Model): name = db.StringProperty() class Owner(db.Model): name = db.StringProperty() pets = db.ListProperty(db.Key)

От каждого «хозяина» можно извлечь список его «питомцев»:

pets = db.get(bob.pets)

А чтобы найти всех «владельцев» данного «питомца», выполните следующий запрос:

owners = Owner.all().

filter('pets =', felix).

fetch(100)

Наконец, гибридный подход может оказаться наиболее продуктивным и гибким.

Советую посмотреть замечательный репортаж Бретта Слаткинса на эту тему.

Разработка сложных масштабируемых приложений на App Engine .

* Имеется в виду разработанный автором статьи паттерн для извлечения сущностей из ссылок без выполнения лишних запросов к хранилищу.

Короче говоря, ссылочное поле не загружает объект сразу, и при доступе к ссылочному атрибуту или методу будет выдан запрос.

Чтобы минимизировать количество запросов, паттерн загружает сущности по ссылкам за раз (примечание переводчика).

Теги: #gae #ссылка #ссылки #отношения #Google App Engine

Вместе с данным постом часто просматривают: