¿Cómo puedo manejar relaciones de muchos a muchos en una API RESTful?

Resuelto Richard Handworker asked hace 13 años • 7 respuestas

Imagina que tienes dos entidades, Jugador y Equipo , donde los jugadores pueden estar en varios equipos. En mi modelo de datos, tengo una tabla para cada entidad y una tabla de combinación para mantener las relaciones. Hibernate maneja bien esto, pero ¿cómo puedo exponer esta relación en una API RESTful ?

Se me ocurren un par de formas. Primero, podría hacer que cada entidad contenga una lista de la otra, de modo que un objeto Jugador tendría una lista de Equipos a los que pertenece, y cada objeto Equipo tendría una lista de Jugadores que le pertenecen. Entonces, para agregar un jugador a un equipo, simplemente PUBLICARÍA la representación del jugador en un punto final, algo así como POST /playero POST /teamcon el objeto apropiado como carga útil de la solicitud. Esto me parece lo más "RESTful", pero se siente un poco extraño.

/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'
}

La otra forma que se me ocurre para hacer esto sería exponer la relación como un recurso en sí mismo. Entonces, para ver una lista de todos los jugadores de un equipo determinado, puedes hacer un GET /playerteam/team/{id}o algo así y obtener una lista de entidades PlayerTeam. Para agregar un jugador a un equipo, realice una PUBLICACIÓN /playerteamcon una entidad PlayerTeam construida adecuadamente como carga útil.

/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'
]

¿Cuál es la mejor práctica para esto?

Richard Handworker avatar Jun 13 '11 03:06 Richard Handworker
Aceptado

Haga un conjunto separado de /memberships/recursos.

  1. REST se trata de crear sistemas evolucionables al menos. En este momento, puede que solo te importe que un jugador determinado esté en un equipo determinado, pero en algún momento en el futuro querrás anotar esa relación con más datos: cuánto tiempo han estado en ese equipo, quién los recomendó. a ese equipo, quién es/fue su entrenador mientras estuvo en ese equipo, etc, etc.
  2. REST depende del almacenamiento en caché para lograr eficiencia, lo que requiere cierta consideración sobre la atomicidad y la invalidación del caché. Si PUBLICA una nueva entidad en /teams/3/players/esa lista, se invalidará, pero no desea que la URL alternativa /players/5/teams/permanezca en caché. Sí, diferentes cachés tendrán copias de cada lista con diferentes edades, y no hay mucho que podamos hacer al respecto, pero al menos podemos minimizar la confusión para el usuario que PUBLICA la actualización limitando la cantidad de entidades que necesitamos invalidar. en el caché local de su cliente a uno y solo uno en /memberships/98745(consulte la discusión de Helland sobre "índices alternativos" en Life beyond Distributed Transactions para una discusión más detallada).
  3. Podrías implementar los 2 puntos anteriores simplemente eligiendo /players/5/teamso /teams/3/players(pero no ambos). Supongamos lo primero. Sin embargo, en algún momento querrás reservar /players/5/teams/para obtener una lista de membresías actuales y, aún así, poder consultar membresías anteriores en algún lugar. Haga /players/5/memberships/una lista de hipervínculos a /memberships/{id}/recursos y luego podrá agregarlos /players/5/past_memberships/cuando lo desee, sin tener que romper los marcadores de todos para los recursos de membresía individuales. Este es un concepto general; Seguro que puedes imaginar otros futuros similares que sean más aplicables a tu caso concreto.
fumanchu avatar Jun 13 '2011 16:06 fumanchu

En una interfaz RESTful, puede devolver documentos que describen las relaciones entre recursos codificando esas relaciones como enlaces. Por lo tanto, se puede decir que un equipo tiene un recurso de documento ( /team/{id}/players) que es una lista de enlaces a los jugadores ( /player/{id}) del equipo, y un jugador puede tener un recurso de documento ( /player/{id}/teams) que es una lista de enlaces a los equipos en los que está el jugador. un miembro de. Bonito y simétrico. Puedes realizar las operaciones del mapa en esa lista con bastante facilidad, incluso dando a una relación sus propios ID (podría decirse que tendrían dos ID, dependiendo de si estás pensando en la relación primero el equipo o primero el jugador) si eso facilita las cosas. . El único truco es que debes recordar eliminar la relación del otro extremo también si la eliminas de un extremo, pero manejando esto rigurosamente usando un modelo de datos subyacente y luego haciendo que la interfaz REST sea una vista de ese modelo lo hará más fácil.

Los ID de relación probablemente deberían basarse en UUID o algo igualmente largo y aleatorio, independientemente del tipo de ID que utilice para equipos y jugadores. Eso le permitirá usar el mismo UUID como componente de ID para cada extremo de la relación sin preocuparse por las colisiones (los números enteros pequeños no tienen esa ventaja). Si estas relaciones de membresía tienen otras propiedades además del mero hecho de que relacionan a un jugador y un equipo de manera bidireccional, deberían tener su propia identidad que sea independiente tanto de los jugadores como de los equipos; un GET en la vista jugador»equipo ( /player/{playerID}/teams/{teamID}) podría entonces realizar una redirección HTTP a la vista bidireccional ( /memberships/{uuid}).

Recomiendo escribir enlaces en cualquier documento XML que devuelva (si está produciendo XML, por supuesto) utilizando atributos XLink . xlink:href

Donal Fellows avatar Jun 13 '2011 08:06 Donal Fellows