Url dan ID induk bersarang, desain yang lebih baik?

20

Oke, kami memiliki dua sumber: Albumdan Song. Inilah API:

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

Kita tahu bahwa kita membenci beberapa lagu, demikian sebutannya Susy, misalnya. Di mana kita harus searchbertindak?

Pertanyaan lain. Oke, sekarang ini lebih nyata. Kami membuka album 1 dan memuat semua lagu. Kami membuat objek JS, masing-masing menyimpan data lagu dan memiliki beberapa metode seperti: remove, update.

Objek lagu memiliki ID, nama, dan barang-barang, tetapi tidak memiliki petunjuk tentang orang tua mana yang dimilikinya, karena kami mengambil daftar lagu berdasarkan permintaan dan itu tidak akan baik untuk mengembalikan id induk dengan masing-masing begitu. Apakah aku salah?

Jadi, saya melihat beberapa solusi, tetapi saya tidak yakin benar.

  1. Jadikan parent id opsional - sebagai get-parameter. Pendekatan ini saya gunakan saat ini, tetapi saya merasa itu adalah pendekatan yang jelek.

    List,Create /songs?album=albumId
    Update,Delete /songs/:songId
    Get /songs/?name=susy # also, solution for first question
    
  2. Hibrida. Sekarang sangat praktis karena kita membutuhkan id album untuk melakukan OPTIONSkueri untuk mendapatkan meta-data.

    List,Create /album/:albumId/songs
    Update,Delete /songs/:songId
    POST /songs/search # also, solution for first question
    
  3. Kembalikan url lengkap dengan setiap instance sumber. APInya sama, tetapi kami akan mendapatkan lagu seperti ini:

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

    Saya mendengar pendekatan ini disebut HATEOAS.

  4. Jadi ... Untuk memberikan ID induk

    id: 5
    name: 'Elegy'
    albumId: 2
    

Mana yang lebih baik? Atau mungkin saya bodoh? Lemparkan beberapa saran, teman-teman!

dt0xff
sumber

Jawaban:

31

Di mana kita harus meletakkan tindakan pencarian?

Dalam GET /search/:text. Ini akan mengembalikan susunan JSON yang berisi kecocokan, setiap kecocokan yang berisi album miliknya. Ini masuk akal, karena klien mungkin tertarik bukan pada trek itu sendiri, tetapi seluruh album (bayangkan bahwa Anda sedang mencari lagu yang, Anda yakin, ada di album yang sama dengan yang Anda ingat namanya).

tidak akan baik untuk mengembalikan id orang tua dengan masing-masing begitu. Apakah aku salah?

Masing-masing trek dapat berisi album. Ini akan memastikan bahwa representasi trek seragam jika Anda bisa mendapatkan trek baik melalui album atau melalui pencarian (tidak ada album di sini).

Mana yang lebih baik?

Seperti yang dinyatakan sebelumnya, termasuk album masuk akal. Meskipun poin ketiga (dengan URI relatif) dapat menarik dalam beberapa kasus (Anda tidak harus memikirkan tentang bagaimana URI harus dibentuk), ia memiliki kelemahan karena tidak menyediakan album secara eksplisit. Poin keempat mengoreksi ini. Jika Anda melihat manfaat memiliki URI relatif dalam respons, Anda dapat menggabungkan poin 3 dan 4.

Atau mungkin saya bodoh?

Memilih URI yang baik bukanlah tugas yang mudah, terutama karena tidak ada jawaban yang benar. Jika Anda mengembangkan klien pada saat yang sama dengan API, mungkin membantu Anda untuk memvisualisasikan lebih baik bagaimana API dapat digunakan. Karena itu, orang lain mungkin lebih suka penggunaan lain yang tidak Anda pikirkan saat mengembangkan API.

Aspek yang mungkin bermasalah adalah bagaimana Anda mengatur data secara internal, yaitu penggunaan hierarki. Dari komentar Anda, Anda bertanya-tanya apa yang harus berisi tanggapan GET /artist/1/album/10/song/3/comment/23, yang menunjukkan visi yang sangat berorientasi pohon. Ini dapat menyebabkan beberapa masalah saat memperpanjang sistem nanti. Contohnya:

  • Bagaimana jika sebuah lagu tidak memiliki album?
  • Bagaimana jika sebuah album memiliki beberapa artis?
  • Bagaimana jika Anda ingin menambahkan fitur yang memungkinkan untuk mengomentari album?
  • Bagaimana jika harus ada komentar komentar?
  • dll.

Ini pada dasarnya adalah masalah yang saya jelaskan di blog saya : representasi pohon memiliki terlalu banyak keterbatasan untuk digunakan secara efektif dalam banyak kasus.

Apa yang terjadi jika Anda menghancurkan hierarki? Ayo lihat.

  1. GET /albums/:albumIdmengembalikan JSON yang berisi informasi meta tentang album (seperti tahun ketika diterbitkan atau URI dari JPEG yang menunjukkan sampul album) dan serangkaian trek. Sebagai contoh:

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

    Mengapa saya memasukkan, misalnya, panjang setiap trek? Karena saya membayangkan bahwa klien yang menunjukkan album mungkin tertarik dengan membuat daftar lagu berdasarkan judul, tetapi juga menunjukkan panjang setiap lagu — kebanyakan klien melakukannya. Di sisi lain, saya tidak boleh menunjukkan komposer atau artis untuk setiap lagu, karena saya memutuskan bahwa informasi ini tidak diperlukan pada tingkat ini. Jelas, pilihan Anda mungkin berbeda.

  2. GET /tracks/:trackIdmengembalikan informasi tentang trek tertentu. Karena tidak ada hierarki lagi, Anda tidak perlu menebak album atau artis: satu-satunya hal yang benar-benar harus Anda ketahui adalah pengidentifikasi trek itu sendiri.

    Atau mungkin bahkan tidak? Bagaimana jika Anda dapat menentukan namanya dengan nama 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
        }
    }

    Sekarang lihat lebih dekat albums; apa yang kamu lihat? Benar, bukan satu, tapi dua album. Jika Anda memiliki hierarki, Anda tidak bisa melakukan itu (kecuali jika Anda menduplikasi catatan).

  3. GET /comments/:objectGid. Anda mungkin telah melihat GUID yang jelek dalam respons. GUID tersebut memungkinkan untuk mengidentifikasi entitas di seluruh basis data untuk melakukan tugas yang dapat diterapkan pada album, atau artis, atau trek. Seperti berkomentar.

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

    Komentar mereferensikan objek yang bersangkutan, sehingga memungkinkan untuk pergi ketika mengakses komentar di luar konteksnya (misalnya ketika memoderasi komentar terbaru melalui GET /comments/latest).

Perhatikan bahwa ini tidak berarti Anda harus menghindari segala bentuk hierarki di API Anda. Ada kasus di mana itu masuk akal. Sebagai aturan praktis:

  • Jika sumber daya tidak masuk akal di luar konteks sumber daya induknya, gunakan hierarki.

  • Jika sumber daya dapat hidup (1) sendiri atau (2) dalam konteks sumber daya orang tua dari berbagai jenis atau (3) memiliki banyak orang tua, hierarki tidak boleh digunakan.

Misalnya, baris file tidak masuk akal di luar konteks file, jadi:

GET /file/:fileId

dan:

GET /file/:fileId/line/:lineIndex

baik-baik saja.

Arseni Mourzenko
sumber
Ya, dari pencarian, saya dapat mengembalikan info album lengkap juga, itu akan membuat sumber daya lain - SongSearchResult, tidak apa-apa, saya kira. Tapi bagaimana dengan URL? Haruskah saya memberikan parentIDsetiap objek dan menggunakannya sebagai parameter-GET atau bagian normal dari url? Bagaimana jika saya memiliki kedalaman> 2? /artist/1/album/10/song/3/comment/23- Ini gila untuk memberikan setiap id artis, album dan lagu dalam commentobjek, tapi saya dengar itu adalah cara untuk pergi, tetapi bukankah ini dsigusting ?!
dt0xff
@ dt0xff: Saya mengedit jawaban saya. Saya pikir itu sekarang harus memberi Anda gambaran yang jelas tentang bagaimana kedalamannya dapat dihindari.
Arseni Mourzenko
Ya, jelas sekarang bahwa lebih mudah untuk mengimplementasikan entry-point untuk setiap sumber daya (kecuali beberapa seperti garis atau hal fungsional lainnya) tanpa menambahkannya ke induk oleh url. Terima kasih, Anda meyakinkan saya bahwa pilihan saya benar dan "pendekatan umum" (sungguh, banyak hal yang bersarang .. restangulardibangun di atasnya) tidak begitu baik.
dt0xff
Jawaban yang bagus Saya punya beberapa keberatan. "Bagaimana jika sebuah album memiliki beberapa artis?" URI yang berbeda dapat mengidentifikasi sumber daya yang sama karena relasi biner URI -> sumber daya adalah unik-unik (banyak-ke-satu). Jadi URI /artists/foo/albums/quxdan /artists/bar/albums/quxdapat dengan sempurna mengidentifikasi sumber daya album yang sama. Dengan kata lain, komponen jalur dalam URI mewakili hierarki grafik , tidak harus hierarki pohon , yang membuatnya cocok untuk mewakili tidak hanya kategori tetapi juga tag.
Maggyero
1
... "Ini pada dasarnya masalah yang saya jelaskan di blog saya : representasi pohon memiliki terlalu banyak keterbatasan untuk digunakan secara efektif dalam banyak kasus." Jadi tidak, ini bukan masalah. "Bagaimana jika Anda ingin menambahkan fitur yang memungkinkan untuk berkomentar album?" Itu bukan masalah baik: /artists/foo/albums/qux/comments/7. "Bagaimana kalau harus ada komentar komentar?" Demikian juga: /artists/foo/albums/qux/song/5/comments/2/comments/8.
Maggyero