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

白昼怎懂夜的黑 提交于 2019-12-02 08:32:03

问题


I am having trouble understanding how to structure an ancestor tree with several decedents. Suppose I have a model like this (every Entity has a Long id):

User
    -Post
        -Comment

Where the Comment is the grandchild of the User.

What is really annoying is to insert a Comment I need to generate the Key of the Post. And to generate the Key of the Post I also need to know the ID of the User:

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

This is a problem for me because when trying to insert a Comment into the datastore I need to also pass in the userId and postId just to generate the key of the Post.

Similarly, it is annoying to try and get a one Post because I need to pass in both the userId and postId to generate the Key.

I am looking for a better way to structure my model and API methods without having to pass in all those ancestor IDs to my methods. I was considering storing the websafeKey in every Post and Comment entity as a property like this:

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

Then I could the key to every Post and Comment (and other children of these Entities)) right there in the Entity. Then presumably I wouldn't have to pass in all those ancestor IDs in to my API methods all the time.

Not sure if that is a good idea though.


回答1:


Based on the info provided you have two options:

  • Pass full keys around and design your API around that
  • decouple your entities ancestor relationships, so that no entity has a parent

Note: after writing this all out, I realized the end API looks the same really either way:

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} 

Full keys

In this case, {key} is the websafe key.

Advantages:

  • you can maintain transactional and consistency control
  • allows you to change parent entity relationship later without migrating old data and breaking old links (in my experience this is a massive win)
  • Allows you to mix kinds (e.g. Have a Post kind and a Post2 kind, or an ImagePost kind - useful for polymorphism or for breaking migrations)

Decouple entities as ancestors

In this case {key} is the id, and there is no entity hierarchy

Dis/Advantages:

  • Simplicity
  • You need to infer the kind based on the URL
  • listing posts for a user or comments for a post would always be eventual
  • no migration path if you need to introduce transaction groups
Overall benefits of structuring your API like this:
  • internal coupling for consistency and transactionality not exposed through API
  • aligns with restful API as resource concept
  • supports either model

In my experience you should absolutely do the first option, you will probably get your data model wrong a few times, and being able to change/migrate it is an absolute win.




回答2:


First of all, I agree with Konqi when he says that you should carefully design your ancestor/keys model to get the right level of consistency that you want, taking in consideration the trade-offs of write/sec throughput.

Assuming that you still want the user -> post -> comment design, to deal with datastore ancestor key references like this, we came to a solution that is almost similar to your idea. We create string representations of the keys. For your specific case, it would be:

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

We also have a convenience type to encapsulate that representation, called IdRef<T>.

Everytime an Entity is retrieved from the datastore store we construct this representation and for every API request we translate it from the given string.

Using that approach, we can easily expose APIs like that:

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

I maintain an opensource project that, among other features, solves the problem you are mentioning. It is a Java DSL designed to expose RESTful APIs from your appengine datastore models.

If you are interested, here is a gist that exemplifies your use case.




回答3:


I ran into the same exact problem, and have currently implemented something similar to @feroult, though I am still looking for a better method. Let me explain some of the logic behind this decision.

Let's assume we read a list of some resource that is at least a few levels down in the tree. The "comment" example above could be one example. In this case, if we were to use datastore keys on the client side, then yes, our API would be very RESTful in that the key itself would encompass the path to the root entity. The problem however is that this method is inefficient since the ancestor path is repeated in every key.

The alternative is to create a tree like structure on the client that resembles the tree like data from the datastore. If we want all the comments made by a user, then we only need to have the user id reside in memory once. There is no reason to have it represented in a key for every comment. Which then leads us to the ugly mess of sending the full ancestor path /users/{userId}/posts/{postId}/comments/{commentId} in the RESTful api.

At the end of the day, no matter how the actually path looks, the same information must be passed to the datastore. This is critical. Google Datastore cannot find a comment without the ancestor path.

This means that the problem can be reduced to one of semantics. Perhaps the compromise is to create an equivalent client side key factory that can generate a key from the ancestor ids and then send it to the server. In this case, repetition would be avoided AND the RESTful API would still be clean.

Would be interested in hearing other thoughts.



来源:https://stackoverflow.com/questions/34860123/how-to-create-api-methods-in-google-app-engine-that-have-several-decedents-ances

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!