Search code examples
pythongoogle-app-enginegoogle-cloud-datastoreapp-engine-ndbdenormalization

How to keep your code clean with denormalized data?


I'm going to ask this question in two parts: first the general question, and than the question for my specific use case.

The general question:

I'm building a podcast app, where, hopefully, we'll have users. Users have subscripitons, settings, ... , which I'd like to store on the User object, but subscriptions and settings don't belong in the same module in my code.

How do you structure your code so that all the relevant data about a user is stored together, but the code that defines and deals with specific properties can be separated?

My specific use case

I'm building the back end on Google App Engine. My user class looks something like this:

class User(ndb.Model):
    username = ndb.StringProperty(required=True)
    email = ndb.StringProperty(required=True)
    ...

Now I could just add another property for subscriptions, settings etc, but these definitions don't really belong in the users module. I've tried defining a SubscriptionsHolder and SettingsHolder class using ndb.PolyModel, but with multiple inheritance, only queries on the last superclass in the User definition supports querying.

I could just make the settings and other module query the User model directly, but this results in a circular dependency, where the User the users module depends on settings for subclassing, and settings depends on users for querying. I know I can resolve the circular dependency by moving the import statement around, but that just seems like a hack to me.


Solution

  • My approach was to treat User and Settings data as separate but related collections. Instead of subclassing or using PolyModel I simply introduced a way to imply a 1:1 relation between those data sets.

    One way is to add a KeyProperty to Settings that links back to User. Another way is to create each Settings entity with the same id/name that is used by the related User entity. This second way allows a direct Settings.get_by_id() call once you have the User key.