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
):
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.
Based on the info provided you have two options:
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}
In this case, {key} is the websafe key.
Advantages:
In this case {key} is the id, and there is no entity hierarchy
Dis/Advantages:
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.
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.