Another alternative (using HATEOAS). This is simple, mostly in practice you add a links tag in the json depending on your use of HATEOAS.
http://api.example.com/games/1
:
{
"id": 1,
"title": "Game A",
"publisher": "Publisher ABC",
"developer": "Developer DEF",
"releaseDate": "2015-01-01",
"platforms": [
{"_self": "http://api.example.com/games/1/platforms/53", "name": "Playstation"},
{"_self": "http://api.example.com/games/1/platforms/34", "name": "Xbox"},
]
}
http://api.example.com/games/1/platforms/34
:
{
"id": 34,
"title": "Xbox",
"publisher": "Microsoft",
"releaseDate": "2015-01-01",
"testReport": "http://api.example.com/games/1/platforms/34/reports/84848.pdf",
"forms": [
{"type": "edit", "fields: [] },
]
}
You can off course embed all data in all listing but that will likely be way too much data. This way you can embed the required data and then load more if you really want to work with it.
The technical implementation can contain caching. You can cache the platforms links and names in the game object and send it instantly without having to load the platforms api at all. Then when required you can load it.
You see for example that I added some form information. I did that to show you there can be much more information in a detailed json object than you would even want to load in the listing of games.
The general underlying answer here is that your ideas may work on a technical level, but that doesn't mean they conform to the standardized conventions of REST.
- PUT/PATCH - What is the point of passing in the entire resource for modification? I only use PUTs to modify resources, and I only pass in the fields I want to be updated. As a result, I have no need to use PATCH
Your idea works on a technical level, but it's simply not how REST has been described. Keep in mind that any discussion about working code (i.e. no compile or runtime errors) is always going to be a matter of convention, not necessarily of clear technical superiority.
- Resource Paths - I use GUIDs in my application. As a result, they will be globally unique. Why do I need the full resource path, including the parent resources, if I can just uniquely refer to a subresource by itself?
There are many nuances to how we define "child/parent" entities. Most commonly, it refers for a one-to-many (parent-to-children) relationship.
However I suspect that for REST, part of what makes a child a child is that there is an expectation of only being able access them through the parent, that they don't carry their own globally unique (and externally known) identifier.
I suspect this is following the same philosophy (but not necessarily for the same reason) as that of aggregates (and their roots) in Domain Driven Development.
A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An aggregate will have one of its component objects be the aggregate root. Any references from outside the aggregate should only go to the aggregate root.
In your case, what you call the "parent" functions as the aggregate root. The single point of contact (if you will) for outside callers.
You might want to conclude from this that your child is actually a different aggregate. That may be the case, but I do want to issue a warning with that decision. You shouldn't base your architecture on the particular type of a field. You have no way of knowing if you will always keep using globally unique IDs for all your entities. If that ever changes, for whatever reason, you're going to compromise the viability of your REST architecture; as you may end up in a situation where the child is no longer uniquely identifiable and thus needs to be referenced through its parent.
- If I POST to create a new resource, PUT to update, or DELETE to remove, I want to see the deltas in the tree, rather than just seeing the resource that I created/updated/deleted.
You're violating the order of operations of the design. A REST API is specifically intended to be consumer agnostic. The API should not be built according to the specifications of one of its consumers.
When you say "I want to see the deltas in the tree", what you're really saying is "the consuming application only has a need to see the deltas in the tree". But that doesn't quite matter to the REST api. It merely provides a standardized approach.
It is the nature of standardized approaches to often lack highly customizable tools, and instead favor the most commonly used tools.
Can you deviate from the path? Well, it will work on a technical level. But it won't be pure REST anymore. This is something that is highly contextual and you need to weigh the options.
- If you're creating an API that is expected to cater to many varied consumers, then I suggest sticking to REST as best as you can.
- If you're building an API that will only have one consumer which is also developed by you; then there's no real need to stick to pure REST.
- Straying from the path means you're going to have to document how you've strayed so other developers can still make sense of it. If you stick to pure REST, you don't have to write the documentation and the other developers don't have to spend time and effort figuring out your customized approach.
Best Answer
Based on your description, I would use option three. Option two is probably the worst of the bunch since you would be maximizing duplication and minimizing reuse.
Option one is the inheritance model. I don't feel like it's a good fit for the situation. The resulting class hierarchy will introduce complexity but you won't be gaining much in the trade-off. Inheritance can be very useful when you have a number of different objects which all share a common superclass. In this case you really only have one type of object (a car), you just have varying levels of completeness.
With your single car class from option three, there are some good tricks you can use like lazy loading of properties. Basically if your class has been filled with the least amount of data possible then in the getter of the other properties you can trigger a call out to the more detailed API and fill in the rest of the information. Be careful to model this type of behavior around actual usage patterns the object may see, but it can greatly help with ease of use for other programmers who consume your object.