minou
minou

Reputation: 16563

Generic method for updating entity with transactions

I have a GAE model with several methods that update entities with transactions. Something like this:

class MyModel(ndb.Model):

    @staticmethod
    @ndb.transactional
    def update_foo(ekey):
        entity = ekey.get()
        entity.foo = "x"
        entity.put()

    @staticmethod
    @ndb.transactional
    def update_bar(ekey):
        entity = ekey.get()
        entity.bar = "y"
        entity.put()

To clean up my code, I was thinking, I could centralize the code that does updates with transactions. Something like this:

class MyModel(ndb.Model):

    @staticmethod
    @ndb.transactional
    def update_tx(ekey, **kwargs):
        entity = ekey.get()
        for prop, value in kwargs.iteritems():
            setattr(entity, prop, value)
        enitty.put()

    def update_foo(self):
        self.update_tx(self.key, foo="x")

    def update_bar(self):
        self.update_tx(self.key, bar="y")

Is this a reasonable idea, or are there dangers in this approach that I haven't considered?

Upvotes: 0

Views: 54

Answers (1)

Alex
Alex

Reputation: 5276

It's reasonable. It really depends on your use case.

I actually had something similar in place, but more and more I have to special case my update_tx() for each entity type to the point that only 1 or 2 of my ~10 models still use it, because there is almost always other logic that needs to be executed.

  • If I update a user's account state from 'deactivated' to 'activated' I need to perform a few queries to see if they completed all the mandatory onboarding steps.
  • If a user updates their email I have to send a new verification link to that new email before it can change
  • Certain fields are only editable if the entity is in a certain state
  • If a user submit's a new start date and end date, I need to save the old start date and end date in new object for historical purposes
  • etc

That logic could happen outside of this function, but I often want that to happen in the same transaction.

That aside, one thing it doesn't seem to handle is needing to update a batch of entities in a single transaction, so your pseudo code would need to look more like this if that's important to you:

class MyModel(ndb.Model):

    def update_properties(self, **kwargs):
        for prop, value in kwargs.iteritems():
            setattr(self, prop, value)

    @staticmethod
    @ndb.transactional(xg=True)
    def update_txs(keys, updates):
        # keys is an array of ndb.Key()s, 
        # updates is an array of the same size, containing **kwargs for each key
        entities = ndb.get_multi(keys)
        for index, entity in enumerate(entities):
            entity.update_properties(**updates[index])
        ndb.put_multi(entities)

    @classmethod
    def update_tx(cls, ekey, **kwargs):
        cls.update_txs([ekey], [kwargs])

    def update_foo(self):
        self.update_tx(self.key, foo="x")

    def update_bar(self):
        self.update_tx(self.key, bar="y")

Upvotes: 1

Related Questions