数据存储:在实体组事务中创建父实体和子实体?

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

阅读 Google 数据存储后 concepts/theory 我开始使用 Go datastore package

场景: UserLinkedAccount 类型要求每个用户都有一个或多个链接帐户(是的第 3 方登录)。为了高度一致,LinkedAccounts 将是关联用户的子级。然后创建新用户涉及创建一个用户和一个 LinkedAccount,而不仅仅是一个。

用户创建似乎是交易的完美用例。如果说 LinkedAccount 创建失败,则事务回滚失败。这目前看来是不可能的。目标是在事务中创建一个父项,然后创建一个子项。

根据文档

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

我们希望新的 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)
// 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

library source 可以清楚地看出为什么这不起作用。 PendingKey 不是密钥,不完整的密钥不能用作父项。

这是 Datastore 或库的必要限制吗?对于那些有过这种需求的人,你们是不是只是牺牲了强一致性而将这两种需求都变成了全局?

对于Google能力:

需要注意的一件事是 Cloud Datastore 中的事务 API can operate on up to 25 entity groups,但这并没有回答如何在同一实体组中创建两个实体作为单笔交易。

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

  1. 为父键使用(字符串)名称,而不是让 Datastore 自动分配数字 ID:

    parentKey := datastore.NewKey(ctx, "Parent", "parent-name", 0, nil)
    childKey := datastore.NewIncompleteKey(ctx, "Child", parentKey)
    
  2. 显式调用 AllocateIds 让数据存储为父键选择一个数字 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)