Reputation: 2500
In general situations, when one entity has a relation to another, I nest REST resources this way:
POST /user/{userId}/accounts
And it is ok for entities from the same domain. But when it comes to entities from different domains it doesn't make sense. For example, I have bus lines (@Entity Line
) and operators (@Entity Operator
). Every line has an operator:
@ManyToOne
@JoinColumn(name = "operator_id")
private Operator operator;
so if I need to create new bus line i must pass operator.
There is no problem, if I need to create line with new operator, but if I want just to reference operator, I need to pass operator_id somehow. Some ideas how to handle this:
1. Nest Line in Operator
POST /operators/{operatorId}/lines {name: "15B", type: "BUS"}
It's OK from the technical perspective, but I want to keep operators and lines separated, because line doesn't really "belong" (nesting) to operator.
2. Pass operatorId directly
POST /lines {name: "15B", type: "BUS", operator: 12}
There are some problems with this thing. There is a case, when I want to create new Line with new Operator and the query will look like:
POST /lines {name: "15B", type: "BUS", operator: {name: "SuperBUS"}}
And I need to handle both situations. This will bring additional entity (coz original one has Operator operator
, not int operator
) and "magic" logic, that will decide if I want to create line with new operator or old one.
Are there any best practices for handling such situations?
Upvotes: 1
Views: 148
Reputation: 7744
Here are a couple of ideas, that may or may not help you to decide what to use:
Passing Ids
Passing Ids around couples the client to some implementation detail of the object. This is generally something to be avoided. Consider the following code:
public Line createLine(String name, LineType lineType, int operatorId);
The caller must know about the operatorId, which is usually not part of the Model. The id is usually just an implementation detail of the persistence, in other words, an Operator does not really have an id, it has a name at most, but that actually can change.
In "normal" OO code, this method may look like this instead:
public Line createLine(String name, LineType lineType, Operator operator);
Passing the actual Operator instead, with the help of a reference. What is the equivalent of an object reference in RESTful HTTP? The URI
of course. So that would get us to:
POST /lines
{"name": "15B", "type": "BUS", "operator": "http://something/124"}
There is however another problem with this approach, present in both Java and HTTP API: We can not be sure what kind of object we get. In Java, if Operator is an interface, we do not know what implementation we get. That implementation might not be in our database for example. The same is true for the HTTP API. If we accept a URI, we can not be really sure where that resource is coming from, maybe it doesn't even support our media-types
.
This is probably not what you want.
Offering Context
This is what you call "nesting". Thinking about Java code, this is equivalent to:
public interface Operator {
...
Line createLine(String name, LineType lineType);
...
}
In HTTP:
POST /operator/123/lines
{"name": "15B", "type": "BUS"}
This would probably be more appropriate, even if the Operators do not really "own" the lines. The server can check for duplicate lines, or similar semantic rules.
Hypermedia
If you really want the Lines to be independent entities, you have to reference the operators somehow. Ids would introduce coupling, but offering URIs is too generic, then the only solution is to introduce forms.
This might be more complicated to implement correctly. The point is to make forms similar to what HTML has. Just offer those options which fit to the client with hypermedia.
Something like this:
GET /lines
{"create": {
"operator": {
"type": "select",
"values": [ ... ]
}
},
"lines": [ ... ]
}
Upvotes: 2