4

I'm basing my question on How to handle many-to-many relationships in a RESTful API? but want to continue from the accepted answer.

Let's suppose we have a many-to-many relationship between players and teams (just like in the question mentioned above).

As I understand it, there are several options to model this with REST resources:

The payload contains references to the related resources

GET /players/john

yields

{
    "name": "John",
    "_links": [
        {
            "rel": "team",
            "href": "/teams/1"
        },
        {
            "rel": "team",
            "href": "/teams/4"
        }
    ]
}

and

GET /teams/1

yields

{
    "name": "Team 1",
    "_links": [
        {
            "rel": "player",
            "href": "/players/john"
        },
        ...
    ]
}

This forces me to update a player-resource when I just want to add a player to the team. Furthermore, when I add a player to the team using a player-resource, the corresponding team-resource gets automatically updated. According to How to handle many-to-many relationships in a RESTful API?:

you don't want the alternate URL /players/5/teams/ to remain cached

In this case, teams/1 might remain cached when I update player "John" to remove team "Team 1" from it!

The relationship is modelled as another resource

GET /teams/1

yields

{
    "name": "Team 1",
}

and

GET /players/john

yields

{
    "name": "John",
}

Finally,

GET /relationships

yields

    [
    {
        "_links": [
            {
                "rel": "player",
                "href": "/players/john"
            },
            {
                "rel": "team",
                "href": "/teams/1"
            }
        ]
    },

    ...
]

This way, I can create and delete relationships without affecting both player-resources and team-resources. But when I delete /players/john, should the matching relationships be automatically deleted as well? If this is the case, the same rule as above is violated. If this is not the case we need the manually delete these relationships which is a lot of work I do not want to burden the consumers of my API with.

Furthermore, if we want to update the teams a certain player "John" is in, we need to delete some relationships and add others. We open ourselves up to merge conflicts (and race conditions) when someone else is editing the player "John" or the team "Team 1".

Each side of the relationship gets its own relationship-object

GET /teams/1/players

yields something like

{
    "_links": [
        {
            "rel": "player",
            "href": "/players/john"
        },
        ...
    ]
}

and

GET /players/john/teams

something like

{
    "_links": [
        {
            "rel": "team",
            "href": "/teams/1"
        },
        ...
    ]
}

But adding or removing one might still affect a resource that is located at a different URL (which does not share a root element)

My questions

Is there away around the problems I have mentioned in both cases?

Which of both approaches is 'preferable' or more pure REST?

How serious should I take the contstraint mentioned in How to handle many-to-many relationships in a RESTful API?:

you don't want the alternate URL /players/5/teams/ to remain cached

Thank you in advance!

Community
  • 1
  • 1
Werner de Groot
  • 933
  • 7
  • 15
  • *This forces me to update a player-resource when I just want to add a player to the team.* why is that bad? – Tim Mar 12 '15 at 10:28
  • For instance, I need to know all OTHER teams a player is in, to update the player to the right state (having ALL the right relationships to teams). Furthermore, I want to allow players to change teams, but I do not want to allow players to change their names. This is the responsibility of an administrator. Both use the same resource and I need to check roles and compare those to the changes that are requested instead of just to disallow PUT or POST on the resource for some users. – Werner de Groot Mar 12 '15 at 10:31
  • *I need to know all OTHER teams a player is in* why? you could just append a team to player.teams – Tim Mar 12 '15 at 10:33
  • How would that work? Would you say you prefer the first approach then? – Werner de Groot Mar 12 '15 at 10:37
  • You would PUT a user and add the new team to the user's list of teams – Tim Mar 12 '15 at 10:53
  • Could you please explain in a couple of examples what you mean? Where is the 'Team 1' resource located, where is the 'John' resource located? How do the responses look like? How can I get all teams of a certain player and vice versa? Thank you in advance! – Werner de Groot Mar 13 '15 at 15:29

1 Answers1

1

You could have the following

Team

GET /teams/dream

{
    "_links": {
        "self": {
            "href": "/teams/dream"
        }
        "players": {
            "href": "/players?team=dream"
        }
    },
    "name": "Dream"
}

Player

GET /player/john

{
    "_links": {
        "self": {
            "href": "/player/john"
        },
        "teams": {
            "href": "/teams?player=john"
        },
    },
    "name": "John",
}

John's teams

GET /teams?player=john

{
    "_links": {
    },
    "items": [
        {
            "href": "/teams/a-team"
        }
    ],
}

Adding john to dream team, (using json patch for example) (query string on patch post...etc though rare, is valid)

PATCH /teams?player=john

[{
    "op": "add",
    "path": "/-",
    "value": {
        "href": "/team/dream"
    }
}]

Get john's teams

GET /teams?player=john

{
    "_links": {
    },
    "items": [
        {
            "href": "/teams/a-team"
        },
        {
            "href": "/teams/dream"
        }
    ]
}

John leaves A Team :

PATCH /teams?player=john

[{
    "op": "remove",
    "path": "/0"
}]
redben
  • 5,578
  • 5
  • 47
  • 63
  • Thank you for taking the time to answer my question. Is there a reason why you use an "op" attribute "remove" and "add" instead of using DELETE and PATCH? And how do you DELETE all relations of John? Through /teams?user=john? – Werner de Groot Mar 23 '15 at 20:38
  • DELETE /teams?player=john For patch, you could use any structure you want but there is an effort on making something standard for that. See http://jsonpatch.com – redben Mar 23 '15 at 20:45
  • Thank you for your answer. I was not aware of JSON patch, but it looks like it might solve a lot of my problems! – Werner de Groot Mar 27 '15 at 07:42
  • I would (and do in practice) use the actual resource path (i.e. `/teams/a-team`) in the delete operation. You wouldn't want to use an index. Even if the order was guaranteed (rarely true in a collection), another user could delete an earlier index between the time you GET and DELETE. You'd end up affecting the wrong record. – claytond Mar 22 '18 at 21:24