ฉันควรใช้ PATCH หรือ PUT ใน REST API หรือไม่


274

ฉันต้องการออกแบบจุดปลายทางที่เหลือของฉันด้วยวิธีการที่เหมาะสมสำหรับสถานการณ์ต่อไปนี้

มีกลุ่ม แต่ละกลุ่มมีสถานะ กลุ่มสามารถเปิดใช้งานหรือปิดใช้งานโดยผู้ดูแลระบบ

ฉันควรจะออกแบบจุดสิ้นสุดของฉันเป็น

PUT /groups/api/v1/groups/{group id}/status/activate

หรือ

PATCH /groups/api/v1/groups/{group id}

with request body like 
{action:activate|deactivate}

1
ทั้งคู่ก็โอเค แต่อย่าลืมดูที่ RFC สำหรับรูปแบบ JSON PATCH ( tools.ietf.org/html/rfc6902 ) PATCH คาดว่าจะได้รับเอกสาร diff / patch บางอย่างสำหรับ payload (และ JSON แบบดิบไม่ใช่หนึ่งในนั้น)
Jørn Wildt

1
@ JørnWildtไม่ใส่จะเป็นตัวเลือกที่น่ากลัว คุณกำลังวางอะไรอยู่ PATCH เป็นเพียงตัวเลือกที่สมเหตุสมผล ในกรณีนี้คุณสามารถใช้รูปแบบแพทช์ที่แสดงในคำถามและใช้เมธอด PUT ตัวอย่าง PUT ผิดไป
thecoshman

3
ไม่มีอะไรผิดปกติในการเปิดเผยคุณสมบัติอย่างน้อยหนึ่งอย่างเป็นทรัพยากรแบบสแตนด์อโลนที่ลูกค้าสามารถรับและแก้ไขด้วย PUT แต่ใช่แล้ว URL ควรเป็น / groups / api / v1 / groups / {group id} / สถานะที่คุณสามารถ PUT "active" หรือ "inactive" หรือ GET เพื่ออ่านสถานะปัจจุบัน
Jørn Wildt

3
นี่คือคำอธิบายที่ดีของวิธี PATCH จริงๆควรจะใช้: williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot
Rishat

4
" activate" การก่อสร้างสงบไม่เพียงพอ คุณอาจพยายามอัปเดตเป็นstatus"ใช้งานอยู่" หรือ "ปิดใช้งาน" ในกรณีที่คุณสามารถแพทช์.../statusกับสตริง "ใช้งาน" หรือ "ปิดการใช้งาน" ในร่างกาย หรือถ้าคุณพยายามที่จะอัพเดทบูลีนที่status.activeคุณสามารถแพทช์.../status/activeกับบูลีนในร่างกาย
Augie Gardner

คำตอบ:


328

PATCHวิธีการเป็นทางเลือกที่ถูกต้องที่นี่ในขณะที่คุณกำลังอัปเดตเป็นทรัพยากรที่มีอยู่ - รหัสกลุ่ม PUTควรใช้หากคุณกำลังแทนที่ทรัพยากรทั้งหมด

ข้อมูลเพิ่มเติมเกี่ยวกับการปรับเปลี่ยนทรัพยากรบางส่วนที่มีอยู่ในRFC 5789 โดยเฉพาะPUTวิธีการอธิบายดังต่อไปนี้:

แอปพลิเคชั่นหลายตัวที่ขยาย Hypertext Transfer Protocol (HTTP) ต้องการคุณสมบัติเพื่อทำการแก้ไขบางส่วนของทรัพยากร วิธี HTTP PUT ที่มีอยู่อนุญาตให้เปลี่ยนเอกสารได้อย่างสมบูรณ์เท่านั้น ข้อเสนอนี้เพิ่มวิธี HTTP ใหม่ PATCH เพื่อแก้ไขทรัพยากร HTTP ที่มีอยู่


1
เพื่อความเป็นธรรมคุณสามารถใส่สตริง 'เปิดใช้งาน' หรือ 'ปิดใช้งาน' กับทรัพยากร เนื่องจากมี (สิ่งเดียว) ที่จะเป็นเพียงสิ่งเดียวที่จะสลับการแทนที่อย่างสมบูรณ์มันไม่ใช่เรื่องใหญ่อะไร และมันจะทำให้คำขอมีขนาดเล็กลง (ไม่มีนัยสำคัญ)
thecoshman

35
เป็นสิ่งสำคัญที่จะต้องทราบว่า RFC 5789 ยังอยู่ในช่วงข้อเสนอและยังไม่ได้รับการยอมรับอย่างเป็นทางการและขณะนี้ถูกตั้งค่าสถานะเป็น 'ไม่มีข้อมูล' 'แนวปฏิบัติที่ดีที่สุด' นี้ได้รับการถกเถียงกันอย่างมากและทางเทคนิค PATCH ยังไม่ได้เป็นส่วนหนึ่งของมาตรฐาน HTTP
fishpen0

4
เพียง 2 เซ็นต์ของฉันไม่กี่ปีต่อมา: คุณสามารถพิจารณาสถานะของตัวเองว่าเป็นทรัพยากรและถ้าเป็นเช่นนั้นการใช้ PUT เทียบกับ / สถานะจะเป็นการแทนที่ทรัพยากรสถานะทางเทคนิคที่จุดสิ้นสุดนั้น
Jono Stewart

3
ฉันกล้าที่จะโต้แย้งกับเอกสารแม้ว่าจะเป็น "RFC" รัฐเอกสารที่คุณควรใช้ PATCH เพื่อแก้ไขเพียงส่วนหนึ่งของทรัพยากร แต่มันละเว้นสิ่งสำคัญที่กำหนดวิธีการ PATCH เป็นวิธีที่ไม่ใช่ idempotent ทำไม? หากวิธีการ PUT ถูกสร้างขึ้นด้วยการอัพเดท / การแทนที่ของทรัพยากรทั้งหมดในใจแล้วทำไมไม่วิธีการ PATCH ที่สร้างขึ้นเป็นวิธี idempotent เช่น PUT ถ้ามันมีวัตถุประสงค์เพื่อเพียงแค่อัปเดตส่วนของทรัพยากร? สำหรับฉันแล้วมันดูแตกต่างจาก idempotency ของการอัปเดตเช่น "a = 5" (PUT) และ "a = a + 5" (PATCH) ทั้งสองสามารถอัปเดตทรัพยากรทั้งหมด
Mladen B.

179

Rในส่วนที่เหลือยืนสำหรับทรัพยากร

(ซึ่งไม่เป็นความจริงเพราะมันหมายถึงตัวแทน แต่มันเป็นเคล็ดลับที่ดีในการจดจำความสำคัญของทรัพยากรใน REST)

เกี่ยวกับPUT /groups/api/v1/groups/{group id}/status/activate: คุณไม่ได้อัปเดต "เปิดใช้งาน" "การเปิดใช้งาน" ไม่ใช่สิ่งที่มันเป็นคำกริยา คำกริยาไม่เคยเป็นแหล่งข้อมูลที่ดี กฎของหัวแม่มือ: ถ้าการกระทำคำกริยาเป็นใน URL มันอาจจะไม่สงบ

คุณกำลังทำอะไรแทน ไม่ว่าคุณจะ "เพิ่ม", "ลบ" หรือ "อัปเดต" การเปิดใช้งานในกลุ่มหรือหากคุณต้องการ: จัดการ "สถานะ" - แหล่งข้อมูลบนกลุ่ม โดยส่วนตัวแล้วฉันจะใช้ "การเปิดใช้งาน" เพราะสิ่งเหล่านี้มีความคลุมเครือน้อยกว่าแนวคิด "สถานะ": การสร้างสถานะไม่ชัดเจนการสร้างการเปิดใช้งานไม่ใช่

  • POST /groups/{group id}/activation สร้าง (หรือร้องขอการสร้าง) การเปิดใช้งาน
  • PATCH /groups/{group id}/activationอัพเดตรายละเอียดบางอย่างของการเปิดใช้งานที่มีอยู่ เนื่องจากกลุ่มมีการเปิดใช้งานเพียงครั้งเดียวเราจึงรู้ว่าทรัพยากรการเปิดใช้งานใดที่เราอ้างถึง
  • PUT /groups/{group id}/activationส่วนแทรกหรือแทนที่การเปิดใช้งานแบบเก่า เนื่องจากกลุ่มมีการเปิดใช้งานเพียงครั้งเดียวเราจึงรู้ว่าทรัพยากรการเปิดใช้งานใดที่เราอ้างถึง
  • DELETE /groups/{group id}/activation จะยกเลิกหรือลบการเปิดใช้งาน

รูปแบบนี้มีประโยชน์เมื่อ "การเปิดใช้งาน" ของกลุ่มมีผลข้างเคียงเช่นการชำระเงินการส่งอีเมลที่ส่งเป็นต้น เฉพาะโพสต์และแพทช์อาจมีผลข้างเคียง เมื่อเช่นการลบการเปิดใช้งานจำเป็นต้องพูดแจ้งผู้ใช้ทางจดหมาย DELETE ไม่ใช่ตัวเลือกที่ถูกต้อง ในกรณีที่คุณอาจต้องการที่จะสร้างทรัพยากรการเสื่อมPOST /groups/{group_id}/deactivation :

เป็นความคิดที่ดีที่จะปฏิบัติตามแนวทางเหล่านี้เนื่องจากสัญญามาตรฐานนี้ทำให้ชัดเจนสำหรับลูกค้าของคุณและพร็อกซีและเลเยอร์ทั้งหมดระหว่างลูกค้ากับคุณรู้ว่าจะปลอดภัยเมื่อลองอีกครั้งและเมื่อใด สมมติว่าไคลเอนต์อยู่ที่ไหนสักแห่งที่มี wifi ที่ไม่สม่ำเสมอและผู้ใช้คลิกที่ "ปิดการใช้งาน" ซึ่งก่อให้เกิดDELETE: หากล้มเหลวลูกค้าสามารถลองใหม่ได้จนกว่าจะได้รับ 404, 200 หรืออะไรก็ได้ที่สามารถจัดการได้ แต่ถ้ามันก่อให้เกิดPOST to deactivationมันรู้ว่าจะไม่ลองใหม่: POST หมายถึงสิ่งนี้
ตอนนี้ลูกค้าทุกคนมีสัญญาซึ่งเมื่อติดตามจะป้องกันการส่งอีเมล 42 ฉบับ "กลุ่มของคุณถูกปิดการใช้งาน" เพียงเพราะห้องสมุด HTTP ของพวกเขาพยายามโทรไปที่แบ็กเอนด์

การอัปเดตแอตทริบิวต์เดียว: ใช้ PATCH

PATCH /groups/{group id}

ในกรณีที่คุณต้องการอัพเดทคุณสมบัติ เช่น "สถานะ" อาจเป็นแอตทริบิวต์ของกลุ่มที่สามารถตั้งค่าได้ แอตทริบิวต์เช่น "สถานะ" มักจะเป็นตัวเลือกที่ดีในการ จำกัด ค่ารายการที่อนุญาต ตัวอย่างใช้ JSON-scheme ที่ไม่ได้กำหนด:

PATCH /groups/{group id} { "attributes": { "status": "active" } }
response: 200 OK

PATCH /groups/{group id} { "attributes": { "status": "deleted" } }
response: 406 Not Acceptable

การแทนที่ทรัพยากรโดยไม่มีผลข้างเคียงให้ใช้ PUT

PUT /groups/{group id}

ในกรณีที่คุณต้องการแทนที่ทั้งกลุ่ม นี่ไม่ได้แปลว่าเซิร์ฟเวอร์จะสร้างกลุ่มใหม่และส่งกลุ่มเก่าออกไปเช่นรหัสอาจยังคงเหมือนเดิม แต่สำหรับลูกค้าที่นี้คือสิ่งที่ PUT สามารถหมายถึง: ลูกค้าควรถือว่าเขาได้รับรายการใหม่ทั้งหมดขึ้นอยู่กับการตอบสนองของเซิร์ฟเวอร์

ในกรณีที่มีการPUTร้องขอลูกค้าควรส่งทรัพยากรทั้งหมดโดยมีข้อมูลทั้งหมดที่จำเป็นในการสร้างรายการใหม่: โดยปกติแล้วข้อมูลเดียวกันกับ POST-create จะต้องมี

PUT /groups/{group id} { "attributes": { "status": "active" } }
response: 406 Not Acceptable

PUT /groups/{group id} { "attributes": { "name": .... etc. "status": "active" } }
response: 201 Created or 200 OK, depending on whether we made a new one.

ความต้องการที่สำคัญมากคือPUTเป็น idempotent: ถ้าคุณจำเป็นต้องมีผลข้างเคียงเมื่อปรับปรุงกลุ่ม (หรือการเปลี่ยนแปลงการเปิดใช้งาน) PATCHคุณควรใช้ PUTดังนั้นเมื่อผลการปรับปรุงในเช่นการส่งอีเมลที่ไม่ได้ใช้


3
นี่เป็นข้อมูลที่ดีสำหรับฉัน "รูปแบบนี้มีประโยชน์เมื่อ" การเปิดใช้งาน "ของกลุ่มมีผลข้างเคียง" - ทำไมรูปแบบนี้มีประโยชน์อย่างไรโดยเฉพาะเมื่อการกระทำมีผลข้างเคียงเมื่อเทียบกับจุดเริ่มต้น OP
Abdul

1
@Abdul รูปแบบมีประโยชน์ด้วยเหตุผลหลายประการ แต่ wrt ผลข้างเคียงมันควรจะชัดเจนกับลูกค้าสิ่งที่มีผลกระทบการกระทำ เมื่อใดที่บอกว่าแอพ iOS ตัดสินใจที่จะส่งสมุดรายชื่อทั้งหมดเป็น "ผู้ติดต่อ" ควรมีความชัดเจนอย่างยิ่งว่าผลข้างเคียงที่เกิดจากการสร้างอัปเดตลบ ฯลฯ ของผู้ติดต่อจะเป็นอย่างไร หากต้องการหลีกเลี่ยงการส่งจดหมายถึงผู้ติดต่อทั้งหมดเช่น
berkes

1
ใน RESTfull PUT ยังสามารถเปลี่ยนเอนทิตีเอกลักษณ์ได้ - ตัวอย่างเช่น PrimaryKey ID ซึ่งมันอาจทำให้คำร้องขอแบบขนานล้มเหลว (ตัวอย่างเช่นการอัปเดตเอนทิตีทั้งหมดต้องลบบางแถวและเพิ่มอันใหม่ดังนั้นการสร้างเอนทิตีใหม่) ซึ่ง PATCH จะต้องไม่สามารถทำเช่นนั้นได้ทำให้การร้องขอ PATCH ไม่ จำกัด จำนวนโดยไม่ส่งผลกระทบต่อ "แอปพลิเคชัน" อื่น ๆ
Piotr Kula

1
คำตอบที่เป็นประโยชน์มาก ขอบคุณ! ฉันจะเพิ่มความคิดเห็นเช่นเดียวกับในคำตอบของลุคชี้ให้เห็นว่าความแตกต่างระหว่าง PUT / PATCH ไม่ได้เป็นเพียงแค่การอัพเดททั้งหมด / บางส่วน แต่ยังเป็น idempotency ที่แตกต่างกัน นี่ไม่ใช่ข้อผิดพลาดมันเป็นการตัดสินใจโดยเจตนาและฉันคิดว่ามีคนไม่มากที่จะต้องพิจารณาเรื่องนี้เมื่อตัดสินใจใช้วิธีของ HTTP
Mladen B.

1
@richremer services เช่น model เป็น abstractions ภายใน เช่นเดียวกับที่เป็นนามธรรมที่ไม่ดีที่ต้องการความสัมพันธ์ 1-1 ระหว่าง REST-endpoints-and-ORM-models หรือแม้แต่ตารางฐานข้อมูลมันเป็นนามธรรมที่ไม่ดีที่จะเปิดเผยบริการ API ภายนอกของคุณต้องสื่อสารโมเดลของโดเมน วิธีที่คุณใช้งานภายในไม่ต้องกังวลกับ API คุณควรย้ายจาก ActivationService ไปยังการเปิดใช้งานตาม CQRS โดยไม่ต้องเปลี่ยน API ของคุณ
berkes

12

ฉันขอแนะนำให้ใช้ PATCH เนื่องจาก 'กลุ่ม' ทรัพยากรของคุณมีคุณสมบัติมากมาย แต่ในกรณีนี้คุณกำลังอัปเดตเฉพาะฟิลด์การเปิดใช้งาน (การแก้ไขบางส่วน)

ตาม RFC5789 ( https://tools.ietf.org/html/rfc5789 )

วิธี HTTP PUT ที่มีอยู่อนุญาตให้เปลี่ยนเอกสารได้อย่างสมบูรณ์เท่านั้น ข้อเสนอนี้เพิ่มวิธี HTTP ใหม่ PATCH เพื่อแก้ไขทรัพยากร HTTP ที่มีอยู่

นอกจากนี้ในรายละเอียดเพิ่มเติม

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

PATCH นั้นไม่ปลอดภัยหรือ idempotent ตามที่กำหนดโดย [RFC2616], มาตรา 9.1

ลูกค้าต้องเลือกเวลาที่จะใช้ PATCH แทน PUT สำหรับ
ตัวอย่างเช่นถ้าขนาดเอกสารแพทช์มีขนาดใหญ่กว่าขนาดของ
ข้อมูลทรัพยากรใหม่ที่จะใช้ในการใส่แล้วมันอาจจะทำให้
ความรู้สึกที่จะใช้ PUT แทน PATCH การเปรียบเทียบกับ POST นั้นยากกว่ามากเนื่องจาก POST ถูกนำไปใช้ในรูปแบบที่แตกต่างกันอย่างกว้างขวางและสามารถ
รวมการดำเนินงานที่คล้ายกับ PUT และ PATCH ถ้าเซิร์ฟเวอร์เลือก หาก
การดำเนินการไม่ได้แก้ไขทรัพยากรที่ระบุโดย Request- URI ในวิธีที่คาดการณ์ได้ POST ควรได้รับการพิจารณาแทน PATCH
หรือ PUT

รหัสตอบกลับสำหรับ PATCH คือ

มีการใช้รหัสการตอบสนอง 204 เนื่องจากการตอบสนองไม่ได้มีเนื้อหาของข้อความ (ซึ่งการตอบสนองด้วยรหัส 200 จะมี) โปรดทราบว่าสามารถใช้รหัสสำเร็จอื่น ๆ ได้เช่นกัน

ยังอ้างถึง thttp: //restcookbook.com/HTTP%20Methods/patch/

Caveat: API ที่ใช้ PATCH จะต้องแก้ไขแบบอะตอม มันเป็นไปไม่ได้ที่ทรัพยากรจะได้รับการแก้ไขเพียงครึ่งเดียวเมื่อ GET ร้องขอ


7

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

/groups/api/groups/{group id}/status

ข้อเสียของวิธีการนี้ผ่าน PATCH สำหรับการปรับเปลี่ยนคือคุณจะไม่สามารถทำการเปลี่ยนแปลงมากกว่าหนึ่งคุณสมบัติของกลุ่มอะตอมและธุรกรรม หากการเปลี่ยนแปลงธุรกรรมมีความสำคัญให้ใช้ PATCH

หากคุณตัดสินใจที่จะเปิดเผยสถานะเป็นทรัพยากรย่อยของกลุ่มมันควรจะเป็นลิงค์ในการเป็นตัวแทนของกลุ่ม ตัวอย่างเช่นหากเอเจนต์ได้รับกลุ่ม 123 และยอมรับ XML เนื้อหาการตอบกลับอาจมี:

<group id="123">
  <status>Active</status>
  <link rel="/linkrels/groups/status" uri="/groups/api/groups/123/status"/>
  ...
</group>

การเชื่อมโยงหลายมิติเป็นสิ่งจำเป็นเพื่อเติมเต็มสื่อสิ่งพิมพ์ในขณะที่เอ็นจินของเงื่อนไขสถานะแอ็พพลิเคชันของรูปแบบสถาปัตยกรรม REST


0

โดยทั่วไปฉันต้องการสิ่งที่เรียบง่ายขึ้นเล็กน้อยเช่นactivate/ deactivatesub-resource (เชื่อมโยงโดยLinkส่วนหัวด้วยrel=service)

POST /groups/api/v1/groups/{group id}/activate

หรือ

POST /groups/api/v1/groups/{group id}/deactivate

สำหรับผู้ใช้งานอินเทอร์เฟซนี้ใช้งานง่ายและเป็นไปตามหลักการ REST โดยไม่ทำให้คุณเสียเวลาในการกำหนดแนวคิด "การเปิดใช้งาน" เป็นทรัพยากรส่วนบุคคล


0

ตัวเลือกหนึ่งที่เป็นไปได้ที่จะใช้พฤติกรรมดังกล่าวคือ

PUT /groups/api/v1/groups/{group id}/status
{
    "Status":"Activated"
}

และถ้าใครต้องการปิดการใช้งานก็PUTจะมีDeactivatedสถานะเป็น JSON

ในกรณีที่จำเป็นต้องมีการเปิดใช้งาน / ปิดใช้งานจำนวนมากPATCHสามารถเข้าสู่เกม (ไม่ใช่สำหรับกลุ่มที่แน่นอน แต่สำหรับgroupsทรัพยากร:

PATCH /groups/api/v1/groups
{
    { “op”: “replace”, “path”: “/group1/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group7/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group9/status”, “value”: “Deactivated” }
}

โดยทั่วไปนี่เป็นแนวคิดที่ @Andrew Dobrowolski แนะนำ แต่มีการเปลี่ยนแปลงเล็กน้อยในการรับรู้ที่แน่นอน

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