Zagnieżdżone adresy URL REST i identyfikator rodzica, który jest lepszy?

20

Ok, mamy dwa zasoby: Albumi Song. Oto API:

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

Wiemy, że nienawidzimy jakiejś piosenki Susy, na przykład nazywa się . Gdzie powinniśmy podjąć searchdziałania?

Inne pytanie. Okej, teraz jest to bardziej realne. Otwieramy album 1 i ładujemy wszystkie piosenki. Tworzymy obiekty JS, każdy zawiera dane utworu i ma kilka takich metod: remove, update.

Obiekt utworu ma identyfikator, nazwę i inne elementy, ale nie ma pojęcia, do którego rodzica należy, ponieważ pobieramy listę utworów według zapytania i zwracanie identyfikatorów nadrzędnych za każdym razem nie będzie dobre. Czy się mylę?

Widzę więc kilka rozwiązań, ale tak naprawdę nie jestem pewien.

  1. Ustaw identyfikator nadrzędny jako opcjonalny - jako parametr get. Tego podejścia używam obecnie, ale uważam, że jest brzydkie.

    List,Create /songs?album=albumId
    Update,Delete /songs/:songId
    Get /songs/?name=susy # also, solution for first question
    
  2. Hybrydowy. Jest to teraz przydatne, ponieważ potrzebujemy identyfikatora albumu do wykonania OPTIONSzapytania w celu uzyskania metadanych.

    List,Create /album/:albumId/songs
    Update,Delete /songs/:songId
    POST /songs/search # also, solution for first question
    
  3. Zwraca pełny adres URL z każdą instancją zasobu. Interfejs API jest taki sam, ale otrzymamy takie utwory:

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

    Słyszałem, że takie podejście nazywa się HATEOAS.

  4. Więc ... Aby podać identyfikator rodzica

    id: 5
    name: 'Elegy'
    albumId: 2
    

Który jest lepszy? A może jestem głupi? Rzućcie kilka rad, chłopaki!

dt0xff
źródło

Odpowiedzi:

31

Gdzie powinniśmy umieścić akcję wyszukiwania?

W GET /search/:text. Zwróci tablicę JSON zawierającą dopasowania, każde dopasowanie zawierające album, do którego należy. Ma to sens, ponieważ klient może nie być zainteresowany samym utworem, ale całym albumem (wyobraź sobie, że szukasz utworu, który Twoim zdaniem znajdował się w tym samym albumie, co ten, w którym pamiętasz nazwę).

zwracanie identyfikatorów nadrzędnych za każdym razem nie będzie tak dobrze. Czy się mylę?

Poszczególne utwory mogą zawierać album. Zapewni to jednolitość reprezentacji ścieżki, jeśli można ją uzyskać za pośrednictwem albumu lub wyszukiwania (brak albumu tutaj).

Który jest lepszy?

Jak już wspomniano, włączenie albumu ma sens. Podczas gdy trzeci punkt (z względnym URI) może być interesujący w niektórych przypadkach (nie musisz myśleć o tym, jak powinien zostać utworzony URI), ma on wadę polegającą na tym, że nie podajesz wyraźnie albumu. Czwarty punkt to poprawia. Jeśli zauważysz zaletę posiadania względnego identyfikatora URI w odpowiedzi, możesz połączyć punkty 3 i 4.

A może jestem głupi?

Wybór dobrych URI nie jest łatwym zadaniem, zwłaszcza że nie ma jednej właściwej odpowiedzi. Jeśli rozwijasz klienta w tym samym czasie co interfejs API, może to pomóc w lepszej wizualizacji sposobu użycia interfejsu API. To powiedziawszy, inne osoby mogą wtedy preferować inne zastosowania, o których nie myślałeś podczas tworzenia interfejsu API.

Problemem może być sposób wewnętrznej organizacji danych, tj. Wykorzystanie hierarchii. Po twoim komentarzu zastanawiasz się, na co powinna zawierać odpowiedź GET /artist/1/album/10/song/3/comment/23, która pokazuje wizję bardzo zorientowaną na drzewo. Może to prowadzić do kilku problemów przy późniejszym rozszerzaniu systemu. Na przykład:

  • Co jeśli utwór nie ma albumu?
  • Co jeśli album ma kilku wykonawców?
  • Co jeśli chcesz dodać funkcję, która umożliwia komentowanie albumów?
  • Co jeśli powinny być komentarze do komentarzy?
  • itp.

Zasadniczo jest to problem, który wyjaśniłem na moim blogu : reprezentacja drzewa ma zbyt wiele ograniczeń, aby można go było skutecznie wykorzystać w wielu przypadkach.

Co się stanie, jeśli zniszczysz hierarchię? Zobaczmy.

  1. GET /albums/:albumIdzwraca JSON zawierający meta informacje o albumie (takie jak rok wydania lub URI JPEG pokazujący okładkę albumu) oraz tablicę utworów. Na przykład:

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

    Dlaczego podaję na przykład długość każdej ścieżki? Ponieważ wyobrażam sobie, że klient pokazujący album może być zainteresowany, wymieniając utwory według tytułu, ale także pokazując długość każdego utworu - większość klientów tak robi. Z drugiej strony nie mogę pokazać kompozytora (ów) ani artysty (artystów) dla każdego utworu, ponieważ uważam, że na tym poziomie informacje te nie są konieczne. Oczywiście twoje wybory mogą być inne.

  2. GET /tracks/:trackIdzwraca informacje o określonej ścieżce. Ponieważ nie ma już hierarchii, nie musisz zgadywać albumu ani wykonawcy: jedyne, co naprawdę musisz wiedzieć, to identyfikator samego utworu.

    A może nawet nie? Co jeśli możesz podać to po nazwie za pomocą 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
        }
    }

    Teraz spójrz bliżej albums; co widzisz? Racja, nie jeden, ale dwa albumy. Jeśli masz hierarchię, nie możesz tego zrobić (chyba że powielisz rekord).

  3. GET /comments/:objectGid. Być może zauważyłeś brzydkie identyfikatory GUID w odpowiedziach. Te identyfikatory GUID umożliwiają identyfikację jednostki w bazie danych w celu wykonania zadań, które można zastosować do albumów, wykonawców lub utworów. Takie jak komentowanie.

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

    Komentarz odwołuje się do danego obiektu, umożliwiając przejście do niego podczas uzyskiwania dostępu do komentarza poza jego kontekstem (na przykład podczas moderowania najnowszych komentarzy przez GET /comments/latest).

Pamiętaj, że nie oznacza to, że powinieneś unikać jakiejkolwiek formy hierarchii w interfejsie API. Są przypadki, w których ma to sens. Jako zasada:

  • Jeśli zasób nie ma sensu poza kontekstem zasobu nadrzędnego, użyj hierarchii.

  • Jeśli zasób może żyć (1) sam lub (2) w kontekście zasobów nadrzędnych różnych typów lub (3) ma wielu rodziców, hierarchia nie powinna być używana.

Na przykład linie pliku nie mają sensu poza kontekstem pliku, więc:

GET /file/:fileId

i:

GET /file/:fileId/line/:lineIndex

są w porządku.

Arseni Mourzenko
źródło
Tak, z wyszukiwania mogę też zwrócić pełne informacje o albumie, będzie to kolejny zasób - SongSearchResultzakładam, że jest w porządku. Ale co jest z adresami URL? Czy powinienem podać parentIDkażdy obiekt i użyć go jako parametru GET lub zwykłej części adresu URL? Co jeśli mam głębokość> 2? /artist/1/album/10/song/3/comment/23- szaleństwem jest podawanie każdego identyfikatora artysty, albumu i piosenki w commentobiekcie, ale słyszałem, że jest to dobra droga, ale czyż nie jest dsigusting ?!
dt0xff
@ dt0xff: Zredagowałem swoją odpowiedź. Myślę, że powinien dać ci teraz jasny obraz tego, jak można uniknąć głębokości.
Arseni Mourzenko
Tak, teraz jest jasne, że o wiele łatwiej jest zaimplementować punkt wejścia dla każdego zasobu (z wyjątkiem niektórych takich jak linia lub inna funkcjonalna rzecz) bez dodawania go do elementu nadrzędnego przez adres URL. Dziękuję, przekonałeś mnie, że mój wybór był słuszny i „wspólne podejście” (tak naprawdę, wiele zagnieżdżonych rzeczy… restangularjest na nim zbudowanych) nie jest tak dobre.
dt0xff
Świetna odpowiedź. Mam jednak kilka zastrzeżeń. „Co jeśli album zawiera kilku wykonawców?” Różne identyfikatory URI mogą identyfikować ten sam zasób, ponieważ relacja binarna URI -> zasób jest unikalny dla prawej strony (wiele do jednego). Więc URI /artists/foo/albums/quxi /artists/bar/albums/quxmoże doskonale zidentyfikować ten sam zasób albumu. Innymi słowy, składnik ścieżki w URI reprezentuje hierarchię grafów , niekoniecznie hierarchię drzew , co czyni ją odpowiednią do reprezentowania nie tylko kategorii, ale także znaczników.
Maggyero
1
… „Jest to zasadniczo problem, który wyjaśniłem na moim blogu : reprezentacja drzewa ma zbyt wiele ograniczeń, aby można go było skutecznie wykorzystać w wielu przypadkach”. Więc nie, to nie jest problem. „Co jeśli chcesz dodać funkcję, która umożliwia komentowanie albumów?” To nie jest problem albo: /artists/foo/albums/qux/comments/7. „Co jeśli powinny być komentarze do komentarzy?” Analogicznie: /artists/foo/albums/qux/song/5/comments/2/comments/8.
Maggyero