开发者

Modify entity data either transactionally or not (depending on the need)

开发者 https://www.devze.com 2022-12-19 21:52 出处:网络
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

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)
开发者_JAVA技巧    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?


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号