Вкладені URL-адреси REST та ідентифікатор батьків, який краще дизайн?


20

Гаразд, у нас є два ресурси: Albumі Song. Ось API:

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

Ми знаємо, що ненавидимо якусь пісню, її називають Susy, наприклад. Де ми повинні вжити searchзаходів?

Інше питання. Гаразд, зараз це більш реально. Відкриваємо альбом 1 і завантажуємо всі пісні. Ми створюємо об'єкти JS, кожен має пісенні дані і має кілька методів , наприклад: remove, update.

Об'єкт пісні має ідентифікатор, ім'я та інше, але не має поняття, до якого з батьків належить, тому що ми отримуємо список пісень за запитом, і повертати батьківські ідентифікатори з кожним так буде не так добре. Я помиляюся?

Отже, я бачу мало рішень, але насправді не впевнений.

  1. Зробити батьківський ідентифікатор необов’язковим - як параметр get. Цей підхід я використовую зараз, але я вважаю, що це некрасиво.

    List,Create /songs?album=albumId
    Update,Delete /songs/:songId
    Get /songs/?name=susy # also, solution for first question
    
  2. Гібридний. Зараз це зручно, оскільки нам потрібен ідентифікатор альбому для виконання OPTIONSзапиту, щоб отримати метадані.

    List,Create /album/:albumId/songs
    Update,Delete /songs/:songId
    POST /songs/search # also, solution for first question
    
  3. Повертайте повну URL-адресу з кожним екземпляром ресурсу. API такий же, але ми отримаємо такі пісні:

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

    Я чув, що такий підхід називається HATEOAS.

  4. Отже ... Надати батьківський ідентифікатор

    id: 5
    name: 'Elegy'
    albumId: 2
    

Який краще? А може, я німий? Киньте кілька порад, хлопці!

Відповіді:


31

Куди нам слід поставити пошукову дію?

В GET /search/:text. Це поверне масив JSON, що містить відповідність, кожен матч, що містить альбом, до якого він належить. Це має сенс, тому що клієнта може зацікавити не сам трек, а весь альбом (уявіть, що ви шукаєте пісню, яка, ви вважаєте, була в тому самому альбомі, як і назва, яку ви пам’ятаєте).

буде не так добре повертати батьківські ідентифікатори з кожним так. Я помиляюся?

Окремі композиції можуть містити альбом. Це забезпечить рівномірність представлення доріжок, якщо ви можете отримати трек або через альбом, або через пошук (тут немає альбому).

Який краще?

Як було сказано раніше, в тому числі альбом має сенс. Хоча третій пункт (з відносним URI) може бути цікавим у деяких випадках (не потрібно думати про те, як повинен бути сформований URI), він має недолік, якщо явно не надавати альбом. Четвертий пункт це виправляє. Якщо ви бачите перевагу наявності відносного URI у відповіді, ви можете комбінувати точки 3 та 4.

А може, я німий?

Вибір хороших URI не є легким завданням, тим більше, що немає єдиної правильної відповіді. Якщо ви розробляєте клієнта одночасно з API, це може допомогти вам краще уявити, як API може бути використаний. З цього приводу, інші люди можуть віддавати перевагу іншим звичаям, про які ви не думали, розробляючи API.

Аспект, який може бути проблематичним, - це організація внутрішньої організації даних, тобто використання ієрархії. З вашого коментаря вам цікаво, що має містити відповідь на це GET /artist/1/album/10/song/3/comment/23, що показує дуже орієнтоване на дерево бачення. Це може призвести до виникнення кількох проблем при продовженні системи згодом. Наприклад:

  • Що робити, якщо у пісні немає альбому?
  • Що робити, якщо в альбомі є кілька виконавців?
  • Що робити, якщо ви хочете додати функцію, яка дає можливість коментувати альбоми?
  • Що робити, якщо слід залишити коментарі?
  • тощо.

Це, по суті, проблема, яку я пояснив у своєму блозі : представлення дерева має занадто багато обмежень, щоб ефективно використовуватись у багатьох випадках.

Що станеться, якщо зруйнувати ієрархію? Подивимось.

  1. GET /albums/:albumIdповертає JSON, що містить мета-інформацію про альбом (наприклад, рік, коли він був опублікований або URI JPEG, що показує обкладинку альбому) та масив треків. Наприклад:

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

    Чому я включаю, наприклад, довжину кожного треку? Тому що я думаю, що клієнту, який демонструє альбом, може бути цікаво, перераховуючи доріжки за назвою, а також показуючи довжину кожного треку - більшість клієнтів це робить. З іншого боку, я не можу показувати композитору чи виконавцю для кожної композиції, тому що я вирішую, що ця інформація на цьому рівні не потрібна. Очевидно, ваш вибір може бути різним.

  2. GET /tracks/:trackIdповертає інформацію про певний трек. Оскільки вже немає ієрархії, вам не потрібно вгадувати альбом чи виконавця: єдине, що ви дійсно повинні знати, - це ідентифікатор самого треку.

    А може навіть і ні? Що робити, якщо ви можете вказати його по імені 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
        }
    }

    Тепер придивіться уважніше albums; що ти бачиш? Правильно, не один, а два альбоми. Якщо у вас є ієрархія, ви не можете цього робити (якщо ви не дублюєте запис).

  3. GET /comments/:objectGid. Можливо, у відповідях ви помітили некрасиві GUID. Ці GUID дозволяють ідентифікувати об'єкт у всій базі даних, щоб виконувати завдання, які можна застосувати до альбомів, виконавців або композицій. Такі як коментування.

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

    У коментарі йдеться про відповідний об’єкт, що дозволяє перейти до нього під час доступу до коментаря поза його контекстом (наприклад, при модеруванні останніх коментарів через GET /comments/latest).

Зауважте, що це не означає, що вам слід уникати будь-якої форми ієрархії у вашому API. Бувають випадки, коли це має сенс. Як правило:

  • Якщо ресурс не має сенсу поза контекстом його батьківського ресурсу, використовуйте ієрархію.

  • Якщо ресурс може жити (1) поодинці або (2) в контексті батьківських ресурсів різних типів або (3) мати декількох батьків, ієрархія не повинна використовуватися.

Наприклад, рядки файлу не мають сенсу поза контекстом файлу, тому:

GET /file/:fileId

і:

GET /file/:fileId/line/:lineIndex

добре.


Так, з пошуку я також можу повернути повну інформацію про альбом, це створить інший ресурс - SongSearchResultце добре, я припускаю. Але що стосується URL-адрес? Чи потрібно надавати parentIDкожен об'єкт і використовувати його як параметр GET або нормальну частину URL-адреси? Що робити, якщо у мене глибина> 2? /artist/1/album/10/song/3/comment/23- божевільно надавати кожен ідентифікатор виконавця, альбом та пісню в commentоб’єкті, але я чув, що це шлях, але це не гасне ?!
dt0xff

@ dt0xff: я відредагував свою відповідь. Я думаю, це має тепер дати чітке уявлення про те, як можна уникнути глибини.
Арсеній Муренко

Так, зараз зрозуміло, що набагато простіше реалізувати вхідну точку для кожного ресурсу (за винятком деяких, таких як рядок чи інша функціональна річ) без додавання її до батьківського URL-адреси. Дякую, ви переконали мене, що мій вибір був правильним, і "загальний підхід" (насправді багато вкладених речей .. restangularбудується на ньому) не так добре.
dt0xff

Чудова відповідь. Однак у мене є кілька заперечень. "Що робити, якщо в альбомі є кілька виконавців?" Різні URI можуть ідентифікувати один і той же ресурс, оскільки бінарне відношення URI -> ресурс є єдиним правильним (багато-на-один). Таким чином, URI /artists/foo/albums/quxі /artists/bar/albums/quxможуть ідеально ідентифікувати той самий ресурс альбому. Іншими словами, компонент шляху в URI являє собою ієрархію графіків , не обов'язково ієрархію дерев , що робить його придатним для представлення не тільки категорій, але і тегів.
Маджієро

1
… "По суті це проблема, яку я пояснив у своєму блозі : представлення дерева має занадто багато обмежень, щоб ефективно використовуватись у багатьох випадках". Так ні, це не проблема. "Що робити, якщо ви хочете додати функцію, яка дає можливість коментувати альбоми?" Це не проблема: /artists/foo/albums/qux/comments/7. "Що робити, якщо повинні бути коментарі коментарів?" Точно так же: /artists/foo/albums/qux/song/5/comments/2/comments/8.
Маджієро
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.