Urls REST anidados e ID de los padres, ¿cuál es el mejor diseño?

13

Bien, tenemos dos recursos: Album y 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 una canción, se llama Susy , por ejemplo. ¿Dónde deberíamos poner search action?

Otra pregunta. Está 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 algunos métodos como: remove , update .

El objeto de la canción tiene ID, nombre y material, pero no tiene idea de a qué padre pertenece, porque recuperamos la lista de canciones por consulta y no será tan bueno devolver las identificaciones de los padres con cada una. ¿Estoy equivocado?

Veo pocas soluciones, pero en realidad no estoy seguro.

  1. Haz que el ID de los padres sea opcional - como parámetro-get Este enfoque lo utilizo actualmente, pero creo 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 así de práctico, ya que necesitamos la identificación del álbum para realizar la consulta OPTIONS 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
    

¿Cuál es mejor? O tal vez soy tonto? ¡Tira algunos consejos, chicos!

    
pregunta dt0xff 02.03.2015 - 20:29

1 respuesta

24
  

¿Dónde deberíamos poner acción de búsqueda?

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

  

no será tan bueno devolver los identificadores de los padres con cada uno. ¿Estoy equivocado?

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 (no hay álbum aquí).

  

¿Cuál 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 tienes que pensar en la forma en que se debe formar 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 el punto 3 y el punto 4.

  

¿O tal vez soy tonto?

Elegir buenos URI no es una tarea fácil, especialmente porque no hay una única respuesta correcta. 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 cuando desarrolló la API.

Un aspecto que puede ser problemático es cómo se organizan los datos internamente, es decir, el uso de una jerarquía. De tu comentario, te preguntas qué debería contener una respuesta a 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.

Esto 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? A ver.

  1. GET /albums/:albumId devuelve 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 un conjunto 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 me imagino que el cliente que muestra un álbum puede estar interesado al enumerar las pistas por título, pero también mostrar la longitud de cada pista, la mayoría de los clientes lo hacen. Por otro lado, es posible que no muestre el (los) compositor (es) o el (los) artista (s) de cada canción, porque decido que esta información no es necesaria en este nivel. Obviamente, sus opciones pueden ser diferentes.

  2. GET /tracks/:trackId devuelve la información sobre una pista específica. Como ya no existe una jerarquía, no es necesario que adivines el álbum o el artista: lo único que debes saber es el identificador de la pista en sí.

    ¿O tal vez ni siquiera? ¿Qué sucede 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 a albums ; ¿que ves? Claro, no uno, sino dos álbumes. Si tiene una jerarquía, no puede hacer eso (a menos que duplique el registro).

  3. GET /comments/:objectGid . Es posible que haya visto los feos GUID en las respuestas. Esos GUIDs 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 hace posible acceder a él cuando se accede al comentario fuera de su contexto (por ejemplo, al moderar los últimos comentarios a través de 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 regla general:

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

  • Si el recurso puede vivir (1) solo o (2) en un contexto de recursos principales de diferentes tipos o (3) tiene varios padres, no se debe usar la jerarquía.

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

GET /file/:fileId

y:

GET /file/:fileId/line/:lineIndex

están bien.

    
respondido por el Arseni Mourzenko 02.03.2015 - 21:20

Lea otras preguntas en las etiquetas