Rest API Design - Bekerja dengan ID atau String Literal?

8

Ketika mendesain Layanan Web RESTful haruskah API dirancang untuk bekerja ID untuk Strings untuk nilai-nilai yang diteruskan bolak-balik antara server?

Berikut ini sebuah contoh: Katakanlah saya memiliki sumber daya Karyawan, yang memiliki status dan atribut gender. Dalam Status database dan Jenis Kelamin serta tabel terpisah dan dengan demikian memisahkan objek Domain, masing-masing dengan pengidentifikasi sendiri.

Katakanlah permintaan klien / karyawan / 1. Ada server mungkin mengembalikan sesuatu seperti ini ....

Kasus 1:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "gender": "FEMALE"
    },
    "status": {
        "id": 3,
        "status": "FULL_TIME"
    }
}

Kasus 2:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

Kasus 3:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "genderId": 1,
    "statusId": 3
}

Kasus 3 tampaknya masuk akal karena klien tidak tahu apa itu genderId 1 kecuali jika berbalik dan membuat panggilan lain ke server untuk mendapatkan data itu.

Namun sekarang, katakanlah klien memperbarui pengguna melalui:

PUT /employee/1

Haruskah Payload permintaan menggunakan id atau string? Either way, back-end harus mencari mereka untuk memastikan mereka valid, tetapi lebih baik untuk bekerja dengan ID lebih dari Strings.

jkratz55
sumber

Jawaban:

3

Katakanlah saya memiliki sumber daya Karyawan, yang memiliki status dan atribut gender. Dalam Status database dan Jenis Kelamin serta tabel terpisah dan dengan demikian memisahkan objek Domain, masing-masing dengan pengidentifikasi sendiri.

Representasi API Anda tidak boleh digabungkan dengan detail implementasi Anda. Saya lebih jauh mengatakan bahwa mendapatkan representasi API Anda dari detail implementasi Anda persis mundur.

Pikirkan Adapter Patterndari buku Gang of Four . Pesan web adalah dari toko dokumen. Tujuan Anda dalam membuat API adalah untuk menghasilkan dokumen yang diinginkan konsumen Anda, sambil mengisolasinya dari detail yang rumit untuk menghasilkan dokumen-dokumen tersebut.

Motivasi untuk melakukannya, adalah bahwa Anda kemudian dapat mengubah detail implementasi kapan saja Anda inginkan, aman dengan pengetahuan bahwa - selama Anda tidak mengubah representasi yang Anda kembalikan, klien Anda tidak akan rusak.

Juga, perlu diingat bahwa satu sumber daya logis mungkin memiliki banyak representasi, hanya beberapa yang mendukung modifikasi.

katakanlah klien memperbarui pengguna

Sebagai konsumen, representasi mana yang ingin Anda gunakan? Dugaan saya adalah yang terdekat adalah

{
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

Jika saya PUT representasi itu ke lokasi yang Anda tentukan, Anda benar-benar harus bisa menyelesaikan sisanya.

Jika Anda membuat representasi untuk digunakan mesin, maka Anda mungkin ingin lebih sedikit ambiguitas dalam ejaan Anda

{
    "https://schema.org/givenName": "Jane",
    "https://schema.org/familyName": "Doe",
    "active": true,
    "https://schema.org/gender": "https://schema.org/Female",
    "https://schema.org/employmentType": "FULL_TIME"
}

Sumber daya logis yang sama, dua representasi yang berbeda. Kuda untuk kursus.

VoiceOfUnreason
sumber
1

Baik Case 1 dan Case 2 terlihat bagus. Pilihannya dapat diprediksi dengan cara Anda mengatur model Domain Anda.

Anda mencerminkan tabel Karyawan, Jenis Kelamin dan Status di Domain (menggunakan ORM, saya kira). Masing-masing kelas dalam model khusus ini adalah entitas yang memiliki pengenal sendiri . Lebih lanjut mengekspos seluruh model melalui REST API terlihat logis dan cocok dengan Kasus 1.

Atau, Anda dapat tetap berpegang pada prinsip-prinsip DDD yang membayar banyak perhatian pada perbedaan antara entitas dan objek nilai . Dari sudut pandang ini, Karyawan adalah entitas (dengan id) dan Gender dan Status mungkin menjadi kandidat yang baik untuk menjadi objek nilai (tertanam ke dalam entitas Karyawan; tanpa pengidentifikasi). Ini cocok dengan Kasus 2.

Sepenuhnya setuju dengan Anda bahwa Kasus 3 tidak boleh.

Serhii Shushliapin
sumber
1
Kecuali ada alasan kuat, saya tidak akan memasangkan API layanan web dengan erat ke desain basis data. API dan basis data memiliki klien yang berbeda dengan kebutuhan yang berbeda pula.
Eric Stein
Sangat setuju. Itu hanya pengamatan saya tentang desain API penulis (Jenis Kelamin dan Paparan Negara). Mengikuti prinsip-prinsip DDD, saya akan mendesainnya sebagai objek nilai dalam Model Domain saya, dan sebagai hasilnya, saya tidak akan mengekspos pengidentifikasi mereka melalui REST API (yaitu kasus 2).
Serhii Shushliapin
1

Kasus 2 adalah satu-satunya pilihan nyata. Anda sudah menunjukkan masalah dengan Kasus 3. Kasus 1 memberikan informasi yang tidak dipedulikan klien API (ID internal untuk status, misalnya), dan mengharuskan klien untuk mengetahui tentang hal-hal tersebut untuk membangun permintaan PUT . Ya, permintaan PUT sedikit lebih singkat jika dapat menggunakan ID alih-alih string penuh, tetapi menetapkan "FULL_TIME" atau "PART_TIME" adalah apa yang diketahui klien, bukan bahwa mereka kebetulan memiliki beberapa ID sewenang-wenang dalam database Anda .

Tentu saja, Anda dapat mendokumentasikan ID dalam dokumentasi API Anda, tetapi akan lebih mudah untuk mendokumentasikan nilai valid string yang diizinkan, dan mungkin lebih jelas.

David Conrad
sumber
2
Anda harus mencatat ini berarti bahwa jenis kelamin atau status tidak pernah dapat diganti namanya. Jika klien hanya bekerja dengan nama tersebut, maka nama tersebut secara efektif merupakan pengidentifikasi unik. Jika klien harus mulai menggunakan id sebagai pengidentifikasi unik, maka itu adalah perubahan besar.
Eric Stein
0

Data yang dihitung seperti yang Anda dapat di sini sangat dapat disimpan dalam cache. Gunakan tautan sebagai ganti objek. Gunakan header caching untuk memungkinkan klien untuk men-cache gender dan status secara lokal, katakan selama 24 jam. Maka hanya panggilan pertama hari itu yang meninggalkan mesin klien. Anda mungkin juga dapat mengkonfigurasi caching untuk memungkinkan server perantara menyimpan informasi, sehingga beberapa permintaan klien bahkan tidak membuatnya ke server Anda.

GET /employees/1
{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "/genders/1",
    "status": "/statuses/3"
}

// clients populate their dropdown with
GET /genders
[
    {"id":1, "gender":"FEMALE"},
    {"id":2, "gender":"MALE"},
    ...
]

// clients look up an employee's gender with
GET /genders/1
{
    "id": 1,
    "gender": FEMALE
}

Satu kelemahannya adalah itu /genders/1tidak bisa dibaca manusia. Anda bisa menggunakan /genders/female, tetapi Anda tidak akan pernah bisa mengubah nama jenis kelamin tanpa melanggar klien. Itulah kunci sintetis vs. pengorbanan kunci alami - fleksibilitas vs keterbacaan terhadap manusia.

Anda mungkin juga ingin mempertimbangkan untuk meletakkan semua daftar nilai Anda di bawah satu titik akhir yang sama, seperti

/lists/genders/1
/lists/statuses/3

Ini akan menjelaskan kepada klien bahwa mereka pada dasarnya adalah pasangan kunci-nilai yang dimiliki oleh kelompok yang berbeda.

Eric Stein
sumber
0

Saya akan mencari sesuatu di antara 1 dan 2, karena alasan yang disebutkan David:

Anda tidak ingin membuka ID hal-hal kecuali diperlukan.

Namun, mengekspos ID mungkin menjadi perlu di beberapa titik waktu. Jika itu terjadi, kompatibilitas ke belakang menjadi perhatian. Jadi, saya akan melakukan ini:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "name": "FEMALE"
    },
    "status": {
        "name": "FULL_TIME"
    }
}

Itu memiliki properti yang sama seperti opsi 2 miliki; tetapi memiliki manfaat bahwa menambahkan ID nanti tidak memperkenalkan jeda BC:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "name": "FEMALE"
    },
    "status": {
        "id": 3,
        "name": "FULL_TIME"
    }
}

Seperti yang Eric tunjukkan dalam komentar, ini masih menggunakan nama entitas sebagai pengidentifikasi unik. Jika ID diperkenalkan kemudian, nama harus tetap sama karena klien yang lebih tua dapat (atau lebih tepatnya ) akan mengkodekannya.

marstato
sumber
Pendekatan ini memperkenalkan opsi baru. Memiliki 2 sumber yang berbeda: yang pertama untuk query dan yang kedua untuk membuat atau memperbarui. Walaupun mungkin terlihat terlalu banyak kode, perawatannya jadi lebih mudah.
Laiv
@ Longv: saya tidak menyarankan itu; untuk saat ini saya akan menggunakan nameuntuk query dan memperbarui.
marstato
1
Anda harus mencatat ini berarti bahwa jenis kelamin atau status tidak pernah dapat diganti namanya. Jika klien hanya bekerja dengan nama tersebut, maka nama tersebut secara efektif merupakan pengidentifikasi unik. Jika klien harus mulai menggunakan idsebagai pengidentifikasi unik, maka itu adalah perubahan yang melanggar.
Eric Stein