Modify entity data either transactionally or not (depending on the need)
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.
精彩评论