Merancang fungsi berdasarkan RESTful API

8

Tolong selesaikan argumen antara saya dan seorang teman.

Kami sedang merancang API produk. Entitas Produk kami terlihat seperti ini

{
    "Id": "",
    "ProductName": "",
    "StockQuantity": 0
}

Penjualan produk ditangani oleh pihak ke-3 dan mereka wajib memberi tahu kami dengan jumlah yang dibeli sehingga StockQuantitybidangnya dapat dikurangi.

Pendekatan saya:

PUT /api/Product/{Id}/ --data { "StockQuantity": "{NewStockQuantity}" }

Pihak ke-3 bertanggung jawab dengan menanyakan produk, membuat perhitungan berdasarkan StockQuantitykuantitas saat ini dan yang dibeli dan mengirimkan PUTpermintaan dengan nilai baru.

Teman saya tidak ingin pihak ke-3 melakukan perhitungan. Pendekatannya

PUT /api/Product/{Id}/DecreaseStock --data { "PurchasedQuantity": "{PurchasedQuantity}" }

Jadi kita bisa melakukan perhitungan dan memperbarui StockQuantity

Saya tidak ingin membuat titik akhir berbasis fungsi dan dia tidak ingin mempercayai pihak ketiga untuk membuat perhitungan.

Apa cara yang benar bagi kita untuk mendekati masalah ini?

Sefa Ümit Oray
sumber
Ingat bahwa PUT harus (dalam teori) idempoten. Opsi 1 cocok dengan semantik. Opsi 2 tidak mau. Jika menjadi REST compliant penting bagi Anda. Anda akan mencoba untuk menjaga panggilan PUT idempoten karena itu akan menyelamatkan Anda dari banyak sakit kepala. Sama untuk DELETE. Untuk operasi seperti perintah, jujur, saya akan memberikan kesempatan kepada Json atau XML RPC. Kedua strategi (REST dan RPC) dapat hidup bersama dalam API Web yang sama. Itu disampaikan dengan prinsip CQRS (perintah segregasi tanggung jawab permintaan :-)
Laiv

Jawaban:

19

Anda dapat membiarkan pihak ke-3 Anda mengirim penjualan ke produk Anda. Sebagai contoh:

POST /product/{id}/sale { "Quantity": 3 }

Saya setuju dengan poin Anda dan kolega Anda. Ini adalah logika bisnis, dan tidak boleh diserahkan kepada klien API, tetapi Anda juga harus menghindari "fungsi" sebagai titik akhir.

Terkadang menyelesaikan masalah seperti itu semudah menyebutnya secara berbeda, diakui tidak selalu.

Robert Bräutigam
sumber
2
Ini. Plus: sepertinya setiap penjualan juga membutuhkan objek pada basis data juga. Memiliki setiap penjualan sebagai objek terpisah di db memungkinkan penelusuran. Pikirkan, jika ada kesalahan dan kuantitas stok akhir salah dan perlu memperbaiki nilai. Jika Anda hanya memiliki satu kolom nilai akhir maka tidak banyak yang dapat Anda lakukan. Semoga ada log yang berguna dalam sistem untuk mencari tahu apa yang salah. Jika Anda memiliki objek penjualan dengan stempel waktu, nama pengguna, dan kemungkinan alamat IP yang terlampir, Anda memiliki kemampuan untuk menghapus catatan tertentu untuk memperbaiki data, dan untuk melacak dari mana pengguna / lokasi asalnya.
Ski
Terima kasih atas masukannya. Penjualan / Pemesanan adalah sumber daya tim lain, bukan tanggung jawab saya untuk menyimpan atau memprosesnya. Tahu ini, apakah membuat /saletitik akhir masih valid?
Sefa Ümit Oray
@ SefaÜmitOray: Titik akhir /saledan /product/{id}/salesepenuhnya independen dan fakta bahwa mereka memiliki nama yang sama tidak berarti bahwa mereka merujuk ke sumber daya yang sama.
Bart van Ingen Schenau
@ BartvanIngenSchenau Yang saya maksud adalah, saletidak ada dalam domain saya dan itu bukan bagian dari product. Apakah masih masuk akal untuk membuat /product/{id}/salesementara itu tidak mewakili sumber daya yang sebenarnya?
Sefa Ümit Oray
5
@ SefaÜmitOray Ini benar-benar valid jika itu mewakili sesuatu yang bermakna dalam konteks Anda. Itu tidak harus berarti hal yang sama seperti dalam konteks lain dan tidak harus menjadi apa pun yang secara langsung bertahan ke database. Domain! = Tabel Database, Sumber Daya! = Tabel Database.
Robert Bräutigam
3

Tidak ada alasan bahwa Anda juga tidak dapat melakukannya; atau keduanya.

Dalam konteks titik penjualan, melacak transaksi individu sangat masuk akal. Di sana, solusi Robert sangat masuk akal.

Dalam konteks stok / gudang, Anda tidak perlu melacak transaksi sebanyak "mengambil inventaris"; memiliki titik akhir yang memungkinkan klien untuk melaporkan tingkat stok mereka

Saya punya 10 unit. Saya punya 7 unit. Saya punya 3 unit. Saya punya 20 unit

sangat masuk akal.

Tingkat stok berubah karena alasan selain "penjualan"; hanya sesuatu yang perlu diingat.

Secara teori, level stok harus dapat dihitung dari perubahan; tetapi dalam beberapa domain itulah asumsi yang ingin Anda verifikasi . Anda ingin dapat menghitung tingkat stok dengan dua cara berbeda dan memeriksa perbedaan (alias "penyusutan").

Jadi saya tidak berpikir semantiknya jelas, berdasarkan konteks yang Anda berikan.

Adapun bagian HTTP; PUT [target-uri]masuk akal secara semantik ketika Anda mengganti satu representasi dokumen dengan yang lain. Ini adalah UPSERT- PUT kedua untuk sumber daya meminta untuk menimpa representasi yang ada.

PUT /sales { Quantity = 5 }
PUT /sales { Quantity = 2 }
PUT /sales { Quantity = 3 }

mengatakan bahwa jumlah unit yang terjual adalah 3, bukan 10.

PUT /sales/1 { Quantity = 5 }
PUT /sales/2 { Quantity = 2 }
PUT /sales/3 { Quantity = 3 }

10Seperti itulah bentuknya

PUT /sales { Quantity : [5] }
PUT /sales { Quantity : [5,2] }
PUT /sales { Quantity : [5,2,3] }

Itu cara lain untuk mengeja 10.

POST /sales { Quantity = 5 }
POST /sales { Quantity = 2 }
POST /sales { Quantity = 3 }

Sejauh menyangkut HTTP, ini juga dapat diterima. Namun, ini bukan pilihan tepat pada jaringan yang tidak dapat diandalkan karena pesan terkadang digandakan.

POST /sales { Quantity = 5 }
POST /sales { Quantity = 2 }
POST /sales { Quantity = 3 }
POST /sales { Quantity = 3 }

Apakah itu 13? atau 10?

PUT /sales/1 { Quantity = 5 }
PUT /sales/2 { Quantity = 2 }
PUT /sales/3 { Quantity = 3 }
PUT /sales/3 { Quantity = 3 }

Itu jelas 10

PUT /sales { Quantity : [5,2,3] }
PUT /sales { Quantity : [5,2,3] }

Itu jelas 10

PUT /sales/1 { Quantity = 5 }
PUT /sales/2 { Quantity = 2 }
PUT /sales/3 { Quantity = 3 }
PUT /sales/4 { Quantity = 3 }

Itu jelas 13

PUT /sales { Quantity : [5,2,3] }
PUT /sales { Quantity : [5,2,3,3] }

Itu jelas 13

POST /sales { TransactionId = 1 , Quantity = 5 }
POST /sales { TransactionId = 2 , Quantity = 2 }
POST /sales { TransactionId = 3 , Quantity = 3 }
POST /sales { TransactionId = 3 , Quantity = 3 }

10

POST /sales { TransactionId = 1 , Quantity = 5 }
POST /sales { TransactionId = 2 , Quantity = 2 }
POST /sales { TransactionId = 3 , Quantity = 3 }
POST /sales { TransactionId = 4 , Quantity = 3 }

13

(Agar adil, HTTP memang memiliki dukungan untuk permintaan bersyarat ; Anda dapat mengangkat beberapa metadata dari protokol khusus domain Anda ke header agnostik domain untuk menghilangkan beberapa ambiguitas - jika Anda dapat membujuk klien untuk bermain bersama).

Tentu saja, ada trade off - HTML tidak memiliki dukungan PUT asli; jika Anda bermaksud agar klien API Anda menjadi browser, maka Anda memerlukan protokol berdasarkan POST atau Anda memerlukan ekstensi kode-sesuai-permintaan untuk mengonversi pengiriman formulir dari POST ke PUT.

VoiceOfUnreason
sumber
1
Anda tidak harus melacak penjualan individual, hanya karena ada titik akhir untuk itu. Yaitu tidak perlu untuk dapat membuat daftar panggilan penjualan sebelumnya, hanya karena Anda dapat POST untuk itu. Namun Anda benar, bahwa mungkin ada kasus penggunaan lain (kami tidak tahu), dan Anda harus mendefinisikan panggilan idempoten baik dengan panggilan kondisional, atau dengan cara lain.
Robert Bräutigam
2

Ini sepertinya desain yang sangat buruk tidak peduli bagaimana Anda mengirisnya. Saya tidak akan pernah mempercayai pihak ketiga untuk memberi tahu saya inventaris saat ini kecuali saya telah mempekerjakan mereka untuk mengelola gudang saya.

Lebih jauh, pendekatan yang tampak fungsi sama sekali tidak tenang dan pasti akan menciptakan kekhawatiran di antara konsumen Anda.

Akhirnya, saya tidak bisa membayangkan skenario di mana satu-satunya hal yang Anda pedulikan tentang penjualan adalah inventaris yang dihasilkan yang tersisa setelah selesai.

Anda jauh lebih baik jika pihak ketiga memposting sumber daya Penjualan atau Faktur kepada Anda (dengan info seperti produk, jumlah, tanggal, metode pengiriman, info pelanggan, dll). Ini memungkinkan Anda benar-benar melakukan analisis nyata dan melacak apa yang Anda jual, kepada siapa, kapan, dll sehingga Anda dapat mengelola bisnis Anda.

Bahkan jika pihak ketiga Anda melakukan pemenuhan pesanan total, Anda akan ingin melacak penjualan untuk keperluan akuntansi dan demografi pelanggan jika tidak ada yang lain.

Paul
sumber
1

PUT / api / Produk / {Id} / --data {"StockQuantity": "{NewStockQuantity}"}

Jenis desain ini memiliki masalah besar yaitu jika Anda ingin memiliki lebih dari satu utas klien berjalan melawan API Anda, Anda akan terkena baca / tulis kotor. Yaitu, antara waktu klien menarik jumlah saat ini, dan menghitung nilai baru, klien lain dapat menarik nilai sebelumnya yang sama dan menghitung jawaban yang berbeda. Kuantitas yang Anda hasilkan adalah pembaruan mana saja yang terakhir tetapi tidak ada yang benar. Misalnya, jumlah Anda saat ini adalah 10. Klien A ingin menjual 5 item dan menarik jumlah saat ini. Pada saat yang sama, klien B ingin menjual 6 item dan menarik jumlah saat ini. Keduanya melihat 10 item dalam stok. A menghitung 5 item tersisa. Bmenghitung sisa 4. Keduanya memperbarui. Anda sekarang menampilkan 4 atau 5 item yang tersisa tergantung pada siapa yang terakhir direkam. Namun, Anda sebenarnya telah menjual lebih banyak barang yang sebenarnya Anda miliki. Yang lebih buruk adalah bahwa tidak ada cara mudah untuk berjalan dan melihat apa yang salah. Yang Anda miliki hanyalah dua yang salah PUTsdalam log Anda untuk dilihat.

Dalam sistem catatan dunia nyata apa pun, sekadar memiliki total saat ini tidak memadai. Pertimbangkan jika Anda pergi ke toko dan membeli sejumlah barang. Anda meminta tanda terima dan kasir hanya menyerahkan slip kepada Anda dengan jumlah total di atasnya. Bagaimana Anda menunjukkan bahwa jumlah itu benar dari tanda terima itu? Bagaimana Anda menunjukkan Anda membeli barang jika Anda ingin mengembalikan sesuatu?

Pendekatan teman Anda lebih baik tetapi saya akan menyarankan menambahkan id transaksi ke dalam campuran. Ini mengatasi kekhawatiran VoiceOfUnason yang sebenarnya menyebutkan tentang transaksi duplikat. Salah satu opsi adalah menyediakan POSToperasi untuk membuat transaksi baru dan kemudian PUTuntuk transaksi itu untuk mengonfirmasi. Pada titik konfirmasi Anda mengurangi stok total atau menolak permintaan karena tidak tersedia cukup.

JimmyJames
sumber
1

Karena penjualan ditangani oleh pihak ke-3, Anda harus memiliki kontrol atas inventaris produk Anda dengan tidak memperbolehkan mereka untuk memperbarui jumlah stok.

Untuk penggunaan internal, mis. Tujuan penghitungan stok, Anda dapat memiliki pendekatan Anda, yaitu PUT /api/Product/{Id}/ --data { "StockQuantity": "{NewStockQuantity}" }.

Untuk penggunaan eksternal, Anda harus membuat antarmuka terpisah, mis. /api/SalesOrder/Yang mengambil daftar produk dan jumlah, seperti:

POST /api/SalesOrder/ --data { [{"Id": 1, "Qty": 1}, {"Id": 2, "Qty": 3}] }

Berdasarkan dari SalesOrderpengiriman oleh pihak ke-3, jumlah setiap produk dapat diperbarui dan ditetapkan ke pesanan atau Anda dapat menolak pesanan jika tidak tersedia cukup produk.

Pemrosesan dan penghitungan stok adalah proses internal, pihak ke-3 hanya membutuhkan antarmuka sehingga mereka dapat meneruskan pesanan mereka ke inventaris. Pada dasarnya, itulah SalesOrdercara Penjualan, Keuangan, dan Gudang berkomunikasi untuk menyelesaikan penjualan.

imel96
sumber