1
\$\begingroup\$

I'm learning to work in GAE. I'm so used to SQL, but transform my way of think the last 20 years to NoSQL is a little hard for me.

I have the next simple structure:

  • BOOKS than can have CHAPTERS
  • CHAPTERS that can have VOTES

In a traditional SQL I just make foreign keys from VOTES to CHAPTERS, and from it to BOOKS.

I do this for my models in Datastore:

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

    # Get all the Chapters for a book
    def getChapters(self):
        key = self.chapterMemkey()
        chapters = memcache.get(key)
        if chapters is None:
            logging.info('DB access for key %s.', key)
            chapters = Chapter.all().ancestor(self).order("number").fetch(100)
            if not memcache.set(key, chapters, 300):
                logging.error('Memcache set failed for Chapters.')
        else:
            logging.info('Memcache for key %s.', key)
        return chapters

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

    book = db.ReferenceProperty(Book,
                                required=True,
                                collection_name='chapters')

    # 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(db.Model):
    """ All votes that a book-chapter have """
    chapterNumber = db.IntegerProperty(required=True)
    option = db.IntegerProperty(default=1)
    value = db.IntegerProperty(default=1)

    book = db.ReferenceProperty(Book,
                                required=True,
                                collection_name='book_votes')
    chapter = db.ReferenceProperty(Chapter,
                                   required=True,
                                   collection_name='chapter_votes')

    # --------------------------
    # ClassMethods for the class
    # --------------------------

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

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

Well, my doubts are:

  1. Is this approach correct?
  2. Must I define in the Vote class a reference for a book and for a chapter, as it was a foreign keys (just like I think I've done)?
  3. Is well defined the way to retrieve the chapters from a book? I mean, in the Chapter class the function byBook uses a function from the Book class.
  4. In the Vote class, I also define the byBook function but, must I define another byChapter function with similar behaviour than the myBook function? I mean something like that:

    @classmethod
    def byChapter(cls, chapter, limit=100):
        vote = chapter.getVotes()
        return vote
    
  5. Are the parents well-defined for Votes (book) or must Chapter instead be a Book? The votes are for the chapters, but a chapter belongs to book.

  6. Which are the right ways to get the sum of all the votes for a specific chapter and for specific book?

\$\endgroup\$
2
\$\begingroup\$
  1. This depends on a few things (keep reading).

  2. Not necessarily. This is a question of optimization. You could fetch a vote's book by using Chapter as an intermediary (vote.getChapter().getBook()), but this would obviously require sequential trips to the database - one to get the vote, followed by one to get the chapter, and finally one to get the book. Do you plan on fetching a book for a vote frequently? If so, save some resources and stick with what you've got.

  3. From a dependency standpoint, this seems counterintuitive, but since a chapter cannot exist without a book (can it?), I would say this is fine and simply a matter of preference. This always seems awkward to me as well, so maybe someone else can provide more info.

  4. You can, again, this is a question of optimization and convenience. When you have questions like this, ask yourself, "will I really need this?". If the answer is no, or you're unsure, leave it out; you can always add it later if your needs change (the relations are setup correctly to do the joins, right?).

  5. See above.

  6. I would provide a Chapter.getVotesSum() and/or Book.getVotesSum(). These methods would include GQL queries to aggregate the appropriate rows from the votes table into a sum.

(Sorry if I'm missing something, I don't use GAE)

\$\endgroup\$
2
  • \$\begingroup\$ Thanks, @JohnSyrinek. I preffer optimization vs comfort, so in point 2 I will use then the optimized code.In point 3, right, a chapter has no sense without a book. Finally, in point 6, I was thinking in mantain a variable to accumulate every vote, to avoid the aggregate sum every time I need this data. This is more work for me, but is a better performance (and a lot less reading) for the DB. \$\endgroup\$
    – Eagle
    May 20 '13 at 14:46
  • \$\begingroup\$ Yep, I was just waiting for any other answer, but your is also good. Thank you. By the way, I change all my code to NDB today and the use of memcache for list querys. I'm learning a lot in the last days!! \$\endgroup\$
    – Eagle
    May 22 '13 at 19:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.