การนำรูปแบบคำสั่งไปใช้ใน RESTful API


12

ฉันอยู่ในขั้นตอนการออกแบบ HTTP API หวังว่าจะทำให้ RESTful ที่สุดเท่าที่จะทำได้

มีการกระทำบางอย่างที่ฟังก์ชันการทำงานกระจายไปทั่วทรัพยากรบางอย่างและบางครั้งจำเป็นต้องเลิกทำ

ฉันคิดกับตัวเองเสียงนี้ดูเหมือนรูปแบบคำสั่ง แต่ฉันจะสร้างแบบจำลองเป็นทรัพยากรได้อย่างไร

ฉันจะแนะนำทรัพยากรใหม่ชื่อ XXAction เช่น DepositAction ซึ่งจะถูกสร้างขึ้นผ่านบางสิ่งเช่นนี้

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

สิ่งนี้จะสร้าง DepositAction ใหม่และเปิดใช้งานวิธีการ Do / Execute ในกรณีนี้การส่งคืนสถานะ HTTP ที่สร้าง 201 หมายถึงการดำเนินการถูกดำเนินการสำเร็จ

หลังจากนั้นหากลูกค้าต้องการดูรายละเอียดการกระทำที่เขาสามารถทำได้

GET /action/{action-id}

ควรปิดกั้นการอัปเดต / วางเพราะฉันไม่เกี่ยวข้องที่นี่

และเพื่อเลิกทำการกระทำฉันคิดถึงการใช้

DELETE /action/{action-id}

ซึ่งจริงๆแล้วจะเรียกวิธีการเลิกทำของวัตถุที่เกี่ยวข้องและเปลี่ยนสถานะเป็น

สมมติว่าฉันมีความสุขกับสิ่งเดียวที่ต้องเลิกทำฉันไม่จำเป็นต้องทำซ้ำ

วิธีนี้ใช้ได้หรือไม่

มีข้อผิดพลาดใด ๆ เหตุผลที่จะไม่ใช้มัน?

สิ่งนี้เป็นที่เข้าใจจาก POV ของลูกค้าหรือไม่


คำตอบสั้น ๆ นั่นไม่ใช่ REST
Evan Plaice

3
@EvanPlaice สนใจที่จะอธิบายรายละเอียดเกี่ยวกับเรื่องนี้หรือไม่? นั่นคือคำถาม
มิธาร์

1
ฉันจะอธิบายอย่างละเอียดในคำตอบ แต่คำตอบของ Gary ครอบคลุมมากที่สุด / ทั้งหมดที่ฉันเพิ่ม ฉันบอกว่ามันไม่ได้พักเพราะ URIs ควรจะเป็นตัวแทนของทรัพยากรเท่านั้น (เช่นไม่ใช่การกระทำ) การดำเนินการได้รับการจัดการผ่าน GET / POST / PUT / DELETE / HEAD คิดว่า REST เป็นอินเตอร์เฟส OOP เป้าหมายคือการทำให้ API เหมาะสมกับรูปแบบทั่วไปและแยกออกจากการใช้รายละเอียดเฉพาะที่เป็นไปได้
Evan Plaice

1
@EvanPlaice ตกลงฉันเข้าใจแล้วขอบคุณ ฉันคิดว่ามันทำให้เกิดความสับสนที่นี่เพราะเงินฝากอาจจะคิดว่าเป็นคำนามและคำกริยา ...
Mithir

ในกรณีนี้ URI ควรแสดงธุรกรรมที่การหักบัญชี (รับเงิน) และการให้เครดิต (การให้เงิน) เป็นการกระทำผ่านคำขอ POST POST ใช้สำหรับทั้งสองอย่างเนื่องจากแต่ละครั้งที่เงินถูกย้ายในทิศทางใดทิศทางหนึ่งจะหมายถึงธุรกรรมใหม่ที่กำลังสร้าง ในกรณีเฉพาะของคุณการทำธุรกรรมเกิดขึ้นในบัญชีของผู้ถือบัตรดังนั้นหมายเลขบัญชีของบัตรคือ URI ทรัพยากร
Evan Plaice

คำตอบ:


13

คุณกำลังเพิ่มเลเยอร์ของนามธรรมที่ทำให้เกิดความสับสน

API ของคุณเริ่มต้นอย่างสะอาดและเรียบง่าย HTTP POST สร้างทรัพยากรการฝากใหม่ที่มีพารามิเตอร์ที่กำหนด จากนั้นคุณก็ลงจากรางโดยแนะนำแนวคิดของ "การกระทำ" ที่มีรายละเอียดการนำไปปฏิบัติแทนที่จะเป็นส่วนหลักของ API

อีกทางเลือกหนึ่งพิจารณาการสนทนา HTTP นี้ ...

POST / card / {card-id} / บัญชี / {account-id} / เงินฝาก

AmountToDeposit = 100 พารามิเตอร์ต่าง ๆ ...

201 สร้างแล้ว

สถานที่ตั้ง = / บัตร / 123 / บัญชี / 456 / เงินฝาก / 789

ตอนนี้คุณต้องการยกเลิกการดำเนินการนี้ (ในทางเทคนิคแล้วสิ่งนี้ไม่ควรได้รับอนุญาตในระบบบัญชีที่สมดุล แต่มันคืออะไร):

ลบ / การ์ด / 123 / บัญชี / 456 / ฝาก / 789

204 ไม่มีเนื้อหา

ผู้ใช้ API รู้ว่าพวกเขากำลังจัดการกับทรัพยากรการฝากเงินและสามารถกำหนดได้ว่าจะอนุญาตให้ดำเนินการใดได้บ้าง (โดยทั่วไปผ่านทาง OPTIONS ใน HTTP)

แม้ว่าการดำเนินการลบจะดำเนินการผ่าน "การกระทำ" ในปัจจุบันไม่มีการรับประกันว่าเมื่อคุณย้ายระบบนี้จากพูดว่า C # ถึง Haskell และรักษาส่วนหน้าว่าแนวคิดรองของ "การกระทำ" จะยังคงเพิ่มมูลค่าต่อไป ในขณะที่แนวคิดหลักของการฝากเงินไม่แน่นอน

แก้ไขเพื่อให้ครอบคลุมทางเลือกในการลบและการฝากเงิน

เพื่อหลีกเลี่ยงการลบ แต่ยังคงมีประสิทธิภาพในการฝากเงินคุณควรทำสิ่งต่อไปนี้ (ใช้ธุรกรรมทั่วไปเพื่ออนุญาตให้ฝากและถอนเงิน):

POST / card / {card-id} / บัญชี / {account-id} / ทรานแซคชัน

จำนวน = -100พารามิเตอร์ต่าง ๆ ...

201 สร้างแล้ว

สถานที่ตั้ง = / บัตร / 123 / บัญชี / 456 / Transation / 790

ทรัพยากรธุรกรรมใหม่ถูกสร้างขึ้นซึ่งมีจำนวนตรงข้าม (-100) นี่คือผลกระทบของการปรับสมดุลบัญชีกลับเป็น 0 โดยไม่สนใจธุรกรรมเดิม

คุณอาจพิจารณาสร้างจุดปลาย "ยูทิลิตี้" เช่น

POST / card / {card-id} / บัญชี / {account-id} / Transaction / 789 / Undo <- BAD!

เพื่อให้ได้ผลเช่นเดียวกัน อย่างไรก็ตามสิ่งนี้จะแบ่งซีแมนทิกส์ของ URI ว่าเป็นตัวระบุโดยการใช้คำกริยา คุณดีกว่าไปเกาะติดกับคำนามในตัวระบุและทำให้การดำเนินการถูก จำกัด กับกริยา HTTP ด้วยวิธีนี้คุณสามารถสร้างลิงก์ได้อย่างง่ายดายจากตัวระบุและใช้สำหรับ GET และอื่น ๆ


3
+1 "เทคนิคไม่ควรอนุญาตในระบบบัญชีที่สมดุล" บางคนรู้วิธีนับถั่ว คำสั่งนั้นถูกต้องอย่างสมบูรณ์วิธีในการย้อนกลับคือการสร้างธุรกรรมอื่นที่ให้เครดิตเงินคืน รายการบัญชีแยกประเภททั่วไปควรได้รับการพิจารณาว่าไม่เปลี่ยนแปลงและถาวรเมื่อธุรกรรมเสร็จสมบูรณ์
Evan Plaice

ดังนั้นหากฉันเปลี่ยนคำถามของฉันแทนที่จะลบ / กระทำ / ... เพื่อลบ / ฝาก / ... มันใช้ได้ไหม?
มิธาร์

2
@Mithir ฉันอธิบายกฎการบัญชี ในระบบการทำบัญชีสองรายการมาตรฐานคุณจะไม่ลบธุรกรรม ประวัติการกระทำความผิดครั้งหนึ่งถือว่าไม่เปลี่ยนรูปเพื่อรักษาความซื่อสัตย์ของผู้คน ในกรณีของคุณคุณยังคงสามารถใช้การกระทำที่ถูกลบ แต่ในส่วนหลัง (ตารางฐานข้อมูลบัญชีแยกประเภททั่วไป) คุณจะต้องเพิ่มธุรกรรมอีกครั้งที่แสดงถึงการให้เครดิต (เช่นการคืนเงิน) ให้กับผู้ใช้ ฉันไม่มีตัวนับถั่ว (เช่นนักบัญชี) แต่เป็นหนึ่งในวิธีปฏิบัติมาตรฐานที่สอนในหลักสูตร "หลักการบัญชีฉัน"
Evan Plaice

2
(ต่อ) บันทึกฐานข้อมูลใช้ธุรกรรมในลักษณะเดียวกัน นั่นเป็นเหตุผลที่เป็นไปได้ที่จะทำซ้ำและ / หรือสร้างชุดข้อมูลใหม่โดยใช้เพียงบันทึก ตราบใดที่มีการทำธุรกรรมซ้ำตามลำดับเวลามันควรจะเป็นไปได้ที่จะสร้างชุดข้อมูลจากจุดใด ๆ ในประวัติศาสตร์ การถอดความไม่แน่นอนออกจากสมการทำให้มั่นใจได้ถึงความมั่นคง
Evan Plaice

1
ยุติธรรมพอเพียงแค่เปลี่ยนชื่อเป็นธุรกรรม
Gary Rowe

1

เหตุผลหลักสำหรับการมีอยู่ของ REST นั้นมีความยืดหยุ่นต่อข้อผิดพลาดของเครือข่าย ที่จบการดำเนินงานทั้งหมดควรจะidempotent

วิธีการพื้นฐานดูเหมือนสมเหตุสมผล แต่วิธีที่คุณอธิบายการDepositActionสร้างไม่ได้ฟังว่าเป็น idempotent ซึ่งควรได้รับการแก้ไข โดยให้ลูกค้าให้ ID ที่ไม่ซ้ำที่จะใช้ในการตรวจสอบคำขอที่ซ้ำกัน ดังนั้นการสร้างจะเปลี่ยนเป็น

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

หากใส่ PUT อื่นไปยัง URL เดียวกันด้วยเนื้อหาเดียวกันกับก่อนหน้านี้การตอบสนองควรจะยังคงอยู่201 createdถ้าเนื้อหาเหมือนกันและมีข้อผิดพลาดหากเนื้อหานั้นแตกต่าง สิ่งนี้จะช่วยให้ลูกค้าสามารถส่งคำร้องขอใหม่เมื่อล้มเหลวเนื่องจากลูกค้าไม่สามารถบอกได้ว่าคำขอหรือการตอบสนองนั้นหายไปหรือไม่

มันสมเหตุสมผลมากกว่าที่จะใช้ PUT เพราะมันเพิ่งเขียนทรัพยากรและ idempotent แต่การใช้ POST จะไม่ทำให้เกิดปัญหาใด ๆ

หากต้องการดูรายละเอียดธุรกรรมลูกค้าจะGETใช้ URL เดียวกันคือ

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

และเมื่อต้องการเลิกทำมันสามารถลบได้ แต่ถ้ามันมีอะไรเกี่ยวข้องกับเงินจริงตามตัวอย่างแนะนำผมแนะนำให้ใส่ด้วยการเพิ่มค่าสถานะ "ยกเลิก" แทนเพื่อความรับผิดชอบ (ซึ่งยังคงมีร่องรอยของธุรกรรมที่สร้างและยกเลิก)

ตอนนี้คุณต้องเลือกวิธีการสร้าง id ที่ไม่ซ้ำกัน คุณมีหลายทางเลือก:

  1. ออกคำนำหน้าเฉพาะลูกค้าก่อนหน้านี้ในการแลกเปลี่ยนที่จะต้องรวม
  2. เพิ่มคำขอ POST พิเศษเพื่อรับ ID เฉพาะที่ไม่ซ้ำกันจากเซิร์ฟเวอร์ คำขอนี้ไม่จำเป็นต้องเป็น idempotent (และไม่สามารถทำได้จริง ๆ ) เพราะ ID ที่ไม่ได้ใช้จะไม่ทำให้เกิดปัญหาใด ๆ
  3. เพียงใช้ UUID ทุกคนใช้พวกเขาและดูเหมือนว่าไม่มีใครมีปัญหาใด ๆ กับทั้งที่ใช้ MAC หรือแบบสุ่ม

2
จากสิ่งที่ฉันรู้ POST ไม่ใช่ idempotent en.wikipedia.org/wiki/POST_(HTTP)#Affecting_server_state
Mithir

@Mithir: POST ไม่ถือว่าเป็น idempotent มันยังสามารถ แต่เป็นความจริงที่ว่าเนื่องจากการดำเนินการ REST ทั้งหมดควรเป็น idempotent, POST จึงไม่มีตำแหน่งใน REST
Jan Hudec

1
ฉันสับสน ... เนื้อหาที่ฉันอ่านและการใช้งานที่มีอยู่ฉันคุ้นเคยกับ (ServiceStack, ASP.NET Web API) ทุกคนแนะนำว่า POST มีสถานที่ใน REST
Mithir

3
ใน REST idempotence ถูกกำหนดให้กับทรัพยากรไม่ใช่โปรโตคอลหรือรหัสตอบกลับ ดังนั้นใน REST ผ่าน HTTP เมธอด GET, PUT, DELETE, PATCH และอื่น ๆ จะถูกพิจารณาว่าเป็น idempotent แม้ว่ารหัสการตอบกลับอาจแตกต่างกันไปสำหรับการโทรครั้งต่อไป POST เป็น idempotent ในแง่ที่ว่าการเรียกทุกครั้งสร้างทรัพยากรใหม่ ดูฟีลดิงเป็นมันตกลงที่จะใช้ POST
Gary Rowe

1
อนุญาตให้ดำเนินการที่ไม่ได้ใช้ idempotent การยืนยันนั้นผิดพลาด
Andy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.