3

Imagine that I have a system to manage sports teams. Let's not make it specific to a particular sport, but consider that each team consists of a number of players.

Therefore, I might have an operation like this to add a player to a team.

POST http://.../teams/myteam/players/

And something like this to update a player's details.

PUT http://.../teams/myteam/players/foobob

And this to get a list of players in a team.

GET http://.../teams/myteam/players

Now, each player has a specific position within the team - and I have a user interface where someone can change the positions of those players within the team. So I might drag player #1 to position 4, player #2 to position 1, and player #3 to position 6. The impact of this change should only take effect once the user has completed the entire operation - so I cannot update individual players on the fly.

Consequently, at the end of this process, I have some kind of object that maintains an updated mapping of player to order - Player Name -> Number. I now need to push these changes from the front-end to the back-end. However, given that I'm updating the team as a whole, what should my REST endpoint for this operation look like? I'm updating an entire collection of entities with a new ID, so it doesn't feel right to publish it to a player-specific endpoint. Hence, I think I should treat the team as the resource being updated.

I'm also wary of doing a PUT to the team, given that I'm modifying only a single field on each player, rather than replacing the 'team' entity in its entirety.

I'm leaning towards using PATCH to update the team, and then specifying the reordering as part of the body. This will then go off and update the individual players belonging to the team. Are there any obvious design issues with this?

PATCH http://.../teams/myteam

{
  {player: bob, number: 1},
  {player: foo, number: 3},
  {player: bazbar, number: 2}
}
1
  • 1
    I feel like you are getting too caught up in the semantics; the spec describes PUT as [emphasis mine] "The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload." I.e., you are replacing the current state of a conceptual resource; whether that's done by actually replacing the resource, or parts of it, or by updating a few fields is hidden from the client. Conceptually, you are not "modifying only a single field on each player", you are changing the state of the team. Jan 24, 2020 at 18:45

6 Answers 6

7

I think you might be missing a resource... have you considered a /team/myteam/player_positions resource?

There are some also comments about PATCH not being supported... I wouldnt be too concerned about that if you're building both the server and the client, so use it if it makes sense for you.

That being said, using PUT to fully update a resource called /player_positions seems like it would be a good fit. That resource could reference player entities without having to pull in the entire player record.

Whilst this might not be a requirement for you API, the above approach also allows players to have multiple positions across multiple teams.

Also, don't worry too much about how the resources are implemented persistence-layer-wise if the above doesn't match what your database layout has, as that shouldn't have a bearing on how you implement your outward facing API.

3

REST is really only well specified for CRUD operations on objects and sets.

Furthermore, PUT replaces the resource in its entirety. So technically if you were to update each player with a position, you'd have to read and write all fields. Not everyone does it that way.

I prefer to POST a command object, i.e.

POST http://.../teams/myteam/reorder
{ 
   "reorder":[ 
      { 
         "player":"name1",
         "priority":"100"
      },
      { 
         "player":"name2",
         "priority":"200"
      }
   ]
}

Then ignore the fact "reorder" is not a collection of resources and can't be read back

1

Firstly, PATCH is not officially supported, so, using it would be a considerable bottleneck before even thinking about anything else. If possible, steer away from it.

Secondly, before sending anything to the server, try and ensure data consistency and validation on the front-end or even UI side, anything that ensures you that before you click your Save or Confirm button, the ordering you are getting is valid, somehow. Checks for duplicates, consistency in the entries, bogus values, etc, can all be done on the front-end.

Then, for sending the data across, I'd probably use a representation of the permutations that allows you to do a simple variable substitution in the backend (where presumably you have the original array).

Assume your original array is:

5,3,1

And you want to store now:

1,5,3

You could send across, a representation of the values by index (using 0-based indexing):

swaps = 2,0,1

Then in your backend, you can just map this array to your original one:

swaps.map(element -> original[element])

Like this the only thing you'd need to send is a simple string. Maybe with an PUT, since you're essentially updating an existing resource.

1
1

At a very high level, it sounds like your workflow is

  • Download a copy of the server's current representation of the list of players
  • Make edits to your local copy
  • Request that the server modify it's copy of the resource to match yours.

That's right in the sweet spot of HTTP; the flow would normally look like:

GET /teams/myteam/players

// Make your local edits

PUT /teams/myteam/players

The fact that your local editor is just re-ordering the elements of the list isn't particularly important.

The important idea is that PUT has precisely the semantics that we want in the second request

The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload. -- RFC 7231

HTTP PATCH is a different way of achieving the same result.

GET /teams/myteam/players

// Make your local edits
// Compute the patch-document that matches your edits

PATCH /teams/myteam/players

The basic idea -- change the server copy to match your local copy -- isn't changed. What changes is only the representation we send with the request to describe our changes; instead of sending a complete representation of the new version of the document, we only send a representation of the changes.

For example, if the list of players were a JSON document, then we might represent our changes using some generic standard like application/merge-patch+json or application/json-patch+json.

It is deliberate here that the target-uri of the PUT/PATCH request is an exact match to the target-uri used to GET the server representation; we do it that way to take advantage of the cache invalidation semantics that are built into HTTP.

0

Firstly PATCH is not universally supported - see https://restfulapi.net/http-methods/

I would lean towards using PUT instead of PATCH.

6
  • Also, try to avoid using plurals in your resource paths. The REST convention tends more towards singular nouns.
    – Carl
    Jan 24, 2020 at 11:57
  • In this case, with the team being the resource that is updated with a collection of Player objects?
    – Riaan Nel
    Jan 24, 2020 at 12:12
  • Yes. Try team/{teamname}/player/{playername}
    – Carl
    Jan 24, 2020 at 12:15
  • That's the catch though - I'm updating the sequence of players in a team. So I might update 10 players in one go, through a single post, where the 'number' (or order) of a player is the only field that changes.
    – Riaan Nel
    Jan 24, 2020 at 12:19
  • 1
    I could, but what it one of the updates fails for any reason? Those individual calls won't be in a single transaction, so my team will end up in a bad state where it no longer resembles what's on the user interface when the user hits the 'Save' button.
    – Riaan Nel
    Jan 24, 2020 at 12:31
0

How is the following idea? (I think this is wholly or partially mentioned in some of the previous answers)

We can have a new endpoint "/teams/{myteam}/player_positions"

  1. lets think of it as an end-point w/ the sole responsibility of keeping the sequence/order of the players records. it should have 2 fields: id and players_value_ref_id:
  2. we can change the position of existing "player record", by placing it (referencing it) in a different "player_positions record"
  3. changing a "player_positions record" will implicitly change the positions of all the following records:

lets look at the following examples, assuming the initial ordering in example 1, lets follow the actions and results

Example 1:

GET /teams/myteam/player_positions/

RESPONSE:
{
  {
    "id": 1
    "players_value_ref_id": "bob"
  },
  {
    "id": 2
    "players_value_ref_id": "foo"
  },
  {
    "id": 3
    "players_value_ref_id": "bazbar"
  }
}

Example 2:

GET /teams/myteam/player_positions/1

RESPONSE:
{
  "id": 1
  "players_value_ref_id": "bob"
}

Example 3:

PUT /teams/myteam/player_positions/2
{
  "players_value_ref_id": "bazbar"
}

Example 4:

GET /teams/myteam/player_positions/

RESPONSE:
{
  {
    "id": 1
    "players_value_ref_id": "bob"
  },
  {
    "id": 2
    "players_value_ref_id": "bazbar"
  },
  {
    "id": 3
    "players_value_ref_id": "foo"
  }
}

How do you like it?!

PS

  1. of course we'll need some error handling and logic, like when user is placing a record to an out-of-bound record id...
  2. for mass re-ordering we can make use of PUT or PATCH directly to the parent record /teams/myteam/player_positions/, but we would avoid using such whenever possible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.