Foreign Key in Rest API design [duplicate] - rest
Imagine you have two entities, Player and Team, where players can be on multiple teams. In my data model, I have a table for each entity, and a join table to maintain the relationships. Hibernate is fine at handling this, but how might I expose this relationship in a RESTful API?
I can think of a couple of ways. First, I might have each entity contain a list of the other, so a Player object would have a list of Teams it belongs to, and each Team object would have a list of Players that belong to it. So to add a Player to a Team, you would just POST the player's representation to an endpoint, something like POST /player or POST /team with the appropriate object as the payload of the request. This seems the most "RESTful" to me, but feels a little weird.
/api/team/0:
{
name: 'Boston Celtics',
logo: '/img/Celtics.png',
players: [
'/api/player/20',
'/api/player/5',
'/api/player/34'
]
}
/api/player/20:
{
pk: 20,
name: 'Ray Allen',
birth: '1975-07-20T02:00:00Z',
team: '/api/team/0'
}
The other way I can think of to do this would be to expose the relationship as a resource in its own right. So to see a list of all the players on a given team, you might do a GET /playerteam/team/{id} or something like that and get back a list of PlayerTeam entities. To add a player to a team, POST /playerteam with an appropriately built PlayerTeam entity as the payload.
/api/team/0:
{
name: 'Boston Celtics',
logo: '/img/Celtics.png'
}
/api/player/20:
{
pk: 20,
name: 'Ray Allen',
birth: '1975-07-20T02:00:00Z',
team: '/api/team/0'
}
/api/player/team/0/:
[
'/api/player/20',
'/api/player/5',
'/api/player/34'
]
What is the best practice for this?
Make a separate set of /memberships/ resources.
REST is about making evolvable systems if nothing else. At this moment, you may only care that a given player is on a given team, but at some point in the future, you will want to annotate that relationship with more data: how long they've been on that team, who referred them to that team, who their coach is/was while on that team, etc etc.
REST depends on caching for efficiency, which requires some consideration for cache atomicity and invalidation. If you POST a new entity to /teams/3/players/ that list will be invalidated, but you don't want the alternate URL /players/5/teams/ to remain cached. Yes, different caches will have copies of each list with different ages, and there's not much we can do about that, but we can at least minimize the confusion for the user POST'ing the update by limiting the number of entities we need to invalidate in their client's local cache to one and only one at /memberships/98745 (see Helland's discussion of "alternate indices" in Life beyond Distributed Transactions for a more detailed discussion).
You could implement the above 2 points by simply choosing /players/5/teams or /teams/3/players (but not both). Let's assume the former. At some point, however, you will want to reserve /players/5/teams/ for a list of current memberships, and yet be able to refer to past memberships somewhere. Make /players/5/memberships/ a list of hyperlinks to /memberships/{id}/ resources, and then you can add /players/5/past_memberships/ when you like, without having to break everyone's bookmarks for the individual membership resources. This is a general concept; I'm sure you can imagine other similar futures which are more applicable to your specific case.
In a RESTful interface, you can return documents that describe the relationships between resources by encoding those relationships as links. Thus, a team can be said to have a document resource (/team/{id}/players) that is a list of links to players (/player/{id}) on the team, and a player can have a document resource (/player/{id}/teams) that is a list of links to teams that the player is a member of. Nice and symmetric. You can the map operations on that list easily enough, even giving a relationship its own IDs (arguably they'd have two IDs, depending on whether you're thinking about the relationship team-first or player-first) if that makes things easier. The only tricky bit is that you've got to remember to delete the relationship from the other end as well if you delete it from one end, but rigorously handling this by using an underlying data model and then having the REST interface be a view of that model is going to make that easier.
Relationship IDs probably ought to be based on UUIDs or something equally long and random, irrespective of whatever type of IDs you use for teams and players. That will let you use the same UUID as the ID component for each end of the relationship without worrying about collisions (small integers do not have that advantage). If these membership relationships have any properties other than the bare fact that they relate a player and a team in a bidirectional fashion, they should have their own identity that is independent of both players and teams; a GET on the player»team view (/player/{playerID}/teams/{teamID}) could then do an HTTP redirect to the bidirectional view (/memberships/{uuid}).
I recommend writing links in any XML documents you return (if you happen to be producing XML of course) using XLink xlink:href attributes.
I would map such relationship with sub-resources, general design/traversal would then be:
# team resource
/teams/{teamId}
# players resource
/players/{playerId}
# teams/players subresource
/teams/{teamId}/players/{playerId}
In RESTful-terms it helps a lot in not thinking of SQL and joins, but more into collections, sub-collections and traversal.
Some examples:
# getting player 3 who is on team 1
# or simply checking whether player 3 is on that team (200 vs. 404)
GET /teams/1/players/3
# getting player 3 who is also on team 3
GET /teams/3/players/3
# adding player 3 also to team 2
PUT /teams/2/players/3
# getting all teams of player 3
GET /players/3/teams
# withdraw player 3 from team 1 (appeared drunk before match)
DELETE /teams/1/players/3
# team 1 found a replacement, who is not registered in league yet
POST /players
# from payload you get back the id, now place it officially to team 1
PUT /teams/1/players/44
As you see, I don't use POST for placing players to teams, but PUT, which handles your n:n relationship of players and teams better.
My preferred solution is to create three resources: Players, Teams and TeamsPlayers.
So, to get all the players of a team, just go to Teams resource and get all its players by calling GET /Teams/{teamId}/Players.
On the other hand, to get all the teams a player has played, get the Teams resource within the Players. Call GET /Players/{playerId}/Teams.
And, to get the many-to-many relationship call GET /Players/{playerId}/TeamsPlayers or GET /Teams/{teamId}/TeamsPlayers.
Note that, in this solution, when you call GET /Players/{playerId}/Teams, you get an array of Teams resources, that is exactly the same resource you get when you call GET /Teams/{teamId}. The reverse follows the same principle, you get an array of Players resources when call GET /Teams/{teamId}/Players.
In either calls, no information about the relationship is returned. For example, no contractStartDate is returned, because the resource returned has no info about the relationship, only about its own resource.
To deal with the n-n relationship, call either GET /Players/{playerId}/TeamsPlayers or GET /Teams/{teamId}/TeamsPlayers. These calls return the exactly resource, TeamsPlayers.
This TeamsPlayers resource has id, playerId, teamId attributes, as well as some others to describe the relationship. Also, it has the methods necessary to deal with them. GET, POST, PUT, DELETE etc that will return, include, update, remove the relationship resource.
The TeamsPlayers resource implements some queries, like GET /TeamsPlayers?player={playerId} to return all TeamsPlayers relationships the player identified by {playerId} has. Following the same idea, use GET /TeamsPlayers?team={teamId} to return all the TeamsPlayers that have played in the {teamId} team.
In either GET call, the resource TeamsPlayers is returned. All the data related to the relationship is returned.
When calling GET /Players/{playerId}/Teams (or GET /Teams/{teamId}/Players), the resource Players (or Teams) calls TeamsPlayers to return the related teams (or players) using a query filter.
GET /Players/{playerId}/Teams works like this:
Find all TeamsPlayers that the player has id = playerId. (GET /TeamsPlayers?player={playerId})
Loop the returned TeamsPlayers
Using the teamId obtained from TeamsPlayers, call GET /Teams/{teamId} and store the returned data
After the loop finishes. Return all teams that were got in the loop.
You can use the same algorithm to get all players from a team, when calling GET /Teams/{teamId}/Players, but exchanging teams and players.
My resources would look like this:
/api/Teams/1:
{
id: 1
name: 'Vasco da Gama',
logo: '/img/Vascao.png',
}
/api/Players/10:
{
id: 10,
name: 'Roberto Dinamite',
birth: '1954-04-13T00:00:00Z',
}
/api/TeamsPlayers/100
{
id: 100,
playerId: 10,
teamId: 1,
contractStartDate: '1971-11-25T00:00:00Z',
}
This solution relies on REST resources only. Although some extra calls may be necessary to get data from players, teams or their relationship, all HTTP methods are easily implemented. POST, PUT, DELETE are simple and straightforward.
Whenever a relationship is created, updated or deleted, both Players and Teams resources are automatically updated.
The existing answers don't explain the roles of consistency and idempotency - which motivate their recommendations of UUIDs/random numbers for IDs and PUT instead of POST.
If we consider the case where we have a simple scenario like "Add a new player to a team", we encounter consistency issues.
Because the player doesn't exist, we need to:
POST /players { "Name": "Murray" } //=> 201 /players/5
POST /teams/1/players/5
However, should the client operation fail after the POST to /players, we've created a player that doesn't belong to a team:
POST /players { "Name": "Murray" } //=> 201 /players/5
// *client failure*
// *client retries naively*
POST /players { "Name": "Murray" } //=> 201 /players/6
POST /teams/1/players/6
Now we have an orphaned duplicate player in /players/5.
To fix this we might write custom recovery code that checks for orphaned players that match some natural key (e.g. Name). This is custom code that needs to be tested, costs more money and time etc etc
To avoid needing custom recovery code, we can implement PUT instead of POST.
From the RFC:
the intent of PUT is idempotent
For an operation to be idempotent, it needs to exclude external data such as server-generated id sequences. This is why people are recommending both PUT and UUIDs for Ids together.
This allows us to rerun both the /players PUT and the /memberships PUT without consequences:
PUT /players/23lkrjrqwlej { "Name": "Murray" } //=> 200 OK
// *client failure*
// *client YOLOs*
PUT /players/23lkrjrqwlej { "Name": "Murray" } //=> 200 OK
PUT /teams/1/players/23lkrjrqwlej
Everything is fine and we didn't need to do anything more than retry for partial failures.
This is more of an addendum to the existing answers but I hope it puts them in context of the bigger picture of just how flexible and reliable ReST can be.
I know that there's an answer marked as accepted for this question, however, here is how we could solve the previously raised issues:
Let's say for PUT
PUT /membership/{collection}/{instance}/{collection}/{instance}/
As an example, the followings will all result in the same effect without a need for syncing because they are done on a single resource:
PUT /membership/teams/team1/players/player1/
PUT /membership/players/player1/teams/team1/
now if we want to update multiple memberships for one team we could do as follows (with proper validations):
PUT /membership/teams/team1/
{
membership: [
{
teamId: "team1"
playerId: "player1"
},
{
teamId: "team1"
playerId: "player2"
},
...
]
}
/players (is a master resource)
/teams/{id}/players (is a relationship resource, so it react diferent that 1)
/memberships (is a relationship but semantically complicated)
/players/memberships (is a relationship but semantically complicated)
I prefer 2
Related
Using a sub-resource or not?
Let's take the following example: We want to expose company and employee information from a RESTful API. Company data should be quite simply: GET api/v1/companies GET api/v1/companies/{id} Employees BELONG to a company, but we still want to retrieve them individually as well, so which solution is best: Solution 1: Using sub-resources Get all employees for a company: GET api/v1/companies/{companyId}/employees Get a specific employee: GET api/v1/companies/{companyId}/employees/{employeeId} Solution 2: Using an independent resources Get all employees for a company: GET api/v1/employees?companyId={companyId} Get a specific employee: GET api/v1/employees/{employeeId} Both options seem to have their pros and cons. With sub-resources, I may not always have the CompanyId on hand when wanting to retrieve an individual employee. With an independent resource, getting all employees for a company should use the sub-resource approach if we want to be RESTful. Otherwise, we could use a mix, but this lacks consistency: Get all employees for a company: GET api/v1/companies/{companyId}/employees Get a specific employee: GET api/v1/employees/{employeeId} What is the best approach to take in such a situation if we want to stay true to RESTful standards?
For me this sounds like the common many-to-many relationship problem for RESTful services. (see How to handle many-to-many relationships in a RESTful API?) Your first solution seems good at first but you will have problems whenever you want to access the relation itself. Instead of returning the employee with the following GET request you should return the relation. GET api/v1/companies/{companyId}/employees/{employeeId} If the relation can be identified by 2 keys this solutions seems to be fine. But what happens if the relation is identified by 3+ id's? The URI becomes rather long. GET api/v1/companies/{companyId}/employees/{employeeId}/categories/{categoryId} In this case I would come up with a separate resource for the relation: GET api/v1/company-employees/{id} The returned model in JSON would look like this: { "id": 1 <- the id of the relation "company": { "id": 2 }, "employee": { "id": 3 }, "category": { "id": 4 } }
I think it would be okay to provide both. If you want the client to browse through the list of companies first, then select a company and then get the list of all employees, the first approach is necessary. If, may be in addition, you want the client to be able to filter employees by name or age, but without knowing the company identifier, you must provide the second approach as well. It depends on what you want the client to do. In my opinion, it would not be necessary to provide the second approach, if clients can only filter employees by company identifier.
I would go for the first approach and providing some links to retrieve the subordinate resource. If I take the example of a new employee that you may add in a company. It seems to be difficult, for the client with the second approach to make a POST on your collections. Why ? Because he has to know the company id that is "somewhere else". With the first approach, as you followed a path, you already know this information (the companyId)... so it's easier for the client to add a new employee. Back to your example, the main benefit of the second approach is, if your client want something like "the amount of employees in a city", where you don't care about the notion of company. But it seems that you need the notion of company, so I would go for the first. Also, very related to this question: RESTful design: when to use sub-resources?
What is the correct REST endpoint for adding an item to an array field?
Say I'm trying to model the action of adding a Student to a Group in a RESTful API written in Go with MongoDB. A Group is modeled like this: type Group struct { Section mgo.DBRef Instructor mgo.DBRef Students []mgo.DBRef } An additional constraint is that the API is implementing HAL+JSON protocol, where resources are represented as links. I've seen a couple of options (below): POST /groups/{groupID}/students/{studentID} will add student with studentID to the group. The problem with this approach is that since I'm implementing the HAL+JSON protocol, I don't want the client to have manually pull out the ID and generate this link. All resources will be represented, i.e. /person/123 could be a Student. PUT /groups/{groupID} while sending the complete array of Students that should belong to the group. This seems like it will introduce a lot of complicated parsing logic. If there are other options I'd be open to it too. EDIT: The approach that I'm going with is the following: * POST /groupmembership/ by sending a JSON with the ID of the student and the ID of the group to add the student to. However, on the backend, I'm not generating a new model, but instead taking the object and programmatically adding the specified student to the specified group. The question then is how would I remove the Student from the Group? Can I similar send a DELETE request to /groupmembership with { "student": 123, "group": 456 } to remove student 123 from group 456?
where resources are represented as links This is not true. Links are possibly operations calls, so they are representing possible resource state transitions. To add something to a collection, you need a collection resource and you have to decide what you want to store in that collection. In your case this can be 2 things: group-student memberships or students. If this is an 1:n relation, then you can store students and remove students. If this is an n:m relation then you have to store memberships and remove memberships, since you don't want to remove the students from your storage, just the memberships. You can identify the memberships 2 ways: you can use the ids of the participants: /groups/1/memberships/student:1 or /students/1/memberships/group:1 you can add a unique id to each membership: /memberships/1234 notes: The URI structure matters only from a human perspective. The REST client will check the link relations and not the URI structure. The resources are different from the entities in your database. Only by simple CRUD application represent them the same thing. So REST has nothing to do with your database structure.
First of all, there's no correct REST endpoint. URL semantics are irrelevant to REST. All that matters is that URLs are obtained from hypertext and not from out-of-band information, and seems like you got that part right, since you're using HAL. So, the correct REST endpoint is whatever link your server gives to the clients in order to add the item. As long as an option isn't incorrect from an HTTP standpoint, I'd say to stick with whatever is more consistent with the REST of your API. The option to POST /groups/{groupID}/students/{studentID} in order to create a new student in that location is incorrect, since a POST is submitting the payload to be processed by the targeted resource, and in this case it doesn't exist yet. A common pattern is to use POST /groups/{groupID}/students, where the collection acts as a facory for new elements, with the creation parameters in the payload, and returning the created student URL in the Location header, with 201 HTTP status code.
Looking for RESTful approach to update multiple resources with the same field set
The task: I have multiple resources that need to be updated in one HTTP call. The resource type, field and value to update are the same for all resources. Example: have set of cars by their IDs, need to update "status" of all cars to "sold". Classic RESTFul approach: use request URL something like PUT /cars with JSON body like [{id:1,status:sold},{id:2,status:sold},...] However this seems to be an overkill: too many times to put status:sold Looking for a RESTful way (I mean the way that is as close to "standard" rest protocol as possible) to send status:sold just once for all cars along with the list of car IDs to update. This is what I would do: PUT /cars With JSON {ids=[1,2,...],status:sold} but I am not sure if this is truly RESTful approach. Any ideas? Also as an added benefit: I would like to be able to avoid JSON for small number of cars by simply setting up a URL with parameters something like this: PUT /cars?ids=1,2,3&status=sold Is this RESTful enough?
An even simpler way would just be: {sold:[1,2,...]} There's no need to have multiple methods for larger or smaller numbers of requests - it wastes development time and has no noteable impact upon performance or bandwidth. As far as it being RESTful goes, as long as it's easily decipherable by the recipient and contains all the information you need, then there's no problem with it.
As I understand it using put is not sufficient to write a single property of a resource. One idea is to simply expose the property as a resource itself: Therefore: PUT /car/carId/status with body content 'Sold'. Updating more than one car should result in multiple puts since a request should only target a single resource. Another Idea is to expose a certain protocol where you build a 'batch' resource. POST /daily-deals-report/ body content {"sold" : [1, 2, 3, 4, 5]} Then the system can simply acknowledge the deals being made and update the cars status itself. This way you create a whole new point of view and enable a more REST like api then you actually intended. Also you should think about exposing a resource listing all cars that are available and therefore are ready for being sold (therefore not sold, and not needing repairs or are not rent). GET /cars/pricelist?city=* -> List of all cars ready to be sold including car id and price. This way a car do not have a status regarding who is owning the car. A resource is usually independent of its owner (owner is aggregating cars not a composite of it). Whenever you have difficulties to map an operation to a resource your model tend to be flawed by object oriented thinking. With resources many relations (parent property) and status properties tend to be misplaced since designing resources is even more abstract than thinking in services. If you need to manipulate many similar objects you need to identify the business process that triggers those changes and expose this process by a single resource describing its input (just like the daily deals report).
Can a single url have two different objects (single item or collect of items) be posted and still be RESTfully designed?
I have person json objects that are posted to a rest service that is responsible for creating the objects and storing them in a database. But I have two situations I need to handle. Post a single person object and have one person created Post a person array and have multiple people created Can I use the same url...ie www.mysite.com/people/ and have the server determine whether it is a single person or a collection of people? I know I can technically do this but how should I deal with this situation and still have a RESTful design?
Yes. "POST" is kind of a wildcard, and can "get away" with most anything. What you don't want to do is conflate this resource with the underlying resource. You don't want to POST to /people, rather something specific for the task. So, you'll want something like /people_loader for this task that returns a explicit result of the process (/people_loader/1234), which is a resource linking to the new people you just created. /people is your base resource, so a /people_loader result would be a collection of /people links, and perhaps some other information for those objects that weren't loaded (do to errors or other constraints).
How can I handle many-to-many relationships in a RESTful API?
Imagine you have two entities, Player and Team, where players can be on multiple teams. In my data model, I have a table for each entity, and a join table to maintain the relationships. Hibernate is fine at handling this, but how might I expose this relationship in a RESTful API? I can think of a couple of ways. First, I might have each entity contain a list of the other, so a Player object would have a list of Teams it belongs to, and each Team object would have a list of Players that belong to it. So to add a Player to a Team, you would just POST the player's representation to an endpoint, something like POST /player or POST /team with the appropriate object as the payload of the request. This seems the most "RESTful" to me, but feels a little weird. /api/team/0: { name: 'Boston Celtics', logo: '/img/Celtics.png', players: [ '/api/player/20', '/api/player/5', '/api/player/34' ] } /api/player/20: { pk: 20, name: 'Ray Allen', birth: '1975-07-20T02:00:00Z', team: '/api/team/0' } The other way I can think of to do this would be to expose the relationship as a resource in its own right. So to see a list of all the players on a given team, you might do a GET /playerteam/team/{id} or something like that and get back a list of PlayerTeam entities. To add a player to a team, POST /playerteam with an appropriately built PlayerTeam entity as the payload. /api/team/0: { name: 'Boston Celtics', logo: '/img/Celtics.png' } /api/player/20: { pk: 20, name: 'Ray Allen', birth: '1975-07-20T02:00:00Z', team: '/api/team/0' } /api/player/team/0/: [ '/api/player/20', '/api/player/5', '/api/player/34' ] What is the best practice for this?
Make a separate set of /memberships/ resources. REST is about making evolvable systems if nothing else. At this moment, you may only care that a given player is on a given team, but at some point in the future, you will want to annotate that relationship with more data: how long they've been on that team, who referred them to that team, who their coach is/was while on that team, etc etc. REST depends on caching for efficiency, which requires some consideration for cache atomicity and invalidation. If you POST a new entity to /teams/3/players/ that list will be invalidated, but you don't want the alternate URL /players/5/teams/ to remain cached. Yes, different caches will have copies of each list with different ages, and there's not much we can do about that, but we can at least minimize the confusion for the user POST'ing the update by limiting the number of entities we need to invalidate in their client's local cache to one and only one at /memberships/98745 (see Helland's discussion of "alternate indices" in Life beyond Distributed Transactions for a more detailed discussion). You could implement the above 2 points by simply choosing /players/5/teams or /teams/3/players (but not both). Let's assume the former. At some point, however, you will want to reserve /players/5/teams/ for a list of current memberships, and yet be able to refer to past memberships somewhere. Make /players/5/memberships/ a list of hyperlinks to /memberships/{id}/ resources, and then you can add /players/5/past_memberships/ when you like, without having to break everyone's bookmarks for the individual membership resources. This is a general concept; I'm sure you can imagine other similar futures which are more applicable to your specific case.
In a RESTful interface, you can return documents that describe the relationships between resources by encoding those relationships as links. Thus, a team can be said to have a document resource (/team/{id}/players) that is a list of links to players (/player/{id}) on the team, and a player can have a document resource (/player/{id}/teams) that is a list of links to teams that the player is a member of. Nice and symmetric. You can the map operations on that list easily enough, even giving a relationship its own IDs (arguably they'd have two IDs, depending on whether you're thinking about the relationship team-first or player-first) if that makes things easier. The only tricky bit is that you've got to remember to delete the relationship from the other end as well if you delete it from one end, but rigorously handling this by using an underlying data model and then having the REST interface be a view of that model is going to make that easier. Relationship IDs probably ought to be based on UUIDs or something equally long and random, irrespective of whatever type of IDs you use for teams and players. That will let you use the same UUID as the ID component for each end of the relationship without worrying about collisions (small integers do not have that advantage). If these membership relationships have any properties other than the bare fact that they relate a player and a team in a bidirectional fashion, they should have their own identity that is independent of both players and teams; a GET on the player»team view (/player/{playerID}/teams/{teamID}) could then do an HTTP redirect to the bidirectional view (/memberships/{uuid}). I recommend writing links in any XML documents you return (if you happen to be producing XML of course) using XLink xlink:href attributes.
I would map such relationship with sub-resources, general design/traversal would then be: # team resource /teams/{teamId} # players resource /players/{playerId} # teams/players subresource /teams/{teamId}/players/{playerId} In RESTful-terms it helps a lot in not thinking of SQL and joins, but more into collections, sub-collections and traversal. Some examples: # getting player 3 who is on team 1 # or simply checking whether player 3 is on that team (200 vs. 404) GET /teams/1/players/3 # getting player 3 who is also on team 3 GET /teams/3/players/3 # adding player 3 also to team 2 PUT /teams/2/players/3 # getting all teams of player 3 GET /players/3/teams # withdraw player 3 from team 1 (appeared drunk before match) DELETE /teams/1/players/3 # team 1 found a replacement, who is not registered in league yet POST /players # from payload you get back the id, now place it officially to team 1 PUT /teams/1/players/44 As you see, I don't use POST for placing players to teams, but PUT, which handles your n:n relationship of players and teams better.
My preferred solution is to create three resources: Players, Teams and TeamsPlayers. So, to get all the players of a team, just go to Teams resource and get all its players by calling GET /Teams/{teamId}/Players. On the other hand, to get all the teams a player has played, get the Teams resource within the Players. Call GET /Players/{playerId}/Teams. And, to get the many-to-many relationship call GET /Players/{playerId}/TeamsPlayers or GET /Teams/{teamId}/TeamsPlayers. Note that, in this solution, when you call GET /Players/{playerId}/Teams, you get an array of Teams resources, that is exactly the same resource you get when you call GET /Teams/{teamId}. The reverse follows the same principle, you get an array of Players resources when call GET /Teams/{teamId}/Players. In either calls, no information about the relationship is returned. For example, no contractStartDate is returned, because the resource returned has no info about the relationship, only about its own resource. To deal with the n-n relationship, call either GET /Players/{playerId}/TeamsPlayers or GET /Teams/{teamId}/TeamsPlayers. These calls return the exactly resource, TeamsPlayers. This TeamsPlayers resource has id, playerId, teamId attributes, as well as some others to describe the relationship. Also, it has the methods necessary to deal with them. GET, POST, PUT, DELETE etc that will return, include, update, remove the relationship resource. The TeamsPlayers resource implements some queries, like GET /TeamsPlayers?player={playerId} to return all TeamsPlayers relationships the player identified by {playerId} has. Following the same idea, use GET /TeamsPlayers?team={teamId} to return all the TeamsPlayers that have played in the {teamId} team. In either GET call, the resource TeamsPlayers is returned. All the data related to the relationship is returned. When calling GET /Players/{playerId}/Teams (or GET /Teams/{teamId}/Players), the resource Players (or Teams) calls TeamsPlayers to return the related teams (or players) using a query filter. GET /Players/{playerId}/Teams works like this: Find all TeamsPlayers that the player has id = playerId. (GET /TeamsPlayers?player={playerId}) Loop the returned TeamsPlayers Using the teamId obtained from TeamsPlayers, call GET /Teams/{teamId} and store the returned data After the loop finishes. Return all teams that were got in the loop. You can use the same algorithm to get all players from a team, when calling GET /Teams/{teamId}/Players, but exchanging teams and players. My resources would look like this: /api/Teams/1: { id: 1 name: 'Vasco da Gama', logo: '/img/Vascao.png', } /api/Players/10: { id: 10, name: 'Roberto Dinamite', birth: '1954-04-13T00:00:00Z', } /api/TeamsPlayers/100 { id: 100, playerId: 10, teamId: 1, contractStartDate: '1971-11-25T00:00:00Z', } This solution relies on REST resources only. Although some extra calls may be necessary to get data from players, teams or their relationship, all HTTP methods are easily implemented. POST, PUT, DELETE are simple and straightforward. Whenever a relationship is created, updated or deleted, both Players and Teams resources are automatically updated.
The existing answers don't explain the roles of consistency and idempotency - which motivate their recommendations of UUIDs/random numbers for IDs and PUT instead of POST. If we consider the case where we have a simple scenario like "Add a new player to a team", we encounter consistency issues. Because the player doesn't exist, we need to: POST /players { "Name": "Murray" } //=> 201 /players/5 POST /teams/1/players/5 However, should the client operation fail after the POST to /players, we've created a player that doesn't belong to a team: POST /players { "Name": "Murray" } //=> 201 /players/5 // *client failure* // *client retries naively* POST /players { "Name": "Murray" } //=> 201 /players/6 POST /teams/1/players/6 Now we have an orphaned duplicate player in /players/5. To fix this we might write custom recovery code that checks for orphaned players that match some natural key (e.g. Name). This is custom code that needs to be tested, costs more money and time etc etc To avoid needing custom recovery code, we can implement PUT instead of POST. From the RFC: the intent of PUT is idempotent For an operation to be idempotent, it needs to exclude external data such as server-generated id sequences. This is why people are recommending both PUT and UUIDs for Ids together. This allows us to rerun both the /players PUT and the /memberships PUT without consequences: PUT /players/23lkrjrqwlej { "Name": "Murray" } //=> 200 OK // *client failure* // *client YOLOs* PUT /players/23lkrjrqwlej { "Name": "Murray" } //=> 200 OK PUT /teams/1/players/23lkrjrqwlej Everything is fine and we didn't need to do anything more than retry for partial failures. This is more of an addendum to the existing answers but I hope it puts them in context of the bigger picture of just how flexible and reliable ReST can be.
I know that there's an answer marked as accepted for this question, however, here is how we could solve the previously raised issues: Let's say for PUT PUT /membership/{collection}/{instance}/{collection}/{instance}/ As an example, the followings will all result in the same effect without a need for syncing because they are done on a single resource: PUT /membership/teams/team1/players/player1/ PUT /membership/players/player1/teams/team1/ now if we want to update multiple memberships for one team we could do as follows (with proper validations): PUT /membership/teams/team1/ { membership: [ { teamId: "team1" playerId: "player1" }, { teamId: "team1" playerId: "player2" }, ... ] }
/players (is a master resource) /teams/{id}/players (is a relationship resource, so it react diferent that 1) /memberships (is a relationship but semantically complicated) /players/memberships (is a relationship but semantically complicated) I prefer 2