views:

29

answers:

2

What is the best way to keep code modular and decoupled but avoid entering a transaction twice?

Entities often have class methods to load, modify, and store data. Often, this must be transactional to be consistent with child/sibling/cousin entities. Here is the pattern:

class MyEntity(db.Model):
  # ... some properties

  @classmethod
  def update_count(cls, my_key):
    def txn():
      me = db.get(my_key)
      me.count += 23
      me.put()
      OtherEntity.update_descendants(ancestor=me)
    return db.run_in_transaction(txn)

Usually, you should fetch entities, modify them, and store them in once block. That technique is more performant; but sometimes performance is less important than modularity and maintainability. The two updates should be decoupled. (Perhaps update_descendants is called often in isolation, and it's responsible for storing the data.)

But, the following code is a bug:

class OtherEntity(db.Model):
  # ... some properties

  @classmethod
  def update_descendants(cls, ancestor):
    def txn(): # XXX Bug!
      descendants = cls.all().ancestor(ancestor).fetch(10)
      for descendant in descendants:
        descendant.update_yourself(ancestor.count)
      db.put(descendants)
    return db.run_in_transaction(txn)

That raises an exception:

>>> MyEntity.update_count(my_key=some_key_i_have)
Traceback (most recent call last):
  ...
BadRequestError: Nested transactions are not supported.

So how can I get the best of both worlds: modularity, and correctness?

+1  A: 

The pattern I use is to have a parameter indicating whether transactional behavior is required.

class OtherEntity(db.Model):
# ... some properties

@classmethod
def update_descendants(cls, ancestor, with_transaction=True):
  if with_transaction:
    return db.run_in_transaction(cls.update_descendants, ancestor,
                                 with_transaction=False)

  # Now I can assume I am in a transaction one way or another...
  descendants = cls.all().ancestor(ancestor).fetch(10)
  for descendant in descendants:
    descendant.update_yourself(ancestor.count)
  return db.put(descendants)

The same principle could be expanded to indicate whether to take responsibility for the put, or to leave it to the caller.

jhs
For the record, I started doing this in my own work after using a bit too much Erlang ("country") and a bit too much Rails ("rock and roll").
jhs
+1  A: 

I would suggest making the transaction functions top-level class methods. Then, you can call them directly or with db.run_in_transaction, as appropriate.

Nick Johnson
Thanks, Nick. If I reversed the logic of the answer I gave (i.e. transaction disabled by default) it would basically be your suggestion.
jhs
I have to say, I personally don't use the technique you describe. I prefer to assign or compute whether transactions are needed and pass that information down the call chain in a more functional style. I think it's a matter of personal preference.
jhs