如何在具有多个 decedents/ancestors 的 Google App Engine 中创建 API 方法

How to create API methods in Google App Engine that have several decedents/ancestors

我无法理解如何构建具有多个后代的祖先树。假设我有一个这样的模型(每个实体都有一个 Long id):

User
    -Post
        -Comment

其中 CommentUser 的孙子。

真正烦人的是插入一个Comment我需要生成Post的Key。要生成 Post 的密钥,我还需要知道 User:

的 ID
Key<Post> postKey = Key.create(Key.create(User.class, userId), Post.class, postId);

这对我来说是个问题,因为在尝试将 Comment 插入数据存储区时,我还需要传入 userId 和 postId 以生成 Post 的键。

同样,尝试获得一个很烦人Post,因为我需要同时传递 userId 和 postId 来生成密钥。

我正在寻找一种更好的方法来构造我的模型和 API 方法,而不必将所有这些祖先 ID 传递给我的方法。我正在考虑将 websafeKey 存储在每个 Post 和 Comment 实体中作为 属性,如下所示:

String websafeKey = Key.create(Key.create(User.class, userId), Post.class, postId).getString();
Key<Post> key = Key.create(websafeKey);

然后我可以在实体中找到每个 Post 和评论(以及这些实体的其他子项)的密钥。那么大概我就不必一直将所有这些祖先 ID 传递到我的 API 方法中了。

虽然不确定这是否是个好主意。

首先,我同意 Konqi 所说的你应该仔细设计你的 ancestor/keys 模型 考虑到 write/sec 的 trade-offs 以获得所需的正确一致性水平 吞吐量。

假设还是要用户->post->评论设计,来处理datastore ancestor 像这样的关键参考,我们得出了一个几乎相似的解决方案 你的想法。我们创建键的字符串表示。对于您的具体情况, 它将是:

/users/{userId}/posts/{postId}/comments/{commentId}

我们还有一个方便的类型来封装该表示,称为 IdRef<T>

每次从数据存储中检索到实体时,我们都会构造此表示 对于每个 API 请求,我们从给定的字符串中翻译它。

使用这种方法,我们可以像这样轻松地公开 API:

GET /users/{userId}/posts/{postId}/comments/{commentId}
POST /users/{userId}/posts/{postId}/comments/{commentId}
DELETE /users/{userId}/posts/{postId}/comments/{commentId}

我维护了一个 opensource project,除其他功能外,它还解决了您提到的问题。 它是一个 Java DSL,旨在从您的应用引擎数据存储模型中公开 RESTful APIs。

如果您有兴趣,这里有一个 gist 来举例说明您的用例。

根据所提供的信息,您有两个选择:

  • 传递完整的密钥并围绕它设计你的API
  • 解耦您的实体祖先关系,这样就没有实体有父代

注:全部写完后,我意识到结尾 API 无论哪种方式看起来都一样:

GET /user/{key} - get user info
POST /user/{key}/post/ - create post 
GET /post/{key} - get post
POST /post/{key}/comment/ - create comment
GET /comment/{key} 

全键

在这种情况下,{key} 是网络安全密钥。

优点:

  • 您可以维护事务和一致性控制
  • 允许您稍后更改父实体关系,而无需迁移旧数据和破坏旧链接(以我的经验,这是一个巨大的胜利)
  • 允许您混合种类(例如,有一个 Post 种类和一个 Post2 种类,或者一个 ImagePost 种类 - 对于多态性或中断迁移很有用)

解耦作为祖先的实体

本例中{key}是id,没有实体层级

Dis/Advantages:

  • 简单
  • 你需要根据 URL
  • 来推断种类
  • 为用户列出 post 或为 post 发表评论总是最终的
  • 如果需要引入事务组则没有迁移路径
像这样构建 API 的总体好处:
  • 一致性和事务性的内部耦合不会通过 API
  • 公开
  • 与 restful API 一致作为资源概念
  • 支持任一模型

根据我的经验,您绝对应该选择第一个选项,您可能会多次弄错数据模型,而能够change/migrate绝对是胜利。

我 运行 遇到了同样的问题,并且目前已经实现了类似于@feroult 的东西,尽管我仍在寻找更好的方法。让我解释一下这个决定背后的一些逻辑。

假设我们读取了一些资源的列表,这些资源至少在树中向下几层。上面的 "comment" 例子就是一个例子。在这种情况下,如果我们要在客户端使用数据存储密钥,那么是的,我们的 API 会非常 RESTful 因为密钥本身将包含到根实体的路径。然而,问题是这种方法效率低下,因为祖先路径在每个键中重复。

另一种方法是在客户端创建类似于数据存储中树状数据的树状结构。如果我们想要一个用户发表的所有评论,那么我们只需要让用户id在内存中驻留一次。没有理由在每一条评论中都用一个键来表示它。然后导致我们在 RESTful api 中发送完整的祖先路径 /users/{userId}/posts/{postId}/comments/{commentId} 。

归根结底,无论实际路径如何,都必须将相同的信息传递到数据存储区。这很关键。 Google Datastore 找不到没有祖先路径的评论。

这意味着问题可以简化为语义问题之一。也许妥协是创建一个等效的客户端密钥工厂,该工厂可以从祖先 ID 生成密钥,然后将其发送到服务器。在这种情况下,将避免重复并且 RESTful API 仍然是干净的。

有兴趣听听其他想法。