Datastore:在实体组事务中创建父实体和子实体?

huangapple go评论70阅读模式
英文:

Datastore: Create parent and child entity in an entity group transaction?

问题

阅读了有关Google Datastore概念/理论的内容后,我开始使用Go datastore package

场景:
UserLinkedAccount这两个种类要求每个用户都有一个或多个关联账户(第三方登录)。为了实现强一致性,LinkedAccounts将作为关联User的子实体。因此,创建新用户需要同时创建User和LinkedAccount,而不是只创建其中一个。

用户创建似乎是事务的完美应用场景。例如,如果LinkedAccount创建失败,事务将回滚并失败。但目前似乎不可能实现这一点。目标是在事务中创建一个父实体和一个子实体。

根据文档:

> 如果事务是单个组事务,则事务中的所有Datastore操作必须在同一实体组中操作。

我们希望新的UserLinkedAccount在同一组中,所以我认为Datastore应该支持这种情况。我担心的是,文档的意思是在同一组中可以对现有实体执行操作。

tx, err := datastore.NewTransaction(ctx)
if err != nil {
	return err
}
incompleteUserKey := datastore.NewIncompleteKey(ctx, "User", nil)
pendingKey, err := tx.Put(incompleteUserKey, user)
if err != nil {
	return err
}
incompleteLinkedAccountKey := datastore.NewIncompleteKey(ctx, "GithubAccount", incompleteUserKey)
// 也尝试过将PendingKey作为父实体,但它是一个单独的结构类型
_, err = tx.Put(incompleteLinkedAccountKey, linkedAccount)
if err != nil {
	return err
}
// 尝试提交
if _, err := tx.Commit(); err != nil {
	return err
}
return nil

库源代码可以清楚地看出为什么这样做行不通。PendingKey不是键,而且不完整的键不能用作父实体。

这是Datastore还是库的必要限制?对于有这种需求经验的人来说,你是牺牲了强一致性并将两种类型都设为全局的吗?

供Google搜索使用:

  • datastore:无效键
  • datastore:无法将pendingKey用作类型*"google.golang.org/cloud/datastore".Key
英文:

After reading about Google Datastore concepts/theory I started using the Go datastore package

Scenario:
Kinds User and LinkedAccount require that every user has one or more linked accounts (yay 3rd party login). For strong consistency, LinkedAccounts will be children of the associated User. New User creation then involves creating both a User and a LinkedAccount, never just one.

User creation seems like the perfect use case for transactions. If, say LinkedAccount creation fails, the transaction rolls back an fails. This doesn't currently seem possible. The goal is to create a parent and then a child within a transaction.

According to docs

> All Datastore operations in a transaction must operate on entities in
> the same entity group if the transaction is a single group transaction

We want a new User and LinkedAccount to be in the same group, so to me it sounds like Datastore should support this scenario. My fear is that the intended meaning is that operations on existing entities in the same group can be performed in a single transaction.

tx, err := datastore.NewTransaction(ctx)
if err != nil {
	return err
}
incompleteUserKey := datastore.NewIncompleteKey(ctx, "User", nil)
pendingKey, err := tx.Put(incompleteUserKey, user)
if err != nil {
	return err
}
incompleteLinkedAccountKey := datastore.NewIncompleteKey(ctx, "GithubAccount", incompleteUserKey)
// also tried PendingKey as parent, but its a separate struct type
_, err = tx.Put(incompleteLinkedAccountKey, linkedAccount)
if err != nil {
	return err
}
// attempt to commit
if _, err := tx.Commit(); err != nil {
	return err
}
return nil

From the library source its clear why this doesn't work. PendingKey's aren't keys and incomplete keys can't be used as parents.

Is this a necessary limitation of Datastore or of the library? For those experienced with this type of requirement, did you just sacrifice the strong consistency and make both kinds global?

For Google-ability:

  • datastore: invalid key
  • datastore: cannot use pendingKey as type *"google.golang.org/cloud/datastore".Key

答案1

得分: 5

需要翻译的内容如下:

需要注意的一点是,Cloud Datastore API 中的事务可以操作多达 25 个实体组,但这并不能回答如何在单个事务中创建属于同一实体组的两个实体的问题。

有几种方法可以解决这个问题(请注意,这适用于 Cloud Datastore API 的任何用法,而不仅仅是 gcloud-golang 库):

  1. 使用(字符串)名称作为父键,而不是让 Datastore 自动分配一个数字 ID:
parentKey := datastore.NewKey(ctx, "Parent", "parent-name", 0, nil)
childKey := datastore.NewIncompleteKey(ctx, "Child", parentKey)
  1. 显式调用 AllocateIDs 来让 Datastore 为父键选择一个数字 ID:
incompleteKeys := [1]*datastore.Key{datastore.NewIncompleteKey(ctx, "Parent", nil)}
completeKeys, err := datastore.AllocateIDs(ctx, incompleteKeys)
if err != nil {
  // ...
}
parentKey := completeKeys[0]
childKey := datastore.NewIncompleteKey(ctx, "Child", parentKey)
英文:

One thing to note is that transactions in the Cloud Datastore API can operate on up to 25 entity groups, but this doesn't answer the question of how to create two entities in the same entity group as part of a single transaction.

There are a few ways to approach this (note that this applies to any use of the Cloud Datastore API, not just the gcloud-golang library):

  1. Use a (string) name for the parent key instead of having Datastore automatically assign a numeric ID:

    parentKey := datastore.NewKey(ctx, "Parent", "parent-name", 0, nil)
    childKey := datastore.NewIncompleteKey(ctx, "Child", parentKey)
    
  2. Make an explicit call to AllocateIds to have the Datastore pick a numeric ID for the parent key:

    incompleteKeys := [1]*datastore.Key{datastore.NewIncompleteKey(ctx, "Parent", nil)}
    completeKeys, err := datastore.AllocateIDs(ctx, incompleteKeys)
    if err != nil {
      // ...
    }
    parentKey := completeKeys[0]
    childKey := datastore.NewIncompleteKey(ctx, "Child", parentKey)
    

huangapple
  • 本文由 发表于 2015年7月17日 12:11:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/31468274.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定