Неожиданное поведение вокруг вложенных транзакций в облачном хранилище данных Google - PullRequest
0 голосов
/ 28 сентября 2019

У меня есть бизнес-проблема: у родительского объекта есть потомок дочернего объекта.Дочерняя сущность имеет значение, которое должно быть уникальным, поэтому сущность ChildLookup существует для обеспечения этой уникальности.Чтобы абстрагировать некоторые вещи, сущности помещают / удаляют, были помещены в свои собственные методы, и оба имеют операторы пакетной обработки / транзакции как часть своей логики.

В Python (используя эту библиотеку ), когда структура подобна этой, все в порядке:

# assuming ('Parent', 11), ('Parent', 11, 'Child', 'foo') (with value 'bar'), and ('ChildLookup-foo', 'bar') all exist

def put_parent(c):
 p2 = c.get(c.key('Parent', 22))
 if p2 is None:
  p2 = Entity(c.key('Parent', 22))
 c.put(p2)

def put_child(c):
 with c.transaction():
  e2 = Entity(c.key(*p2.key.flat_path, 'Child', 'foo'))
  e2['__val'] = 'bar'
  el2 = c.get(c.key('ChildLookup-foo', e2['__val']))
  if el2 is not None:
   raise ValueError('insert would create duplicate')
  el2 = Entity(c.key('ChildLookup-foo', 'val'))
  c.put(el2)
  c.put(e2)

c = google.cloud.datastore.Client()

with c.transaction():
 put_parent(c)
 put_child(c)

Попытка выполнить это приведет к правильному поведению: возникнет исключение, и ни один из p2 или e2 не будет вставлен.Однако я могу изменить put_parent так:

def put_parent():
 with c.transaction():  # only actual change. can also be c.batch()
  p2 = c.get(c.key('Parent', 22))
  if p2 is None:
   p2 = Entity(c.key('Parent', 22))
  c.put(p2)

Когда я делаю это таким образом, вставляется p2, несмотря на откат второй транзакции.Для меня это неожиданно: я ожидаю, что откат будет ограничен только самой внутренней транзакцией (или пакетом), или я ожидаю, что откат повлияет на все дочерние транзакции самой внешней транзакции (или пакета).

Конечно, в приведенном выше тривиальном примере с игрушкой я мог просто вынуть внутренние партии и управлять ими с верхнего уровня.Но смысл их использования в методах заключается в том, что я иногда мог бы вызывать их по отдельности без одинаковых гарантий от метода, который вызывает их оба, и я хотел бы, чтобы бизнес их требований к транзакционности был не важен для потребителя этих методов.,Есть ли шаблон дизайна или какая-нибудь библиотека Python Google Cloud Datastore, которая позволила бы мне делать то, что я пытаюсь сделать?

1 Ответ

1 голос
/ 28 сентября 2019

Вы пробовали c.current_transaction?

https://googleapis.dev/python/datastore/latest/client.html

Идея состоит в том, что вы используете

с c.transaction ()

вне всех ваших вызовов и внутри каждого вызова,просто получите текущую транзакцию и используйте ее для выполнения операций.Я думаю, что вы не должны использовать «с» внутри функций, так как это будет автоматически фиксировать / откатывать в конце.

Итак, это будет что-то вроде следующего.

def put_parent(c):
 txn = c.current_transaction
 p2 = txn.get(c.key('Parent', 22))
 if p2 is None:
  p2 = Entity(c.key('Parent', 22))
 txn.put(p2)

def put_child(c):
  txn = c.current_transaction
  e2 = Entity(c.key(*p2.key.flat_path, 'Child', 'foo'))
  e2['__val'] = 'bar'
  el2 = txn.get(c.key('ChildLookup-foo', e2['__val']))
  if el2 is not None:
   raise ValueError('insert would create duplicate')
  el2 = Entity(c.key('ChildLookup-foo', 'val'))
  txn.put(el2)
  txn.put(e2)

c = google.cloud.datastore.Client()

with c.transaction():
 put_parent(c)
 put_child(c)
...