URLs REST aninhados e ID pai, qual é o melhor design?


20

Ok, temos dois recursos: Albume Song. Aqui está a API:

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

Sabemos que odiamos alguma música, é chamada Susy, por exemplo. Onde devemos searchagir?

Outra pergunta. Ok, agora é mais real. Abrimos o álbum 1 e carregamos todas as músicas. Criamos objetos JS, cada um contém dados de música e possui poucos métodos como:: remove, update.

O objeto de música tem ID, nome e outras coisas, mas não tem idéia sobre a qual pai pertence, porque recuperamos a lista de músicas por consulta e não será tão bom retornar os IDs pai com cada um deles. Estou errado?

Então, vejo poucas soluções, mas não tenho certeza.

  1. Torne o ID principal opcional - como parâmetro de obtenção. Atualmente, uso essa abordagem, mas acho que é feia.

    List,Create /songs?album=albumId
    Update,Delete /songs/:songId
    Get /songs/?name=susy # also, solution for first question
    
  2. Híbrido. Agora é muito útil, pois precisamos do ID do álbum para executar a OPTIONSconsulta e obter metadados.

    List,Create /album/:albumId/songs
    Update,Delete /songs/:songId
    POST /songs/search # also, solution for first question
    
  3. Retorne o URL completo com cada instância do recurso. A API é a mesma, mas obteremos músicas como esta:

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

    Ouvi dizer que essa abordagem é chamada HATEOAS.

  4. Então ... Para fornecer o ID pai

    id: 5
    name: 'Elegy'
    albumId: 2
    

Qual é melhor? Ou talvez eu seja burro? Dê alguns conselhos, pessoal!

Respostas:


31

Onde devemos colocar a ação de pesquisa?

In GET /search/:text. Isso retornará uma matriz JSON contendo as correspondências, todas as correspondências que contêm o álbum ao qual ele pertence. Isso faz sentido, porque o cliente pode estar interessado não na faixa em si, mas no álbum inteiro (imagine que você esteja procurando por uma música que, você acredita, estava no mesmo álbum que aquela que você lembra do nome).

não será bom retornar os IDs pai com cada um deles. Estou errado?

Faixas individuais podem conter o álbum. Isso garantirá que a representação da faixa seja uniforme se você puder obter uma faixa por meio de um álbum ou por meio de pesquisa (nenhum álbum aqui).

Qual é melhor?

Como afirmado anteriormente, a inclusão do álbum faz sentido. Embora o terceiro ponto (com o URI relativo) possa ser interessante em alguns casos (você não precisa pensar na maneira como o URI deve ser formado), ele tem a desvantagem de não fornecer explicitamente o álbum. O quarto ponto corrige isso. Se você vir o benefício de ter o URI relativo na resposta, poderá combinar os pontos 3 e 4.

Ou talvez eu seja burro?

Escolher URIs bons não é uma tarefa fácil, principalmente porque não existe uma resposta certa. Se você desenvolver o cliente ao mesmo tempo que a API, poderá ajudá-lo a visualizar melhor como a API pode ser usada. Dito isto, outras pessoas podem preferir outros usos em que você não estava pensando ao desenvolver a API.

Um aspecto que pode ser problemático é como você organiza os dados internamente, ou seja, o uso de uma hierarquia. Do seu comentário, você está se perguntando para o que deve conter uma resposta GET /artist/1/album/10/song/3/comment/23, o que mostra uma visão muito orientada para a árvore. Isso pode levar a alguns problemas ao estender o sistema posteriormente. Por exemplo:

  • E se uma música não tiver um álbum?
  • E se um álbum tiver vários artistas?
  • E se você quiser adicionar um recurso que permita comentar álbuns?
  • E se houver comentários de comentários?
  • etc.

Este é essencialmente o problema que expliquei no meu blog : uma representação em árvore tem muitas limitações para ser usada efetivamente em muitos casos.

O que acontece se você destruir a hierarquia? Vamos ver.

  1. GET /albums/:albumIdretorna um JSON contendo as meta informações sobre o álbum (como o ano em que foi publicado ou o URI do JPEG mostrando a capa do álbum) e uma matriz de faixas. Por exemplo:

    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 que incluo, por exemplo, o comprimento de cada faixa? Porque imagino que o cliente que mostra um álbum possa estar interessado, listando as faixas por título, mas também mostrando a duração de cada faixa - a maioria dos clientes. Por outro lado, não posso mostrar o (s) compositor (es) ou artista (s) de todas as faixas, porque decido que essas informações não são necessárias nesse nível. Obviamente, suas escolhas podem ser diferentes.

  2. GET /tracks/:trackIdretorna as informações sobre uma faixa específica. Como não há mais hierarquia, você não precisa adivinhar o álbum ou o artista: a única coisa que você realmente precisa saber é o identificador da faixa em si.

    Ou talvez até não? E se você puder especificá-lo pelo nome 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
        }
    }

    Agora olhe mais de perto albums; O que você vê? Certo, não um, mas dois álbuns. Se você tiver uma hierarquia, não poderá fazer isso (a menos que você duplique o registro).

  3. GET /comments/:objectGid. Você pode ter visto os GUIDs feios nas respostas. Esses GUIDs permitem identificar a entidade no banco de dados para executar tarefas que podem ser aplicadas a álbuns, artistas ou faixas. 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"
        }
    ]

    O comentário faz referência ao objeto em questão, possibilitando acessá-lo fora do contexto (por exemplo, ao moderar os comentários mais recentes GET /comments/latest).

Observe que isso não significa que você deve evitar qualquer forma de hierarquia na sua API. Há casos em que faz sentido. Como um princípio básico:

  • Se o recurso não fizer sentido fora do contexto do recurso pai, use a hierarquia.

  • Se o recurso puder viver (1) sozinho ou (2) em um contexto de recursos pai de tipos diferentes ou (3) ter vários pais, a hierarquia não deve ser usada.

Por exemplo, as linhas de um arquivo não fazem sentido fora do contexto de um arquivo, portanto:

GET /file/:fileId

e:

GET /file/:fileId/line/:lineIndex

Estão bem.


Sim, a partir da pesquisa, também posso retornar as informações completas do álbum, isso criará outro recurso - SongSearchResulttudo bem, eu suponho. Mas o que há com URLs? Devo fornecer parentIDcada objeto e usá-lo como parâmetro GET ou parte normal do URL? E se eu tiver profundidade> 2? /artist/1/album/10/song/3/comment/23- é uma loucura fornecer todos os id de artista, álbum e música em commentobjeto, mas ouvi dizer que é um caminho a percorrer, mas não é tão desagradável ?!
dt0xff

@ dt0xff: editei minha resposta. Eu acho que agora deve lhe dar uma imagem clara de como a profundidade pode ser evitada.
Arseni Mourzenko

Sim, agora está claro que é muito mais fácil implementar o ponto de entrada para cada recurso (exceto alguns como linha ou outra coisa funcional) sem adicioná-lo ao pai por url. Obrigado, você me convenceu de que minha escolha estava certa e a "abordagem comum" (realmente, muitas coisas aninhadas ... restangularsão construídas sobre ela) não é tão boa.
precisa saber é o seguinte

Ótima resposta. Mas tenho algumas objeções. "E se um álbum tiver vários artistas?" URIs diferentes podem identificar o mesmo recurso, pois a relação binária URI -> resource é única à direita (muitos para um). Assim, os URIs /artists/foo/albums/quxe /artists/bar/albums/quxpodem identificar perfeitamente o mesmo recurso de álbum. Em outras palavras, o componente de caminho em um URI representa uma hierarquia de gráfico , não necessariamente uma hierarquia de árvore , o que o torna adequado para representar não apenas categorias, mas também tags.
Maggyero

1
… "Este é essencialmente o problema que expliquei no meu blog : uma representação em árvore tem muitas limitações para ser usada efetivamente em muitos casos". Então não, isso não é um problema. "E se você quiser adicionar um recurso que permita comentar álbuns?" Isso não é um problema ou: /artists/foo/albums/qux/comments/7. "E se houver comentários de comentários?" Da mesma forma: /artists/foo/albums/qux/song/5/comments/2/comments/8.
Maggyero
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.