Search code examples
restrestful-architecture

REST and many to many


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!


Solution

  • 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"
    }]