Menerapkan pola perintah di API RESTful

12

Saya sedang dalam proses mendesain API HTTP, semoga membuatnya setenang mungkin.

Ada beberapa tindakan yang fungsionalitasnya tersebar di beberapa sumber daya, dan terkadang perlu dibatalkan.

Saya berpikir sendiri, ini terdengar seperti pola perintah, tetapi bagaimana saya bisa memodelkannya menjadi sumber daya?

Saya akan memperkenalkan sumber daya baru bernama XXAction, seperti DepositAction, yang akan dibuat melalui sesuatu seperti ini

POST /card/{card-id}/account/{account-id}/Deposit
AmountToDeposit=100, different parameters...

ini sebenarnya akan membuat DepositAction baru dan mengaktifkan metode Do / Execute-nya. Dalam hal ini, mengembalikan status HTTP Dibuat 201 berarti tindakan telah berhasil dilaksanakan.

Nanti jika klien ingin melihat detail tindakan yang dia bisa

GET /action/{action-id}

Pembaruan / PUT saya kira harus diblokir, karena tidak relevan di sini.

Dan untuk membatalkan aksinya, saya berpikir untuk menggunakan

DELETE /action/{action-id}

yang sebenarnya akan memanggil metode Undo objek yang relevan, dan ubah statusnya.

Katakanlah saya senang dengan hanya satu Do-Undo, saya tidak perlu Redo.

Apakah pendekatan ini oke?

Apakah ada jebakan, alasan untuk tidak menggunakannya?

Apakah ini dipahami dari POV klien?

Mithir
sumber
Jawaban singkat, itu bukan REST.
Evan Plaice
3
@EvanPlaice peduli untuk menjelaskannya? itulah pertanyaannya.
Mithir
1
Saya akan menguraikan jawaban tetapi jawaban Gary sudah mencakup sebagian besar / semua yang saya tambahkan. Saya mengatakan itu tidak beristirahat karena URI hanya seharusnya mewakili sumber daya (yaitu bukan tindakan). Tindakan ditangani melalui GET / POST / PUT / DELETE / HEAD. Pikirkan REST sebagai antarmuka OOP. Tujuannya adalah membuat API sesuai dengan pola umum dan memisahkannya dari implementasi detail spesifik mungkin.
Evan Plaice
1
@ EvanPlaice Ok saya mengerti, terima kasih. Saya pikir ini membingungkan di sini karena Deposit dapat dianggap sebagai kata benda dan kata kerja ...
Mithir
Dalam hal ini URI harus mewakili transaksi di mana mendebit (mengambil uang) dan mengkredit (memberi uang) adalah tindakan yang dilakukan melalui permintaan POST. POST digunakan untuk keduanya karena setiap kali uang dipindahkan di kedua arah itu merupakan transaksi baru yang sedang dibuat. Dalam kasus spesifik Anda, transaksi terjadi pada akun pemegang kartu sehingga nomor akun kartu itu adalah URI sumber daya.
Evan Plaice

Jawaban:

13

Anda menambahkan lapisan abstraksi yang membingungkan

API Anda dimulai dengan sangat bersih dan sederhana. HTTP POST membuat sumber daya Deposit baru dengan parameter yang diberikan. Kemudian Anda keluar dari jalur dengan memperkenalkan gagasan "tindakan" yang merupakan detail implementasi daripada bagian inti dari API.

Sebagai alternatif pertimbangkan percakapan HTTP ini ...

POST / kartu / {card-id} / akun / {akun-id} / Setoran

AmountToDeposit = 100, parameter berbeda ...

201 DICIPTAKAN

Lokasi = / kartu / 123 / akun / 456 / Setoran / 789

Sekarang Anda ingin membatalkan operasi ini (secara teknis ini tidak boleh diizinkan dalam sistem akuntansi yang seimbang tapi apa hei):

HAPUS / kartu / 123 / akun / 456 / Setoran / 789

204 TANPA KONTEN

Konsumen API tahu bahwa mereka berurusan dengan sumber daya Deposit dan dapat menentukan operasi apa yang diizinkan di atasnya (biasanya melalui OPSI dalam HTTP).

Meskipun implementasi operasi penghapusan dilakukan melalui "tindakan" hari ini, tidak ada jaminan bahwa ketika Anda memigrasi sistem ini dari, katakanlah, C # ke Haskell dan pertahankan ujung depan bahwa konsep sekunder "tindakan" akan terus menambah nilai. , sedangkan konsep utama dari Deposit tentu saja.

Edit untuk mencakup alternatif untuk HAPUS dan Setoran

Untuk menghindari operasi penghapusan, tetapi masih secara efektif menghapus Setoran Anda harus melakukan yang berikut (menggunakan Transaksi generik untuk memungkinkan Setoran dan Penarikan):

POST / kartu / {card-id} / akun / {akun-id} / Transaksi

Jumlah = -100 , parameter berbeda ...

201 DICIPTAKAN

Lokasi = / kartu / 123 / akun / 456 / Transasi / 790

Sumber daya Transaksi baru dibuat yang memiliki jumlah yang persis berlawanan (-100). Ini memiliki efek menyeimbangkan akun kembali ke 0, meniadakan Transaksi asli.

Anda mungkin mempertimbangkan untuk membuat titik akhir "utilitas"

POST / kartu / {card-id} / akun / {akun-id} / Transaksi / 789 / Undo <- BUR!

untuk mendapatkan efek yang sama. Namun, ini mematahkan semantik URI sebagai pengidentifikasi dengan memperkenalkan kata kerja. Anda lebih baik tetap berpegang pada kata benda di pengidentifikasi dan menjaga operasi dibatasi ke kata kerja HTTP. Dengan begitu Anda dapat dengan mudah membuat permalink dari pengidentifikasi dan menggunakannya untuk GET dan sebagainya.

Gary Rowe
sumber
3
+1 "secara teknis ini seharusnya tidak diperbolehkan dalam sistem akuntansi yang seimbang". Seseorang tahu cara menghitung kacang. Pernyataan itu benar sekali, cara untuk membalikkan adalah dengan membuat transaksi lain dengan mengkredit kembali dana tersebut. Entri buku besar harus selalu dianggap tidak berubah dan permanen setelah transaksi selesai.
Evan Plaice
Jadi, jika saya mengubah, dalam pertanyaan saya, alih-alih Hapus / tindakan / ... ke Hapus / setoran / ... apakah boleh?
Mithir
2
@Mithir saya menjelaskan aturan akuntansi. Dalam sistem pembukuan entri ganda standar Anda tidak pernah menghapus transaksi. Sejarah yang pernah dilakukan dianggap tidak dapat diubah untuk menjaga orang tetap jujur. Dalam kasus Anda, Anda masih bisa menggunakan tindakan DELETE tetapi di back-end (ex database buku besar umum) Anda akan menambahkan transaksi lain yang mewakili pengkreditan (yaitu memberikan kembali) uang kembali ke pengguna. Saya bukan penghitung kacang (yaitu akuntan) tetapi ini adalah salah satu praktik standar yang diajarkan dalam kursus "Prinsip Akuntansi I".
Evan Plaice
2
(lanjutan) Log basis data menggunakan transaksi dengan cara yang serupa. Itu sebabnya mungkin untuk mereplikasi dan / atau membangun kembali dataset hanya menggunakan log. Selama transaksi diputar ulang secara kronologis, harus dimungkinkan untuk membangun kembali dataset dari titik mana pun dalam sejarahnya. Menghapus mutabilitas dari persamaan memastikan konsistensi.
Evan Plaice
1
Cukup adil cukup ganti namanya menjadi Transaction.
Gary Rowe
1

Alasan utama untuk keberadaan REST adalah ketahanan terhadap kesalahan jaringan. Untuk itu semua operasi harus idempoten .

Pendekatan dasar tampaknya masuk akal, tetapi cara Anda menggambarkan DepositActionpenciptaan tidak terdengar idempoten, yang harus diperbaiki. Dengan meminta klien memberikan ID unik yang akan digunakan untuk mendeteksi permintaan duplikat. Jadi ciptaan akan berubah menjadi

PUT /card/{card-id}/account/{account-id}/Deposit/{action-id}
AmountToDeposit=100, different parameters...

Jika PUT lain untuk URL yang sama dibuat dengan konten yang sama seperti sebelumnya, responsnya tetap harus 201 createdjika kontennya sama dan kesalahan jika kontennya berbeda. Ini memungkinkan klien untuk mengirimkan kembali permintaan ketika gagal, karena klien tidak dapat memastikan apakah permintaan atau responsnya hilang.

Lebih masuk akal untuk menggunakan PUT, karena itu hanya menulis sumber daya dan idempoten, tetapi menggunakan POST tidak akan benar-benar menyebabkan masalah juga.

Untuk melihat rincian transaksi, klien akan GETmemiliki URL yang sama, yaitu

GET /card/{card-id}/account/{account-id}/Deposit/{action-id}

dan untuk membatalkannya, ia dapat HAPUS. Tetapi jika itu benar-benar ada hubungannya dengan uang seperti yang disarankan sampel, saya akan menyarankan PUTting dengan menambahkan "dibatalkan" bendera sebagai gantinya untuk akuntabilitas (bahwa masih ada jejak transaksi dibuat dan dibatalkan).

Sekarang Anda harus memilih metode untuk membuat id unik. Anda memiliki beberapa opsi:

  1. Keluarkan awalan khusus klien sebelumnya dalam pertukaran yang harus disertakan.
  2. Tambahkan permintaan POST khusus untuk mendapatkan ID unik kosong dari server. Permintaan ini tidak harus idempoten (dan tidak bisa, sungguh), karena ID yang tidak digunakan tidak benar-benar menyebabkan masalah.
  3. Cukup gunakan UUID. Semua orang menggunakannya dan tampaknya tidak ada masalah dengan yang berbasis MAC maupun yang acak.
Jan Hudec
sumber
2
Dari Yang saya tahu, POST bukan idempoten. en.wikipedia.org/wiki/POST_(HTTP)#Affecting_server_state
Mithir
@Mithir: POST tidak dianggap idempoten; masih bisa. Tetapi memang benar bahwa karena semua operasi REST seharusnya idempoten, POST pada dasarnya tidak memiliki tempat di REST.
Jan Hudec
1
Saya bingung ... konten yang saya baca dan implementasi yang saya kenal (ServiceStack, ASP.NET Web API), semua menunjukkan bahwa POST mendapat tempat di REST.
Mithir
3
Dalam REST idempotence ditugaskan ke sumber daya, bukan protokol atau kode responsnya. Dengan demikian, dalam REST melalui HTTP metode GET, PUT, DELETE, PATCH dan sebagainya dianggap idempoten meskipun kode responsnya dapat bervariasi untuk panggilan berikutnya. POST idempoten dalam arti bahwa setiap panggilan menciptakan sumber daya baru. Lihat Fielding. Tidak masalah menggunakan POST .
Gary Rowe
1
Operasi yang bukan idempoten diizinkan dalam keadaan diam. Pernyataan itu salah besar.
Andy