URL REST anidadas e identificación principal, ¿cuál es mejor diseño?

20

Bien, tenemos dos recursos: Albumy Song. Aquí está la API:

GET,POST /albums
GET,POST /albums/:albumId
GET,POST /albums/:albumId/songs
GET,POST /albums/:albumId/songs/:songId

Sabemos que odiamos alguna canción, se llama Susy, por ejemplo. ¿Dónde debemos poner searchacción?

Otra pregunta. Bien, ahora es más real. Abrimos el álbum 1 y cargamos todas las canciones. Creamos objetos JS, cada uno contiene datos de canciones y tiene pocos métodos como este: remove, update.

El objeto de canción tiene ID, nombre y demás, pero no tiene idea de a qué padre pertenece, porque recuperamos la lista de canciones por consulta y no será tan bueno devolver los identificadores principales con cada uno. ¿Me equivoco?

Entonces, veo pocas soluciones, pero en realidad no estoy seguro.

  1. Haga que la identificación principal sea opcional, como parámetro get. Este enfoque lo uso actualmente, pero siento que es feo.

    List,Create /songs?album=albumId
    Update,Delete /songs/:songId
    Get /songs/?name=susy # also, solution for first question
    
  2. Híbrido. Ahora es tan útil ya que necesitamos la identificación del álbum para realizar OPTIONSconsultas para obtener metadatos.

    List,Create /album/:albumId/songs
    Update,Delete /songs/:songId
    POST /songs/search # also, solution for first question
    
  3. Devuelve la url completa con cada instancia de recurso. API es igual, pero obtendremos canciones como esta:

    id: 5
    name: 'Elegy'
    url: /albums/2/songs/5
    

    Escuché que este enfoque se llama HATEOAS.

  4. Entonces ... Para proporcionar la identificación de los padres

    id: 5
    name: 'Elegy'
    albumId: 2
    

¿Cual es mejor? O tal vez soy tonto? ¡Echen algunos consejos, muchachos!

dt0xff
fuente

Respuestas:

31

¿Dónde debemos poner la acción de búsqueda?

En GET /search/:text. Esto devolverá una matriz JSON que contiene las coincidencias, cada coincidencia que contiene el álbum al que pertenece. Esto tiene sentido, porque el cliente puede estar interesado no en la pista en sí, sino en el álbum completo (imagina que estás buscando una canción que, según crees, estaba en el mismo álbum que recuerdas el nombre).

no será tan bueno devolver los identificadores principales con cada uno. ¿Me equivoco?

Las pistas individuales pueden contener el álbum. Esto asegurará que la representación de la pista sea uniforme si puede obtener una pista a través de un álbum o mediante la búsqueda (aquí no hay ningún álbum).

¿Cual es mejor?

Como se dijo anteriormente, incluir el álbum tiene sentido. Si bien el tercer punto (con el URI relativo) puede ser interesante en algunos casos (no tiene que pensar en la forma en que debe formarse el URI), tiene el inconveniente de no proporcionar explícitamente el álbum. El cuarto punto corrige esto. Si ve el beneficio de tener el URI relativo en la respuesta, puede combinar los puntos 3 y 4.

O tal vez soy tonto?

Elegir buenos URI no es una tarea fácil, especialmente porque no hay una respuesta correcta única. Si desarrolla el cliente al mismo tiempo que la API, puede ayudarlo a visualizar mejor cómo podría usarse la API. Dicho esto, otras personas pueden preferir otros usos en los que no estaba pensando al desarrollar la API.

Un aspecto que puede ser problemático es cómo organiza los datos internamente, es decir, el uso de una jerarquía. Por su comentario, se pregunta qué debería contener una respuesta GET /artist/1/album/10/song/3/comment/23, que muestra una visión muy orientada a los árboles. Esto puede llevar a algunos problemas al extender el sistema más adelante. Por ejemplo:

  • ¿Qué pasa si una canción no tiene un álbum?
  • ¿Qué pasa si un álbum tiene varios artistas?
  • ¿Qué sucede si desea agregar una función que permita comentar álbumes?
  • ¿Qué pasa si debería haber comentarios de comentarios?
  • etc.

Este es esencialmente el problema que expliqué en mi blog : una representación de árbol tiene demasiadas limitaciones para ser utilizada efectivamente en muchos casos.

¿Qué pasa si destruyes la jerarquía? Veamos.

  1. GET /albums/:albumIddevuelve un JSON que contiene la metainformación sobre el álbum (como el año en que se publicó o el URI del JPEG que muestra la portada del álbum) y una serie de pistas. Por ejemplo:

    GET /albums/151
    {
        "id": 151,
        "gid": "dbd3cec7-b927-423f-894b-742c4c7b54ce",
        "name": "Yellow Submarine",
        "year": 1969,
        "genre": "Psychedelic rock",
        "artists": ["John Lennon", "Paul McCartney", ...],
        "tracks": [
            {
                "id": 90224,
                "title": "Yellow Submarine",
                "length": "2:40"
            },
            {
                "id": 83192,
                "title": "Only a Northern Song",
                "length": "3:24"
            }
            ...
        ]
    }

    ¿Por qué incluyo, por ejemplo, la longitud de cada pista? Porque imagino que el cliente que muestra un álbum puede estar interesado al enumerar las pistas por título, pero también muestra la duración de cada pista, la mayoría de los clientes lo hacen. Por otro lado, es posible que no muestre los compositores o los artistas para cada pista, porque decido que esta información no es necesaria en este nivel. Obviamente, sus elecciones pueden ser diferentes.

  2. GET /tracks/:trackIddevuelve la información sobre una pista específica. Como ya no hay jerarquía, no es necesario adivinar el álbum o el artista: lo único que realmente debe saber es el identificador de la pista en sí.

    O tal vez incluso no? ¿Qué pasa si puede especificarlo por nombre con GET /tracks/:trackName?

    GET /tracks/Only%20a%20Northern%20Song
    {
        "id": 83192,
        "gid": "8d9c4311-9d7b-40a4-8aeb-4fe96247fe2b",
        "title": "Only a Northern Song",
        "writers": ["George Harrison"],
        "artists": ["John Lennon", "Paul McCartney", "Ringo Starr"],
        "length": "3:24",
        "record-date": 1967,
        "albums": [151, 164],
        "soundtrack": {
            "uri": "http://audio.example.com/tracks/static/83192.mp3",
            "alias": "Beatles - Only a Northern Song.mp3",
            "length-bytes": 3524667,
            "allow-streaming": true,
            "allow-download": false
        }
    }

    Ahora mira más de cerca albums; ¿que ves? Correcto, no uno, sino dos álbumes. Si tiene una jerarquía, no puede hacerlo (a menos que duplique el registro).

  3. GET /comments/:objectGid. Es posible que haya visto los GUID feos en las respuestas. Esos GUID permiten identificar la entidad en la base de datos para realizar tareas que pueden aplicarse a álbumes, artistas o pistas. Como comentar.

    GET /comments/8d9c4311-9d7b-40a4-8aeb-4fe96247fe2b
    [
        {
            "author": {
                "id": 509931,
                "display-name": "Arseni Mourzenko"
            },
            "text": "What a great song! (And I'm proud of the usefulness of my comment)",
            "concerned-object": "/tracks/83192"
        }
    ]

    El comentario hace referencia al objeto en cuestión, lo que permite acceder a él al acceder al comentario fuera de su contexto (por ejemplo, al moderar los últimos comentarios GET /comments/latest).

Tenga en cuenta que esto no significa que deba evitar cualquier forma de jerarquía en su API. Hay casos en los que tiene sentido. Como una regla de oro:

  • Si el recurso no tiene sentido fuera del contexto de su recurso padre, use la jerarquía.

  • Si el recurso puede vivir (1) solo o (2) en un contexto de recursos padres de diferentes tipos o (3) tener padres múltiples, la jerarquía no debe usarse.

Por ejemplo, las líneas de un archivo no tienen sentido fuera del contexto de un archivo, entonces:

GET /file/:fileId

y:

GET /file/:fileId/line/:lineIndex

estan bien.

Arseni Mourzenko
fuente
De la búsqueda, también puedo devolver la información completa del álbum, será otro recurso SongSearchResult, está bien, supongo. Pero, ¿qué hay de las URL? ¿Debo proporcionar parentIDcada objeto y usarlo como parámetro GET o como parte normal de la URL? ¿Qué pasa si tengo profundidad> 2? /artist/1/album/10/song/3/comment/23- Es una locura proporcionar cada identificación de artista, álbum y canción en el commentobjeto, pero escuché que es un camino a seguir, ¡pero no es dsigusting?
dt0xff
@ dt0xff: edité mi respuesta. Creo que ahora debería darle una imagen clara de cómo se puede evitar la profundidad.
Arseni Mourzenko
Sí, ahora está claro que es mucho más fácil implementar el punto de entrada para cada recurso (excepto algunos como línea u otra cosa funcional) sin agregarlo a la matriz por url. Gracias, me convenciste de que mi elección era correcta y que el "enfoque común" (realmente, muchas cosas anidadas ... restangularse basa en eso) no es tan bueno.
dt0xff
Gran respuesta. Sin embargo, tengo algunas objeciones. "¿Qué pasa si un álbum tiene varios artistas?" Diferentes URI pueden identificar el mismo recurso ya que la relación binaria URI -> resource es única (muchos a uno). Entonces, los URI /artists/foo/albums/quxy /artists/bar/albums/quxpueden identificar perfectamente el mismo recurso del álbum. En otras palabras, el componente de ruta en un URI representa una jerarquía de gráficos , no necesariamente una jerarquía de árbol , lo que lo hace adecuado para representar no solo categorías sino también etiquetas.
Maggyero
1
... "Este es esencialmente el problema que expliqué en mi blog : una representación de árbol tiene demasiadas limitaciones para ser utilizada efectivamente en muchos casos". Entonces no, esto no es un problema. "¿Qué sucede si desea agregar una función que permita comentar álbumes?" Eso no es un problema ya sea: /artists/foo/albums/qux/comments/7. "¿Qué pasa si debería haber comentarios de comentarios?" Del mismo modo: /artists/foo/albums/qux/song/5/comments/2/comments/8.
Maggyero