RESTFul: nyatakan tindakan perubahan

59

Saya berencana untuk membangun RESTfull API tetapi ada beberapa pertanyaan arsitektur yang membuat beberapa masalah di kepala saya. Menambahkan logika bisnis backend ke klien adalah pilihan yang ingin saya hindari karena memperbarui beberapa platform klien sulit dipertahankan secara real time ketika logika bisnis dapat dengan cepat berubah.

Katakanlah kita memiliki artikel sebagai sumber daya (api / artikel), bagaimana seharusnya kita menerapkan tindakan seperti menerbitkan, membatalkan publikasi, mengaktifkan atau menonaktifkan dan sebagainya, tetapi untuk mencoba membuatnya sesederhana mungkin?

1) Haruskah kita menggunakan api / article / {id} / {action} karena banyak logika backend dapat terjadi di sana seperti mendorong ke lokasi terpencil atau mengubah beberapa properti. Mungkin hal tersulit di sini adalah kita perlu mengirim semua data artikel kembali ke API untuk memperbarui dan pekerjaan multi-pengguna tidak dapat diimplementasikan. Sebagai contoh, editor dapat mengirim data 5 detik yang lebih lama dan menimpa perbaikan yang dilakukan beberapa jurnalis lain 2 detik yang lalu dan tidak ada cara yang bisa saya jelaskan kepada klien ini karena mereka yang mempublikasikan artikel sama sekali tidak terhubung dengan memperbarui konten.

2) Membuat sumber daya baru juga bisa menjadi opsi, api / article- {action} / id, tapi kemudian sumber daya yang dikembalikan tidak akan berupa artikel- {action} tetapi artikel yang saya tidak yakin apakah ini tepat. Juga di kelas kode sisi server artikel menangani pekerjaan yang sebenarnya pada kedua sumber daya dan saya tidak yakin apakah ini bertentangan dengan pemikiran RESTfull

Ada saran yang disambut ..

Miro Svrtan
sumber
Sangat sah untuk memiliki 'tindakan' menjadi bagian dari URI ISTIRAHAT - jika mereka menyatakan tindakan / algoritma yang akan dilakukan. Ada apa dengan ini api/article?action=publish? Parameter kueri dimaksudkan untuk kasus-kasus seperti itu di mana keadaan sumber daya bergantung pada 'algoritma' (atau tindakan) yang Anda sebutkan. Misalnya api/articles?sort=ascvalid
PhD
1
Saya sarankan Anda memeriksa artikel ini , yang dapat menginspirasi Anda dengan solusi yang lebih tenang.
Benjol
Salah satu masalah yang saya lihat dengan api / artikel? Action = publish adalah bahwa dalam aplikasi RESTfull harus mengirim SEMUA data artikel untuk penerbitan sementara saya lebih suka melakukan ini saja: api / article / 4545 / publish / tanpa tambahan apa pun
Miro Svrtan
2
Sumber yang bagus tentang masalah ini dan banyak lagi: REST API Design - Resource Modeling
Izhaki
@ Phd: sementara itu legal dalam protokol, URI umumnya harus menjadi kata benda dan bukan kata kerja. Memiliki kata kerja di URI biasanya merupakan tanda desain REST yang buruk.
Lie Ryan

Jawaban:

49

Saya menemukan praktik yang dijelaskan di sini sangat membantu:

Bagaimana dengan tindakan yang tidak sesuai dengan dunia operasi CRUD?

Di sinilah hal-hal bisa menjadi kabur. Ada sejumlah pendekatan:

  1. Merestrukturisasi tindakan agar tampak seperti bidang sumber daya. Ini berfungsi jika tindakan tidak mengambil parameter. Misalnya tindakan mengaktifkan dapat dipetakan ke activatedbidang boolean dan diperbarui melalui PATCH ke sumber daya.
  2. Perlakukan itu seperti sub-sumber daya dengan prinsip RESTful. Sebagai contoh, API GitHub ini memungkinkan Anda membintangi inti sebuah dengan PUT /gists/:id/stardan menghapus bintang dengan DELETE /gists/:id/star.
  3. Terkadang Anda benar-benar tidak memiliki cara untuk memetakan tindakan ke struktur RESTful yang masuk akal. Misalnya, pencarian multi-sumber daya tidak benar-benar masuk akal untuk diterapkan pada titik akhir sumber daya tertentu. Dalam hal ini, /searchakan lebih masuk akal meskipun itu bukan sumber daya. Ini OK - lakukan saja apa yang benar dari perspektif konsumen API dan pastikan itu didokumentasikan dengan jelas untuk menghindari kebingungan.
Tim
sumber
Saya memilih pendekatan 2. Walaupun mungkin terlihat seperti sumber daya buatan yang canggung untuk penelepon API, pada kenyataannya mereka tidak memiliki pengetahuan apa yang terjadi di server. Jika saya menelepon POST /article/123/deactivationsuntuk membuat permintaan penonaktifan baru untuk artikel 123, server mungkin tidak hanya menonaktifkan sumber daya yang diminta, tetapi sebenarnya menyimpan permintaan penonaktifan saya sehingga saya dapat mengambil statusnya nanti.
JustAMartin
2
mengapa PUT /gists/:id/star tidak POST /gists/:id/star?
Filip Bartuzi
9
@FilipBartuzi Karena PUT idempoten - yaitu, tidak peduli berapa kali Anda melakukan tindakan dengan parameter yang sama, hasilnya selalu sama (misalnya jika Anda menyalakan lampu dari mati ke hidup, itu berubah. Jika Anda mencoba untuk menghidupkan nyalakan lagi, tidak ada yang berubah - ini sudah menyala). POST bukan idempoten - yaitu, setiap kali Anda melakukan suatu tindakan, bahkan dengan parameter yang sama, tindakan tersebut memiliki hasil yang berbeda (misalnya jika Anda mengirim surat kepada seseorang, orang itu mendapat surat. Jika Anda mengirim surat yang sama kepada orang yang sama, mereka sekarang memiliki 2 huruf).
Raphael
9

Operasi yang menghasilkan perubahan status dan perilaku utama di sisi server seperti tindakan "terbitkan" yang Anda gambarkan sulit untuk dimodelkan secara eksplisit di REST. Solusi yang sering saya lihat adalah menggerakkan perilaku rumit seperti itu secara implisit melalui data.

Pertimbangkan memesan barang melalui API REST yang diekspos oleh pedagang online. Memesan adalah operasi yang kompleks. Beberapa produk akan dikemas dan dikirim, akun Anda akan dikenai biaya, dan Anda akan mendapatkan tanda terima. Anda dapat membatalkan pesanan Anda untuk jangka waktu terbatas dan tentu saja ada jaminan uang kembali penuh yang memungkinkan Anda mengirim kembali produk untuk pengembalian uang.

Alih-alih operasi pembelian yang kompleks, API semacam itu memungkinkan Anda membuat sumber daya baru, pesanan pembelian. Pada awalnya Anda dapat melakukan modifikasi apa pun yang Anda inginkan: menambah atau menghapus produk, mengubah alamat pengiriman, memilih opsi pembayaran lain, atau membatalkan pesanan Anda sekaligus. Anda dapat melakukan semua ini karena Anda belum membeli apa pun, Anda hanya memanipulasi beberapa data di server.

Setelah pesanan pembelian Anda selesai dan masa tenggang Anda berlalu, server mengunci pesanan Anda untuk mencegah perubahan lebih lanjut. Hanya pada saat ini urutan operasi yang rumit dimulai, tetapi Anda tidak dapat mengendalikannya secara langsung, hanya secara tidak langsung melalui data yang Anda tempatkan sebelumnya ke dalam pesanan pembelian.

Berdasarkan uraian Anda, "terbitkan" dapat diterapkan dengan cara ini. Alih-alih mengekspos operasi, Anda menempatkan salinan konsep yang telah Anda ulas dan ingin menerbitkan sebagai sumber daya baru di bawah / terbitkan. Ini menjamin setiap pembaruan berikutnya dari draft tidak akan dipublikasikan bahkan jika operasi penerbitan itu sendiri selesai beberapa jam kemudian.

Ferenc Mihaly
sumber
Gagasan mengubah seluruh sumber daya dari artikel yang tidak diterbitkan menjadi konsep akan cocok dengan kasus ini tetapi tidak akan cocok dengan semua tindakan lain yang ada pada sumber daya secara umum. Apakah REST seharusnya menanganinya? Mungkin saya menyalahgunakannya dan seharusnya hanya menggunakannya sebagai CRUD dan tidak lebih, seperti query SQL di mana saya tidak berharap ada logika di dalam query itu sendiri.
Miro Svrtan
Kenapa saya menanyakan semua ini? Yah sejak beberapa waktu yang lalu aplikasi web mulai multiplatformed dan saya lebih suka menyimpan banyak logika bisnis di server sejak memperbarui logika bisnis di iOS, Android, web, desktop, atau apa pun platform lain yang terlintas dalam pikiran semakin mustahil untuk dilakukan cepat dan saya ingin menghindari semua masalah kompatibilitas ke belakang saat mengubah sebagian kecil BL.
Miro Svrtan
2
Saya pikir REST dapat menangani logika bisnis dengan baik, tetapi tidak sesuai untuk mengekspos logika bisnis yang ada tanpa memikirkan REST. Inilah sebabnya mengapa banyak perusahaan seperti Microsoft, SAP, dan lainnya sering hanya memaparkan data dengan operasi CRUD, seperti yang Anda katakan. Lihat iklan Open Data Protocol untuk melihat bagaimana mereka melakukannya.
Ferenc Mihaly
7

kita perlu mengirim semua data artikel kembali ke API untuk memperbarui dan pekerjaan multi-pengguna tidak dapat dilaksanakan. Misalnya, editor dapat mengirim data 5 detik yang lebih lama dan menimpa perbaikan yang dilakukan beberapa jurnalis lain 2 detik yang lalu dan tidak ada cara yang bisa saya jelaskan kepada klien ini karena mereka yang mempublikasikan sebuah artikel sama sekali tidak terhubung dengan memperbarui konten.

Hal semacam ini merupakan tantangan, apa pun yang Anda lakukan, ini adalah masalah yang sangat mirip dengan kontrol sumber terdistribusi (mercurial, git, dll.), Dan solusinya, yang dieja dalam HTTP / ReST, terlihat sedikit mirip.

Andaikata Anda memiliki dua pengguna, Alice dan Bob, keduanya sedang mengerjakan /articles/lunch. (untuk kejelasan, responsnya berani)

Pertama, alice membuat artikel.

PUT /articles/lunch HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

301 Moved Permanently
Location: /articles/lunch/1 

Server tidak membuat sumber daya, karena tidak ada "versi" yang terlampir pada permintaan, (dengan asumsi pengidentifikasi /articles/{id}/{version}. Untuk melakukan pembuatan, Alice dialihkan ke url artikel / versi yang akan dibuatnya. Pengguna Alice agen kemudian akan mengajukan kembali permintaan di alamat baru.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

201 Created

Dan sekarang artikelnya sudah dibuat. selanjutnya, bob melihat artikel:

GET /articles/lunch HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

301 Moved Permanently
Location: /articles/lunch/1 

Bob melihat ke sana:

GET /articles/lunch/1 HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

200 Ok
Content-Type: text/plain

Hey Bob, what do you want for lunch today?

Dia memutuskan untuk menambahkan kembaliannya sendiri.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

301 Moved Permanently
Location: /articles/lunch/2

Seperti halnya Alice, Bob dialihkan ke tempat ia akan membuat versi baru.

PUT /articles/lunch/2 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

201 Created

Akhirnya, Alice memutuskan untuk menambah artikelnya sendiri:

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?
I was thinking about getting Sushi.

409 Conflict
Location: /articles/lunch/3
Content-Type: text/diff

---/articles/lunch/2
+++/articles/lunch/3
@@ 1,2 1,2 @@
 Hey Bob, what do you want for lunch today?
-Does pizza sound good to you, Alice?
+I was thinking about getting Sushi.

Alih-alih diarahkan kembali seperti biasa, kode status yang berbeda dikembalikan ke klien 409,, yang memberi tahu Alice bahwa versi yang ia coba cabut telah bercabang. Sumber daya baru dibuat anyways (seperti yang ditunjukkan oleh Locationheader), dan perbedaan antara keduanya termasuk dalam badan respons. Alice sekarang tahu bahwa permintaan yang baru saja dibuatnya perlu digabungkan.


Semua pengalihan ini terkait dengan semantik PUT, yang mengharuskan sumber daya baru dibuat persis di tempat yang diminta oleh garis permintaan. ini juga bisa menghemat siklus permintaan menggunakan POST, tetapi kemudian nomor versi harus dikodekan dalam permintaan oleh sihir lain, yang tampaknya kurang jelas bagi saya untuk tujuan ilustrasi, tetapi mungkin masih akan lebih disukai dalam API nyata untuk meminimalkan siklus permintaan / respons.

SingleNegationElimination
sumber
1
Versoning bukan masalah di sini, saya hanya mengatakan itu sebagai contoh masalah yang mungkin jika menggunakan artikel sebagai sumber daya untuk tindakan penerbitan
Miro Svrtan
3

Berikut adalah contoh lain yang tidak berurusan dengan konten dokumen tetapi lebih dengan keadaan sementara. (Saya menemukan versi - mengingat bahwa, secara umum, setiap versi dapat menjadi sumber daya baru - semacam masalah mudah.)

Katakanlah saya ingin mengekspos layanan yang berjalan pada mesin melalui REST sehingga dapat dihentikan, dimulai, dimulai ulang, dan sebagainya.

Apa pendekatan yang paling tenang di sini? POST / service? Command = restart, misalnya? Atau POST / layanan / negara dengan tubuh, katakanlah, 'berjalan'?

Akan menyenangkan untuk menyusun praktik terbaik di sini dan apakah REST adalah pendekatan yang tepat untuk situasi seperti ini.

Kedua, mari kita asumsikan saya ingin menggerakkan beberapa tindakan dari layanan yang tidak mempengaruhi keadaannya sendiri, melainkan memicu efek samping. Misalnya, layanan mailer yang mengirimkan laporan, dibuat pada saat panggilan, ke sekelompok alamat email.

GET / laporan mungkin merupakan cara untuk mendapatkan salinan laporan sendiri; tetapi bagaimana jika kita ingin mendorong ke sisi server tindakan lebih lanjut seperti mengirim email seperti yang saya katakan di atas. Atau menulis ke database.

Kasus-kasus ini menari di sekitar pembagian aksi sumber daya, dan saya melihat cara-cara menangani mereka dengan cara yang berorientasi REST, tetapi terus terang rasanya seperti sedikit peretasan untuk melakukannya. Mungkin pertanyaan kuncinya adalah apakah REST API harus mendukung efek samping secara umum.

AMD
sumber
2

REST berorientasi pada data dan oleh karena itu sumber daya berfungsi paling baik sebagai "hal-hal" bukan tindakan. Semantik implisit dari metode http; DAPATKAN, PUT, HAPUS, dll berfungsi untuk memperkuat orientasi. POST tentu saja, merupakan pengecualian.

Sumber daya dapat berupa campuran data yaitu. konten artikel; dan metadata yaitu. diterbitkan, dikunci, revisi. Ada banyak cara lain yang memungkinkan untuk mengiris data, tetapi Anda harus melihat seperti apa aliran data pertama-tama untuk menentukan yang paling optimal (jika ada). Misalnya, revisi harus menjadi sumber daya mereka sendiri di bawah artikel seperti yang disarankan TokenMacGuy.

Mengenai implementasi saya mungkin akan melakukan sesuatu seperti apa yang disarankan TockenMacGuy. Saya juga akan menambahkan bidang metadata pada artikel, bukan revisi, seperti 'dikunci' dan 'diterbitkan'.

dietbuddha
sumber
1

Jangan menganggapnya secara langsung memanipulasi keadaan artikel. Alih-alih, Anda memasukkan perintah perubahan yang meminta artikel dibuat.

Anda dapat memodelkan menempatkan dalam urutan perubahan sebagai membuat sumber daya perubahan urutan baru (POST). Ada banyak keuntungan. Misalnya, Anda dapat menentukan tanggal dan waktu di masa mendatang ketika artikel tersebut harus diterbitkan sebagai bagian dari urutan perubahan, dan biarkan server khawatir tentang bagaimana itu diterapkan.

Jika penerbitan bukan proses instan, Anda tidak perlu menunggu sampai selesai sebelum kembali ke klien. Anda hanya mengakui bahwa urutan perubahan telah dibuat dan mengembalikan ID urutan perubahan. Anda kemudian dapat menggunakan URL yang sesuai dengan urutan perubahan itu untuk berbagi status urutan perubahan.

Wawasan utama bagi saya adalah mengenali metafora urutan perubahan ini hanyalah cara lain untuk menggambarkan pemrograman berorientasi objek. Alih-alih sumber daya, kami menyebutnya objek. Alih-alih mengubah pesanan, kami menyebutnya pesan. Salah satu cara untuk mengirim pesan dari A ke B di OO adalah memiliki metode panggilan A pada B. Cara lain untuk melakukannya, terutama ketika A dan B di komputer yang berbeda, adalah untuk memiliki A membuat objek baru, M, dan kirimkan ke B. REST hanya memformalkan proses itu.

Patrick McElhaney
sumber
Saya benar-benar akan mempertimbangkan ini lebih dekat ke model Aktor daripada OO. Mempertimbangkan permintaan REST sebagai Pesan (yang ada) menyelaraskannya dengan sangat rapi ke Pengadaan Acara untuk mengelola negara. REST dengan rapi membagi interaksinya di sepanjang jalur CQRS. GET pesan adalah Query, POST, PUT, PATCH, DELETE jatuh ke area Command.
WillD
0

Jika saya memahami Anda dengan benar, saya pikir apa yang Anda miliki lebih merupakan masalah penentuan 'aturan bisnis' daripada masalah teknis.

Fakta bahwa suatu artikel dapat ditimpa dapat diselesaikan dengan memperkenalkan tingkat otorisasi di mana pengguna senior dapat mengganti versi pengguna junior. Juga dengan memperkenalkan versi serta kolom untuk menangkap keadaan artikel (misalnya 'dalam pengembangan', 'final' , dll.), Anda bisa mengatasinya. Anda juga dapat memberi pengguna kemampuan untuk memilih versi yang diberikan baik dengan kombinasi waktu pengiriman dan dengan nomor versi.

Dalam semua kasus di atas, layanan Anda perlu menerapkan aturan bisnis yang Anda tetapkan. Jadi Anda dapat memanggil layanan dengan parameter: userid, artikel, versi, tindakan (di mana versi ini opsional, sekali lagi ini tergantung pada aturan bisnis Anda).

Tidak mungkin
sumber
Saya tidak percaya bahwa ini adalah aturan bisnis tetapi hanya aturan teknis. Gagasan dengan menambahkan versi adalah ide yang bagus untuk membantu mengatasi aturan, tetapi masih tidak menyelesaikan situasi bahwa memperbarui konten dan menerbitkan konten bukan tindakan terkait.
Miro Svrtan