Desain API: Mengapa Lebih Baik Menggunakan Referensi daripada Kunci untuk Mewakili Hubungan dalam API

gambarHalo, Habr!



Kami memiliki edisi kedua Pengembangan Web dengan Node dan Express yang sangat dinantikan .



Sebagai bagian dari penelitian kami tentang topik ini, kami menemukan artikel konseptual tentang mendesain API web dari model, di mana tautan ke sumber daya digunakan sebagai pengganti kunci dan nilai database. Asli - dari blog Google Cloud, selamat datang di bawah cat.





Ketika kita memodelkan informasi, pertanyaan kuncinya adalah bagaimana mendefinisikan hubungan dan hubungan antara dua entitas. Menggambarkan pola yang diamati di dunia nyata dalam kaitannya dengan entitas dan hubungannya adalah gagasan mendasar yang setidaknya kembali ke Yunani kuno. Ini juga memainkan peran mendasar dalam industri TI modern.



Misalnya, dalam teknologi database relasional, hubungan dijelaskan menggunakan kunci asing - nilai yang disimpan dalam satu baris tabel dan menunjuk ke baris yang berbeda, baik di tabel lain atau di tabel yang sama.



Sama pentingnya untuk mengekspresikan hubungan di API. Misalnya, dalam API ritel, entitas informasi dapat sesuai dengan pelanggan, pesanan, entri katalog, keranjang belanja, dan sebagainya. API rekening bank mendeskripsikan pelanggan mana yang memiliki akun tertentu, serta akun mana yang dikaitkan dengan setiap utang atau kredit.



Metode paling umum yang digunakan oleh pengembang API untuk mengekspresikan hubungan adalah dengan menyediakan kunci database atau proxy untuk mereka di bidang entitas yang terkait dengan kunci tersebut. Namun, setidaknya dalam satu kelas API (berorientasi web) ada alternatif yang lebih disukai untuk pendekatan ini: menggunakan tautan web.



Menurut Internet Engineering Task Force ( IETF), tautan web dapat dianggap sebagai alat untuk mendeskripsikan hubungan antar laman di web. Tautan web paling terkenal adalah yang muncul di halaman HTML dan diapit oleh link atau elemen jangkar atau di header HTTP. Namun tautan juga dapat muncul di sumber daya API, dan menggunakannya sebagai ganti kunci asing secara signifikan mengurangi jumlah informasi yang harus didokumentasikan oleh penyedia API dan perlu dipelajari pengguna.



Tautan adalah elemen dalam satu sumber daya web yang berisi referensi ke sumber daya web lain, serta nama hubungan antara kedua sumber daya tersebut. Referensi ke entitas lain ditulis dalam format khusus yang disebut "pengidentifikasi sumber daya unik" (URI), yang memiliki standar IETF... Dalam standar ini, kata "resource" mengacu pada entitas apa pun yang ditunjuk oleh URI. Nama tipe relasi di link bisa dianggap sama dengan nama kolom database yang berisi foreign key, dan URI di link sama dengan nilai foreign key. URI yang paling berguna adalah yang memberikan informasi tentang sumber daya yang direferensikan menggunakan protokol web standar. URI ini disebut "Uniform Resource Locator" (URL), dan URL terpenting untuk API adalah HTTP URL.



Meskipun tautan tidak banyak digunakan di API, beberapa API web yang sangat terkenal masih didasarkan pada URL HTTP sebagai sarana untuk merepresentasikan hubungan. Ini adalah, misalnya, API Google Drivedan GitHub API . Kenapa gitu? Dalam artikel ini, saya akan menunjukkan kepada Anda cara menggunakan API kunci asing dalam praktik, menjelaskan kerugiannya dibandingkan menggunakan tautan, dan menunjukkan kepada Anda cara mengonversi desain yang menggunakan kunci asing ke desain tempat tautan digunakan.



Mewakili hubungan dengan kunci asing



Pertimbangkan aplikasi toko hewan peliharaan pendidikan populer. Aplikasi ini menyimpan catatan untuk melacak informasi tentang hewan peliharaan dan pemiliknya. Hewan peliharaan memiliki atribut seperti nama, spesies, dan ras. Pemilik memiliki nama dan alamat. Setiap hewan terkait dengan pemiliknya, dan hubungan sebaliknya memungkinkan Anda menemukan semua hewan peliharaan dari pemilik tertentu.



Dalam desain tipikal berbasis kunci, API toko hewan menyediakan dua sumber daya yang terlihat seperti ini:







Hubungan antara Lassie dan Joe diekspresikan seperti ini: dalam pandangan Lassie, Joe ditetapkan memiliki nama dan arti yang sesuai dengan "pemilik". Hubungan sebaliknya tidak diungkapkan. Nilai pemilik adalah "98765", yang merupakan kunci asing. Ini mungkin kunci asing sebenarnya dari database - yaitu, kita berurusan dengan nilai kunci utama dari beberapa baris dari beberapa tabel database. Namun, meskipun penerapan API sedikit mengubah nilai kunci, ia masih mendekati kunci asing dalam karakteristik utamanya.

Nilai "98765" sangat tidak sesuai untuk penggunaan pelanggan langsung. Dalam kasus yang paling umum, klien perlu membuat URL menggunakan nilai ini, dan dokumentasi API perlu menjelaskan rumus untuk melakukan konversi ini. Biasanya, ini dilakukan dengan menentukan pola URI , seperti ini:



/people/{person_id}







Hubungan terbalik - hewan peliharaan dimiliki oleh pemilik - juga dapat diekspos ke API dengan mengimplementasikan dan mendokumentasikan salah satu pola URI berikut (perbedaannya hanya gaya, bukan substantif):



/pets?owner={person_id}

/people/{person_id}/pets








API yang dirancang dengan cara ini biasanya membutuhkan banyak pola URI untuk didefinisikan dan didokumentasikan. Bahasa yang paling populer untuk mendefinisikan pola seperti itu bukanlah bahasa yang ditentukan dalam spesifikasi IETF, tetapi OpenAPI (sebelumnya dikenal sebagai Swagger). Sebelum versi 3.0, OpenAPI tidak memiliki cara untuk menentukan nilai bidang mana yang dapat disisipkan ke dalam templat mana, jadi beberapa dokumentasi harus ditulis dalam bahasa alami, dan beberapa harus ditebak oleh klien. OpenAPI 3.0 memperkenalkan sintaks baru yang disebut "tautan" untuk mengatasi masalah ini, tetapi membutuhkan kerja keras untuk menggunakan fitur ini secara konsisten.



Jadi, meskipun gaya ini umum, gaya ini mengharuskan vendor untuk mendokumentasikan dan klien untuk mempelajari dan menggunakan sejumlah besar pola URI yang tidak didokumentasikan dengan baik dalam spesifikasi API saat ini. Untungnya, ada opsi yang lebih baik.



Mewakili hubungan menggunakan tautan



Bagaimana jika sumber daya yang ditunjukkan di atas dimodifikasi sebagai berikut:







Perbedaan utamanya adalah dalam hal ini nilai-nilai tersebut dinyatakan menggunakan referensi, bukan menggunakan nilai kunci asing. Berikut link ditulis dalam JSON biasa, dalam format pasangan nama / nilai (ada bagian di bawah ini yang membahas pendekatan lain untuk menulis link di JSON).



Perhatikan bahwa hubungan terbalik, yaitu dari hewan peliharaan ke pemilik, sekarang juga diterapkan secara eksplisit karena Joel



bidang telah ditambahkan ke tampilan "pets"



.



Perubahan "id"



menjadi "self"



pada dasarnya tidak perlu atau penting, tetapi ada kesepakatan yang digunakan "self"



mengidentifikasi sumber daya yang atribut dan hubungannya ditentukan oleh pasangan nama / nilai lain dalam objek JSON yang sama. "self"



Apakah nama terdaftar di IANA untuk tujuan ini.



Dari sudut pandang implementasi, mengganti semua kunci basis data dengan tautan seharusnya cukup sederhana - server mengonversi semua kunci basis data asing menjadi URL, sehingga tidak ada yang perlu dilakukan pada klien - tetapi API itu sendiri dalam hal ini sangat disederhanakan, dan konektivitas antara klien dan server turun. Banyak pola URI yang penting dalam desain pertama tidak lagi diperlukan dan dapat dihapus dari spesifikasi dan dokumentasi API.



Sekarang tidak ada yang mencegah server mengubah format URL baru kapan pun tanpa memengaruhi klien (tentu saja, server harus terus mematuhi semua URL yang dirumuskan sebelumnya). URL yang diteruskan ke klien oleh server perlu menyertakan kunci utama dari entitas yang ditentukan dalam database ditambah beberapa informasi perutean. Namun, karena klien hanya mengulang URL saat merespons ke server, dan klien tidak pernah harus mengurai URL, klien tidak perlu tahu cara memformat URL. Akibatnya, konektivitas antara klien dan server kurang. Server bahkan dapat menggunakan penyamaran URL-nya sendiri menggunakan base64 atau pengkodean serupa jika ingin menekankan kepada klien bahwa mereka tidak boleh "menebak" apa format URL itu, atau menyimpulkan arti URL dari formatnya.



Dalam contoh sebelumnya, saya menggunakan referensi notasi URI relatif, misalnya /people/98765



. Mungkin klien akan sedikit lebih nyaman (meskipun penulis tidak terlalu membantu dalam memformat posting ini) jika saya mengungkapkan URI dalam bentuk absolut, mis. pets.org/people/98765... Klien hanya perlu mengetahui aturan URI standar yang ditentukan dalam spesifikasi IETF untuk mengonversi URI tersebut dari satu bentuk ke bentuk lainnya, jadi memilih bentuk tertentu untuk URI tidaklah sepenting yang Anda bayangkan. Bandingkan situasi ini dengan konversi di atas dari kunci asing ke URL, yang membutuhkan pengetahuan khusus tentang API toko hewan peliharaan. URL relatif agak lebih nyaman untuk pelaksana server, seperti yang dibahas di bawah ini, tetapi URL absolut mungkin lebih nyaman untuk sebagian besar klien. Ini mungkin alasan mengapa Google Drive dan GitHub API menggunakan URL mutlak.



Singkatnya, menggunakan tautan daripada kunci asing untuk mengekspresikan hubungan antar API mengurangi jumlah informasi yang perlu diketahui klien untuk berinteraksi dengan API, dan juga mengurangi jumlah konektivitas yang dapat terjadi antara klien dan server.



Batuan bawah air



Berikut beberapa hal yang perlu dipertimbangkan sebelum melanjutkan menggunakan tautan.



Banyak implementasi API telah disediakan dengan reverse proxy untuk keamanan, load balancing, dan banyak lagi. Beberapa proxy suka menulis ulang URL. Saat API menggunakan kunci asing untuk merepresentasikan hubungan, satu-satunya URL yang perlu ditulis ulang di proxy adalah URL permintaan utama. Di HTTP, URL ini dipisahkan antara bilah alamat (baris pertama tajuk) dan tajuk host.



API yang menggunakan tautan untuk mengekspresikan hubungan akan memiliki URL lain di header dan isi permintaan dan respons, dan URL ini juga perlu ditulis ulang. Ada beberapa cara berbeda untuk mengatasinya:



  1. URL . URL, .
  2. , . , , , -, , .
  3. . URL; , URL , -. URL, , , , , . - (, URL, ยซยป ยซยป), . , URL, , URL , , , URL .


URL relatif tanpa garis miring di depan juga lebih sulit digunakan klien karena harus bekerja dengan pustaka standar daripada hanya penggabungan string untuk menangani URL tersebut dan memahami serta menyimpan URL dasar dengan cermat.



Menggunakan pustaka standar untuk menangani URL adalah praktik yang baik untuk klien, tetapi banyak klien tidak.

Saat menggunakan tautan, Anda mungkin juga perlu memeriksa ulang versi API Anda. Di banyak API, biasanya memasukkan nomor versi di URL, seperti ini:



/v1/pets/12345

/v2/pets/12345

/v1/people/98765

/v2/people/98765








Ini adalah jenis pembuatan versi, di mana data untuk sumber daya tertentu dapat dilihat secara bersamaan dalam lebih dari satu "format" โ€”ini bukan tentang versi yang saling menggantikan seiring waktu karena kemudian diedit.



Situasi ini sangat mirip dengan kemampuan untuk melihat dokumen yang sama dalam berbagai bahasa alami, yang memiliki standar web; Sayang sekali tidak ada standar seperti itu untuk versi. Dengan menetapkan setiap versi URL-nya sendiri, Anda meningkatkan setiap versi ke sumber daya web yang berfungsi penuh. Tidak ada yang salah dengan "URL berversi" semacam ini, tetapi tidak cocok untuk mengungkapkan tautan. Jika klien meminta Lassie dalam format versi 2, ini tidak berarti bahwa dia juga ingin menerima informasi dalam format 2 tentang Joe, pemilik Lassie, sehingga server tidak dapat memilih nomor versi mana yang akan disertakan dalam tautan.



Mungkin format 2 untuk mendeskripsikan pemilik bahkan tidak akan disediakan. Juga tidak ada poin konseptual dalam menggunakan versi URL tertentu di tautan Anda - lagipula, Lassie bukan milik versi Joe tertentu, tetapi Joe itu sendiri. Oleh karena itu, meskipun Anda memberikan URL dalam format / v1 / people / 98765 dan dengan demikian mengidentifikasi versi tertentu dari Joe, Anda juga harus memberikan URL / people / 98765 untuk mengidentifikasi dirinya sendiri, dan ini adalah opsi kedua yang Anda gunakan di tautan. Pilihan lainnya adalah dengan hanya mendefinisikan URL / people / 98765 dan membiarkan klien memilih versi tertentu dengan memasukkan header permintaan untuk itu. Tidak ada standar untuk tajuk ini, tetapi jika Anda menyebutnya Terima-Versi, ini bekerja dengan baik dengan penamaan tajuk standar.Secara pribadi, saya lebih suka menggunakan header untuk membuat versi dan menghindari menggunakan nomor versi di URL. tetapi URL dengan nomor versi sangat populer dan saya sering menerapkan judulnya juga. dan "URL berversi", karena lebih mudah menerapkan keduanya daripada memperdebatkan mana yang lebih baik. Anda dapat membaca lebih lanjut tentang pembuatan versi API di sini artikel .



Anda mungkin perlu mendokumentasikan beberapa pola url



Di sebagian besar API web, URL sumber daya baru dialokasikan oleh server ketika sumber daya baru dibuat menggunakan metode POST. Jika Anda menggunakan metode ini untuk membuat sumber daya dan menentukan hubungan menggunakan tautan, Anda tidak perlu menerbitkan template untuk URI sumber daya tersebut. Namun, beberapa API memungkinkan klien untuk mengontrol URL sumber daya baru. Dengan mengizinkan klien untuk mengontrol URL sumber daya baru, kami sangat menyederhanakan banyak pola skrip API untuk pengembang front-end, dan juga mendukung skrip di mana API digunakan untuk menyinkronkan model informasi dengan sumber informasi eksternal. HTTP menyediakan metode khusus untuk tujuan ini: PUT. PUT berarti "buat sumber daya di URL ini jika belum ada, dan jika ada, perbarui."Jika API Anda memungkinkan klien membuat entitas baru menggunakan metode PUT, Anda harus mendokumentasikan aturan untuk membuat URL baru, mungkin dengan menyertakan pola URI dalam spesifikasi API. Anda juga dapat memberi klien kontrol parsial atas URL dengan memasukkan nilai seperti kunci utama di badan atau header POST. Dalam kasus ini, pola URI POST tidak diperlukan sendiri, tetapi klien masih harus mempelajari pola URI untuk memanfaatkan sepenuhnya hasil prediksi URI.namun, klien masih harus mempelajari pola URI untuk memanfaatkan sepenuhnya hasil prediksi URI.namun, klien masih harus mempelajari pola URI untuk memanfaatkan sepenuhnya hasil prediksi URI.



Konteks lain yang sesuai untuk mendokumentasikan pola URL adalah ketika API mengizinkan klien untuk permintaan penyandian URL. Tidak setiap API memungkinkan Anda untuk meminta sumber daya Anda, tetapi ini bisa sangat berguna untuk klien, dan tentu saja memungkinkan klien untuk menyandikan URL permintaan dan mengambil hasilnya menggunakan metode GET. Contoh berikut menunjukkan alasannya.



Dalam contoh di atas, kami telah menyertakan pasangan nama / nilai berikut dalam tampilan Joe:



"pets": "/pets?owner=/people/98765"







Klien tidak perlu mengetahui apa-apa tentang strukturnya untuk menggunakan URL ini, selain itu dibuat sesuai dengan spesifikasi standar. Dengan demikian, klien bisa mendapatkan daftar hewan peliharaan Joe dari tautan ini tanpa harus mempelajari bahasa kueri apa pun. Juga tidak perlu mendokumentasikan format URL-nya di API - tetapi hanya jika klien pertama kali membuat permintaan GET untuk /people/98765



... Jika, sebagai tambahan, kemampuan untuk membuat permintaan didokumentasikan di API toko hewan, maka klien dapat membuat URL permintaan yang sama atau setara untuk mengambil hewan peliharaan dari pemilik yang diinginkan, tanpa mengekstrak pemiliknya sendiri - itu akan menjadi cukup untuk mengetahui URI pemiliknya. Mungkin yang lebih penting, klien juga dapat membuat permintaan seperti berikut, yang tidak mungkin dilakukan: Spesifikasi URI menjelaskan untuk tujuan ini sebagian dari URL HTTP yang disebut " komponen permintaan



/pets?owner=/people/98765&species=Dog

/pets?species=Dog&breed=Collie








"Apakah porsi URL setelah yang pertama"? " ke "#" pertama. Gaya permintaan URI yang saya pilih untuk digunakan adalah selalu menempatkan permintaan khusus klien di komponen permintaan URI, tetapi juga dapat diterima untuk mengekspresikan permintaan klien di bagian URL yang disebut "jalur . โ€Bagaimanapun, Anda perlu memberi tahu klien bagaimana URL ini dibuat - Anda sebenarnya mendesain dan mendokumentasikan bahasa permintaan khusus untuk API Anda. Tentu saja, Anda juga dapat mengizinkan klien untuk menempatkan permintaan di badan pesan, bukan di URL, dan gunakan metode POST daripada GET. Batas praktis pada ukuran url - di atas 4k byte Anda selalu tergoda - disarankan untuk mendukung POST untuk permintaan meskipun Anda sudah mendukung GET.



Karena kueri adalah fitur yang sangat berguna di API, dan karena bahasa kueri tidak mudah dirancang dan diterapkan, teknologi seperti GraphQL telah muncul . Saya belum pernah menggunakan GraphQL, jadi saya tidak bisa merekomendasikannya, tetapi Anda dapat menganggapnya sebagai alternatif untuk mengimplementasikan queryability di API Anda. Alat permintaan API, termasuk GraphQL, paling baik digunakan sebagai tambahan pada API HTTP standar untuk membaca dan menulis sumber daya, bukan sebagai alternatif HTTP.



Dan omong-omong ... Apa cara terbaik untuk menulis tautan di JSON?



JSON, tidak seperti HTML, tidak memiliki mekanisme bawaan untuk mengekspresikan tautan. Banyak orang memiliki cara mereka sendiri untuk memahami bagaimana link harus diekspresikan dalam JSON, dan beberapa pendapat seperti itu telah dipublikasikan dalam dokumen resmi yang kurang lebih, tetapi saat ini tidak ada standar yang diratifikasi oleh organisasi terkemuka yang akan mengatur hal ini. Dalam contoh di atas, saya mengungkapkan tautan menggunakan pasangan nama / nilai biasa yang ditulis dalam JSON - Saya lebih suka gaya ini dan gaya ini digunakan di Google Drive dan GitHub. Gaya lain yang mungkin Anda lihat adalah ini:



  {"self": "/pets/12345",
 "name": "Lassie",
 "links": [
   {"rel": "owner" ,
    "href": "/people/98765"
   }
 ]
}
      
      





Secara pribadi, saya tidak melihat untuk apa gaya ini bagus, tetapi beberapa variasinya cukup populer.



Ada gaya referensi JSON lain yang saya suka, dan tampilannya seperti ini:



 {"self": "/pets/12345",
 "name": "Lassie",
 "owner": {"self": "/people/98765"}
}
      
      





Manfaat dari gaya ini adalah ia secara eksplisit memberikan: "/people/98765"



adalah URL, bukan hanya string. Saya mempelajari pola ini dari RDF / JSON . Salah satu alasan untuk menguasai pola ini adalah Anda harus tetap menggunakannya, kapan pun Anda ingin menampilkan informasi tentang satu sumber daya yang bersarang di sumber daya lain, seperti yang ditunjukkan pada contoh berikut. Jika Anda menggunakan pola ini di semua tempat, kode Anda akan mendapatkan keseragaman yang bagus:



{"self": "/pets?owner=/people/98765",
 "type": "Collection",
  "contents": [
   {"self": "/pets/12345",
    "name": "Lassie",
    "owner": {"self": "/people/98765"}
   }
 ]
}
      
      





Untuk informasi selengkapnya tentang cara terbaik menggunakan JSON untuk merepresentasikan data, lihat JSON yang Sangat Sederhana .



Terakhir, apa perbedaan antara atribut dan hubungan?



Saya pikir sebagian besar pembaca akan setuju bahwa JSON tidak memiliki mekanisme bawaan untuk mengekspresikan tautan, tetapi ada juga cara untuk menafsirkan JSON yang memungkinkan Anda untuk membantah sebaliknya. Pertimbangkan JSON berikut:



{"self": "/people/98765",
 "shoeSize": 10
}

      
      





Secara umum diterima bahwa itu shoeSize



adalah atribut, bukan hubungan, dan 10 adalah nilai, bukan entitas. Benar, tidak kalah logisnya untuk menegaskan bahwa string '10 "sebenarnya adalah referensi yang ditulis dalam notasi khusus yang dimaksudkan untuk merujuk ke angka, hingga bilangan bulat ke-11, yang juga merupakan entitas. Jika bilangan bulat ke-11 adalah entitas yang benar-benar valid, dan string '10'



hanya menunjuk padanya, maka pasangan nama / nilai secara '"shoeSize": 10'



konseptual merupakan referensi, meskipun URI tidak digunakan di sini.



Hal yang sama juga berlaku untuk boolean dan string, jadi semua pasangan nama / nilai di JSON dapat diperlakukan sebagai referensi. Jika Anda berpikir seperti ini tentang JSON, maka wajar untuk menggunakan pasangan nama / nilai sederhana di JSON sebagai referensi ke entitas yang juga dapat diarahkan menggunakan URL.



Secara umum, argumen ini dirumuskan sebagai "tidak ada perbedaan mendasar antara atribut dan hubungan". Atribut hanyalah hubungan antara entitas atau entitas abstrak atau konkret lainnya, seperti angka atau warna. Namun secara historis, pemrosesan mereka diperlakukan dengan cara khusus. Terus terang, ini adalah versi persepsi dunia yang cukup abstrak. Jadi, jika Anda menunjukkan seekor kucing hitam kepada seseorang dan bertanya ada berapa benda, kebanyakan orang akan memberi tahu Anda bahwa hanya ada satu. Beberapa orang akan mengatakan bahwa mereka melihat dua objek - kucing dan warna hitamnya - dan hubungan di antara mereka.



Tautan lebih baik



API Web yang meneruskan kunci database, bukan hanya tautan, lebih sulit dipelajari dan juga lebih sulit digunakan untuk klien. Juga API jenis pertama mengikat klien dan server lebih dekat, membutuhkan informasi lebih rinci sebagai "penyebut umum", dan semua informasi ini perlu didokumentasikan dan dibaca. Satu-satunya keuntungan dari API jenis pertama adalah bahwa mereka ada di mana-mana, pemrogram merasa nyaman dengannya, mereka tahu cara membuat dan cara menggunakannya. Jika Anda ingin menyediakan API berkualitas tinggi kepada pelanggan yang tidak memerlukan banyak dokumentasi dan memaksimalkan independensi klien dari server, maka pertimbangkan untuk memberikan tautan ke API web Anda daripada kunci database.



All Articles