REST - ใส่ ID ในร่างกายหรือไม่?


103

สมมติว่าฉันต้องการมีทรัพยากร RESTful สำหรับผู้คนโดยที่ลูกค้าสามารถกำหนด ID ได้

บุคคลมีลักษณะดังนี้: {"id": <UUID>, "name": "Jimmy"}

ตอนนี้ลูกค้าควรบันทึก (หรือ "PUT") อย่างไร

  1. PUT /person/UUID {"id": <UUID>, "name": "Jimmy"} - ตอนนี้เรามีการทำซ้ำที่น่ารังเกียจซึ่งเราต้องตรวจสอบตลอดเวลา: ID ในร่างกายตรงกับที่อยู่ในเส้นทางหรือไม่?
  2. การแสดงแบบไม่สมมาตร:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID ผลตอบแทน {"id": <UUID>, "name": "Jimmy"}
  3. ไม่มีรหัสในเนื้อหา - เฉพาะในตำแหน่ง:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID ผลตอบแทน {"name": "Jimmy"}
  4. ชนิดของการไม่มีPOSTดูเหมือนความคิดที่ดีตั้งแต่ ID ถูกสร้างขึ้นโดยลูกค้า

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

คำตอบ:


65

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

ดังนั้นฉันจะไปหาโซลูชันแบบอสมมาตรใน (2) และหลีกเลี่ยง "การตรวจสอบการทำซ้ำที่น่ารังเกียจ" ที่ฝั่งเซิร์ฟเวอร์เมื่อเขียน:

PUT /person/UUID {"name": "Jimmy"}

GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

2
และหากคุณใช้การพิมพ์ (แบบคงที่หรือไดนามิก) คุณจะไม่สามารถมีโมเดลได้อย่างไม่ลำบากหากไม่มี ID ... ดังนั้นการลบ ID จาก URL สำหรับคำขอ PUT จึงง่ายกว่า มันจะไม่ "สงบ" แต่มันจะถูกต้อง
Ivan Kleshnin

2
เก็บ TO เพิ่มเติมโดยไม่ต้องidใช้ TO พร้อมกับ id และเอนทิตีและตัวแปลงเพิ่มเติมและค่าใช้จ่ายที่มากเกินไปสำหรับโปรแกรมเมอร์
Grigory Kislin

จะเป็นอย่างไรหากได้รับ ID จาก BODY เช่น PUT / person {"id": 1, "name": "Jimmy"} จะเป็นการปฏิบัติที่ไม่ดีหรือไม่?
Bruno Santos

ใส่ ID ในตัวก็คงไม่เป็นไร ใช้ GUID สำหรับ ID แทนจำนวนเต็ม - มิฉะนั้นคุณจะเสี่ยงต่อการทำ ID ซ้ำ
Jørn Wildt

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

27

หากเป็น API สาธารณะคุณควรระมัดระวังในการตอบกลับ แต่ยอมรับอย่างเสรี

โดยที่ฉันหมายความว่าคุณควรสนับสนุนทั้ง 1 และ 2 ฉันยอมรับว่า 3 ไม่สมเหตุสมผล

วิธีรองรับทั้ง 1 และ 2 คือการรับ id จาก url หากไม่มีการระบุไว้ใน request body และถ้าอยู่ใน request body ให้ตรวจสอบว่าตรงกับ id ใน url หากทั้งสองไม่ตรงกันให้ส่งคืนการตอบกลับ 400 คำขอที่ไม่ถูกต้อง

เมื่อส่งคืนทรัพยากรบุคคลให้ระมัดระวังและรวม id ไว้ใน json เสมอแม้ว่าจะเป็นทางเลือกในการใส่ก็ตาม


3
นี่ควรเป็นทางออกที่ได้รับการยอมรับ API ควรเป็นมิตรกับผู้ใช้เสมอ ควรเป็นทางเลือกในร่างกาย ฉันไม่ควรได้รับ ID จาก POST จากนั้นต้องทำให้ไม่ถูกกำหนดใน PUT นอกจากนี้จุดตอบสนอง 400 ที่สร้างไว้ก็เปิดอยู่
Michael

ประมาณ 400 รหัสโปรดดูที่softwareengineering.stackexchange.com/questions/329229/…อภิปราย ในระยะสั้น 400 รหัสไม่เหมาะสมเฉพาะเจาะจงน้อยกว่าเมื่อเปรียบเทียบกับ 422
Grigory Kislin

8

วิธีแก้ปัญหานี้วิธีหนึ่งคือแนวคิดที่ค่อนข้างสับสนของ "Hypertext As The Engine Of Application State" หรือ "HATEOAS" ซึ่งหมายความว่าการตอบกลับ REST ประกอบด้วยทรัพยากรที่มีอยู่หรือการดำเนินการที่จะดำเนินการเป็นไฮเปอร์ลิงก์ การใช้วิธีนี้ซึ่งเป็นส่วนหนึ่งของแนวคิดดั้งเดิมของ REST ตัวระบุ / ID เฉพาะของทรัพยากรคือไฮเปอร์ลิงก์ ตัวอย่างเช่นคุณอาจมีสิ่งต่อไปนี้

GET /person/<UUID> {"person": {"location": "/person/<UUID>", "data": { "name": "Jimmy"}}}

จากนั้นหากคุณต้องการอัปเดตทรัพยากรนั้นคุณสามารถทำได้ (pseudocode):

updatedPerson = person.data
updatedPerson.name = "Timmy"
PUT(URI: response.resource, data: updatedPerson)

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

GET /people
{ "people": [
    "/person/1",
    "/person/2"
  ]
}

(แน่นอนคุณสามารถคืนวัตถุบุคคลเต็มสำหรับแต่ละคนได้ขึ้นอยู่กับความต้องการของแอปพลิเคชัน)

ด้วยวิธีนี้คุณจะคิดถึงวัตถุของคุณในแง่ของทรัพยากรและสถานที่มากขึ้นและในแง่ของ ID น้อยลง การแสดงภายในของตัวระบุเฉพาะจึงแยกออกจากตรรกะไคลเอนต์ของคุณ นี่เป็นแรงผลักดันดั้งเดิมที่อยู่เบื้องหลัง REST: เพื่อสร้างสถาปัตยกรรมไคลเอนต์ - เซิร์ฟเวอร์ที่เชื่อมโยงกันอย่างหลวม ๆ มากกว่าระบบ RPC ที่มีมาก่อนโดยใช้คุณสมบัติของ HTTP สำหรับข้อมูลเพิ่มเติมเกี่ยว HATEOAS, ดูที่บทความวิกิพีเดียได้เป็นอย่างดีเช่นนี้บทความสั้น


4

ในการแทรกคุณไม่จำเป็นต้องเพิ่มรหัสใน URL วิธีนี้หากคุณส่ง ID ใน PUT คุณอาจตีความว่าเป็นการอัปเดตเพื่อเปลี่ยนคีย์หลัก

  1. แทรก:

    PUT /persons/ 
      {"id": 1, "name": "Jimmy"}
    HTTP/1.1 201 Created     
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}
    
    GET /persons/1
    
    HTTP/1.1 200 OK
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}  
    
  2. อัปเดต

    PUT /persons/1 
         {"id": "2", "name": "Jimmy Jr"} - 
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="filled_by_server"}
    
    GET /persons/2 
    
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="updated_by_server"}
    

JSON APIใช้มาตรฐานนี้และแก้ปัญหาบางอย่างกลับวัตถุแทรกหรือการปรับปรุงด้วยการเชื่อมโยงไปยังวัตถุใหม่ การอัปเดตหรือส่วนแทรกบางอย่างอาจรวมถึงตรรกะทางธุรกิจบางอย่างที่จะเปลี่ยนฟิลด์เพิ่มเติม

นอกจากนี้คุณจะเห็นว่าคุณสามารถหลีกเลี่ยงการได้รับหลังจากแทรกและอัปเดตได้


3

สิ่งนี้ถูกถามมาก่อน - การสนทนามีค่าควรดู:

การตอบกลับ RESTful GET ควรส่งคืน ID ของทรัพยากรหรือไม่

นี้เป็นหนึ่งในคำถามเหล่านั้นที่มันเป็นเรื่องง่ายที่จะได้รับจมลงไปในการอภิปรายรอบสิ่งที่เป็นและไม่ได้เป็น "สงบ"

สำหรับสิ่งที่คุ้มค่าฉันพยายามคิดในแง่ของทรัพยากรที่สม่ำเสมอและไม่เปลี่ยนการออกแบบระหว่างวิธีการ อย่างไรก็ตาม IMHO สิ่งที่สำคัญที่สุดจากมุมมองของการใช้งานคือคุณมีความสอดคล้องกันทั้ง API!


2

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

ต้องบอกว่าฉันคิดว่าส่วนใหญ่ควรใช้ PUT สำหรับการอัปเดตและidควรส่งผ่าน URL ด้วยเช่นกัน ด้วยเหตุนี้การใช้ PUT เพื่ออัปเดตตัวระบุทรัพยากรจึงเป็นความคิดที่ไม่ดี มันทำให้เราตกอยู่ในสถานการณ์ที่ไม่พึงปรารถนาเมื่อidใน URL อาจแตกต่างจากidในร่างกาย

ดังนั้นเราจะแก้ไขความขัดแย้งดังกล่าวได้อย่างไร? โดยทั่วไปเรามี 2 ตัวเลือก:

  • โยนข้อยกเว้น 4XX
  • เพิ่มส่วนหัวWarning( X-API-Warnฯลฯ )

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


2

แค่ FYI คำตอบที่นี่ก็ผิด

ดู:

https://restfulapi.net/rest-api-design-tutorial-with-example/

https://restfulapi.net/rest-put-vs-post/

https://restfulapi.net/http-methods/#patch

วาง

ใช้ PUT API เพื่ออัปเดตทรัพยากรที่มีอยู่เป็นหลัก (หากทรัพยากรไม่มีอยู่ API อาจตัดสินใจสร้างทรัพยากรใหม่หรือไม่) หากทรัพยากรใหม่ถูกสร้างขึ้นโดย PUT API เซิร์ฟเวอร์ต้นทางต้องแจ้งตัวแทนผู้ใช้ผ่านการตอบสนองรหัสตอบสนอง HTTP 201 (สร้าง) และหากทรัพยากรที่มีอยู่ถูกแก้ไขไม่ว่าจะเป็น 200 (ตกลง) หรือ 204 (ไม่มีเนื้อหา) รหัสตอบกลับควรถูกส่งไปเพื่อระบุว่าการร้องขอเสร็จสมบูรณ์

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

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

ปะ

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

ดังนั้นคุณควรใช้ในลักษณะนี้:

POST    /device-management/devices      : Create a new device
PUT     /device-management/devices/{id} : Update the device information identified by "id"
PATCH   /device-management/devices/{id} : Partial-update the device information identified by "id"

แนวทางปฏิบัติที่เป็นประโยชน์ระบุว่าไม่สำคัญว่าคุณจะใส่อะไรที่ / {id} - เนื้อหาของบันทึกควรได้รับการอัปเดตเป็นเนื้อหาที่จัดเตรียมโดยเพย์โหลด - แต่ GET / {id} ยังควรลิงก์ไปยังทรัพยากรเดียวกัน

กล่าวอีกนัยหนึ่ง PUT / 3 อาจอัปเดตเป็น payload id เป็น 4 แต่ GET / 3 ควรลิงก์ไปยัง payload เดียวกัน (และส่งคืนค่าที่มี id ตั้งค่าเป็น 4)

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


ในย่อหน้าของคุณ: "กล่าวอีกนัยหนึ่ง PUT / 3 อาจอัปเดตเป็น payload id เป็น 4 แต่ GET / 3 ยังควรลิงก์ไปยัง payload เดียวกัน (และส่งคืนค่าที่มี id ตั้งค่าเป็น4 )" - คุณหมายถึง3แทนที่จะเป็น4ในรหัสสุดท้ายหรือไม่?
มล

Re: "การใช้ PATCH แทน PUT ถ้าคุณไม่รวมประชาชนได้" นี่เป็นข้อมูลที่ไม่ดี 2014 ของ RFC 7231 (หนึ่งใน RFCs ที่ล้าสมัย 1999 ของ RFC 2616) กล่าวถึงที่นี่transformation applied to the bodyว่าเซิร์ฟเวอร์สามารถมี มีแม้กระทั่งกลไกสำหรับไคลเอนต์ที่allows a user agent to know when the representation body it has in memory remains currentซึ่งเป็น: MUST NOT send ... an ETag or Last-Modified ... unless the request's representation was saved without any transformationเซิร์ฟเวอร์
Slawomir Brzezinski

นอกจากนี้โปรดดูคำถาม SO นี้ + คำตอบจากผู้ใช้รายอื่นที่มีเหตุผลว่าทำไมจึงเหมาะสมที่จะใช้ PUT สรุปได้เป็นอย่างดีว่า "สิ่งที่สำคัญคือต้องเข้าใจว่าอะไรคือเจตนาของคำขอของลูกค้าลูกค้าตั้งใจที่จะแทนที่เนื้อหาของทรัพยากรโดยสมบูรณ์ด้วยค่าที่ส่งผ่าน"
Slawomir Brzezinski

1

ไม่มีอะไรเลวร้ายในการใช้แนวทางต่างๆ แต่ฉันคิดว่าวิธีที่ดีที่สุดคือการแก้ปัญหาที่มี 2

 PUT /person/UUID {"name": "Jimmy"}

 GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

ส่วนใหญ่จะใช้ในลักษณะนี้แม้แต่กรอบงานเอนทิตีก็ใช้เทคนิคนี้เมื่อเพิ่มเอนทิตีใน dbContext คลาสที่ไม่มี ID ที่สร้างขึ้นคือ ID ที่สร้างขึ้นโดยการอ้างอิงใน Entity Framework


1

ฉันกำลังมองหาที่นี้จากJSON-LDจุดเว็บ / ความหมายของมุมมองเพราะเห็นว่าเป็นวิธีที่ดีที่จะไปเพื่อให้บรรลุความสอดคล้อง REST จริงที่ผมได้อธิบายไว้ในภาพนิ่งเหล่านี้ เมื่อมองจากมุมมองนั้นไม่มีคำถามที่จะเลือกตัวเลือก (1. ) เนื่องจาก ID (IRI) ของทรัพยากรบนเว็บควรเท่ากับ URL ที่ฉันสามารถใช้เพื่อค้นหา / หักล้างทรัพยากรได้ ฉันคิดว่าการตรวจสอบไม่ใช่เรื่องยากที่จะนำไปใช้จริง ๆ และไม่ได้ทวีความรุนแรงขึ้นในเชิงคำนวณ ดังนั้นฉันจึงไม่คิดว่านี่เป็นเหตุผลที่ถูกต้องสำหรับการใช้ตัวเลือก (2. ) ฉันคิดว่าตัวเลือก (3. ) ไม่ใช่ตัวเลือกจริงๆเนื่องจาก POST (สร้างใหม่) มีความหมายที่แตกต่างจาก PUT (อัปเดต / แทนที่)


0

คุณอาจต้องดูประเภทคำขอ PATCH / PUT

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

เกี่ยวกับการมี ID ใน url ฉันคิดว่าคุณควรมีไว้เสมอเนื่องจากเป็นแนวทางปฏิบัติมาตรฐานในการระบุทรัพยากร แม้แต่ Stripe API ก็ทำงานเช่นนั้น

คุณสามารถใช้คำขอ PATCH เพื่ออัปเดตทรัพยากรบนเซิร์ฟเวอร์ด้วย ID เพื่อระบุได้ แต่ไม่ต้องอัปเดต ID จริง

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