GAE: от СУБД к проблемам NDB

Учусь работать в GAE. Я прочитал много статей, все документы NDB от Google и некоторые вопросы здесь. Я так привык к SQL, но преобразовать мое мышление за последние 20 лет в NoSQL было немного сложно для меня, и все эти различные решения, приведенные здесь, сводят меня с ума.

У меня следующая простая структура: КНИГИ, в которых могут быть ГЛАВЫ, ГЛАВЫ, которые могут иметь ГОЛОСА. Например, в книге «Страж» может быть 3 главы, и каждая глава будет иметь 0, 8 и 12 голосов.

В традиционном SQL я просто делаю внешние ключи от ГОЛОСОВ к ГЛАВАМ и КНИГАМ и от ГЛАВ к КНИГАМ.

Я делаю это для своих моделей:

class Book(ndb.Model):
    title = ndb.StringProperty(required=True)
    author = ndb.StringProperty(required=True)
    created = ndb.DateTimeProperty(auto_now_add=True)

    # Define a default ancestor for all the books
    @staticmethod
    def bookKey(group='books'):
        return ndb.Key(Book, group)

    # Search all
    @classmethod
    def getAll(cls):
        q = Book.query(ancestor=cls.bookKey())
        q = q.order(Book.title)
        books = q.fetch(100)
        return books

    @classmethod
    def byId(cls, id):
        book = Book.get_by_id(long(id), cls.bookKey())

    # Get all the Chapters for a book
    def getChapters(self):
        chapters = Chapter.query(ancestor=self).order(Chapter.number).fetch(100)
        return chapters

class Chapter(ndb.Model):
    """ All chapters that a book have """
    title = ndb.StringProperty(required=True)
    number = ndb.IntegerProperty(default=1)
    created = ndb.DateTimeProperty(auto_now_add=True)

    book = ndb.KeyProperty(kind=Book)

    # Search by Book (parent)
    @classmethod
    def byBook(cls, book, limit=100):
        chapter = book.getChapters()
        return chapter

    # Search by id
    @classmethod
    def byId(cls, id, book):
        return Chapter.get_by_id(long(id), parent=book)

class Vote(ndb.Model):
    """ All votes that a book-chapter have """
    value = ndb.IntegerProperty(default=1)

    book = ndb.KeyProperty(kind=Book)
    chapter = ndb.KeyProperty(kind=Chapter)

Что ж, мои сомнения:

  1. Это правильный подход?
  2. Созданная мною функция bookKey () хороша для наличия «фиктивного предка», чтобы гарантировать, что все сущности используют предков?
  3. Должен ли я определить в классе Vote ссылку на книгу и на главу, поскольку это были внешние ключи (как я думаю, я сделал)?
  4. Хорошо ли определен способ извлечения глав из книги? Я имею в виду, что в классе Chapter функция byBook использует функцию из класса Book. Или я должен избегать использования функций из другой сущности, чтобы иметь более чистый код?
  5. как я могу получить все голоса за главу?
  6. Каковы верные способы получить сумму всех голосов за конкретную главу и за конкретную книгу?

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

Имя | Голосует Sentinel | 30 голосов Ведьма | 4 голоса

Как я могу получить эту информацию, особенно подсчитанные голоса.

Затем, щелкнув название книги, я хочу показать все его главы (я полагаю, именно тогда я должен использовать функцию byBook в модели Chapter, верно?).

Какой GQL мне нужен для получения таких данных?

Заранее спасибо.


person Eagle    schedule 21.05.2013    source источник


Ответы (1)


Хорошее начало. Хранилище данных GAE сбивает с толку. Поскольку он не имеет схемы, я обнаружил, что работа с объектами больше похожа на работу с объектами / структурами данных в памяти, чем с таблицами базы данных.

Вот несколько вещей, которые я бы сделал иначе:

  • Похоже, вы создаете все свои книги под одним предком. Ужасная идея. Замечательно с точки зрения производительности. Это неправильно, если вам не нужно выполнить какую-либо транзакционную операцию с группой книг, которой нет в вашем текущем коде.

  • Из функции Book.getChapters () видно, что вы хотите сделать книгу предком нескольких глав. Вероятно, это хорошее применение предку. Я не вижу кода, в котором вы создаете главы, но убедитесь, что соответствующая книга указана в качестве предка.

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

  • Если количество глав в книге будет ограничено, я бы подумал об использовании StructuredProperty для глав. StructuredProperties - это, по сути, структурированные данные в родительской сущности (книге). Вы будете ограничены максимальным размером объекта Book (1 МБ), но если он уместится, это сэкономит вам затраты на выполнение дополнительных запросов, поскольку вы в любом случае не будете запрашивать главы без соответствующей книги.

person dragonx    schedule 21.05.2013
comment
сказал большую часть того, что я мог бы сказать, но я могу добавить, что если вы хотите получить итоги и промежуточные итоги, нет опции группировки по sql, поэтому вам нужно пройти через цикл и добавить итоги самостоятельно (я думаю, у вас может быть вычисляемое поле ) - person ; 21.05.2013
comment
Спасибо, @dragonx. Что же мне делать для книжного предка? Когда я выполняю getAll () для книг, мне нужно указать предка. Если у каждой книги разные предки, будет выбрана только одна книга, не так ли? Когда я создаю главу, я передаю книгу как предку. Очевидно, что в SQL голосование должно быть независимым, а в NoSQL - нет. Один пользователь будет голосовать, что влияет на главу и, по наследству, на Книгу. Если я включу его в обе сущности, мне придется управлять двойной работой для каждого голоса, но это возможно. Я протестирую вашу последнюю рекомендацию о StructuredProperties. Трудный путь это NoSQL. ;) - person Eagle; 21.05.2013
comment
Предки необходимы только в том случае, если вам нужен строго согласованный запрос. Вы можете получить все книги, если не укажете предка. Обратной стороной является то, что ваш запрос в конечном итоге согласован. Вам нужно будет решить, приемлемо ли в конечном итоге последовательное поведение. Если это только для просмотра, то, вероятно, так и есть. - person dragonx; 21.05.2013
comment
С точки зрения голосов, это не совсем SQL против NoSQL. Вам может понадобиться таблица голосования, если у вас есть дополнительные данные для голосов, например, кто проголосовал. Но в приведенном вами примере просто неэффективно суммировать все голоса каждый раз, когда вы просматриваете книгу или главу. Менее сложно обновлять его при голосовании, тогда, когда кто-то просматривает вашу страницу, вам не нужно снова пересчитывать голос. Немного сложнее сложить все голоса в главе книги, но на самом деле это простой цикл. - person dragonx; 21.05.2013
comment
Таким образом, использование уникального предка для корневого объекта (в данном случае книги) - плохая идея для проблем с производительностью, и лучше иметь в конечном итоге согласованность (что, я полагаю, означает, что можно не читать данные, которые все еще сохраняются, что происходит, когда я сохраняю книгу без предка). Что касается голосов, я кратко изложил проблему, чтобы легко понять, но на самом деле у меня будет 4 вида голосов (есть свойство, которое я не включил), поэтому я полагаю, что это изменит правила и будет необходимо иметь его в своей собственной сущности. Но это правда, если в одиночестве больше смысла быть в книге - person Eagle; 21.05.2013