หมายเหตุ : เมื่อฉันใช้เวลาอ่านเกี่ยวกับ REST เป็นครั้งแรก idempotence เป็นแนวคิดที่สับสนที่จะพยายามทำให้ถูกต้อง ฉันยังไม่เข้าใจในคำตอบดั้งเดิมของฉันเนื่องจากความคิดเห็นเพิ่มเติม (และคำตอบของ Jason Hoetger ) ได้แสดงขึ้น ในขณะที่ฉันได้ต่อต้านการปรับปรุงคำตอบนี้อย่างกว้างขวางเพื่อหลีกเลี่ยงการขโมยความคิดอย่างมีประสิทธิภาพเจสัน แต่ตอนนี้ฉันกำลังแก้ไขเพราะเพราะฉันถูกขอให้ (ในความคิดเห็น)
หลังจากอ่านคำตอบของฉันฉันขอแนะนำให้คุณอ่านคำตอบที่ยอดเยี่ยมของ Jason Hoetgerและฉันจะพยายามทำให้คำตอบของฉันดีขึ้นโดยไม่ขโมยจาก Jason
ทำไมต้องใส่ idempotent?
ดังที่คุณบันทึกไว้ในการอ้างอิง RFC 2616 ของคุณ PUT ถือว่าเป็น idempotent เมื่อคุณใส่ทรัพยากรข้อสันนิษฐานสองข้อนี้อยู่ในระหว่างเล่น:
คุณกำลังอ้างถึงเอนทิตีไม่ใช่การรวบรวม
เอนทิตีที่คุณกำลังจัดหาเสร็จสมบูรณ์ ( เอนทิตีทั้งหมด )
ลองดูตัวอย่างหนึ่งของคุณ
{ "username": "skwee357", "email": "skwee357@domain.com" }
หากคุณโพสต์เอกสารนี้/users
ตามที่คุณแนะนำคุณอาจได้รับเอนทิตีกลับเช่น
## /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
หากคุณต้องการแก้ไขเอนทิตีนี้ในภายหลังคุณเลือกระหว่าง PUT และ PATCH PUT อาจมีลักษณะเช่นนี้:
PUT /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // new email address
}
คุณสามารถทำได้เหมือนกันโดยใช้ PATCH ที่อาจมีลักษณะเช่นนี้:
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
คุณจะสังเกตเห็นความแตกต่างได้ทันทีระหว่างสองสิ่งนี้ PUT รวมพารามิเตอร์ทั้งหมดในผู้ใช้รายนี้ แต่ PATCH รวมเฉพาะพารามิเตอร์ที่กำลังแก้ไข ( email
)
เมื่อใช้ PUT จะถือว่าคุณกำลังส่งเอนทิตีที่สมบูรณ์และเอนทิตีที่สมบูรณ์นั้นจะแทนที่เอนทิตีที่มีอยู่ที่ URI นั้น ในตัวอย่างด้านบน PUT และ PATCH บรรลุเป้าหมายเดียวกัน: ทั้งคู่เปลี่ยนที่อยู่อีเมลของผู้ใช้รายนี้ แต่ PUT จัดการมันด้วยการแทนที่เอนทิตีทั้งหมดในขณะที่ PATCH จะอัพเดตเฉพาะฟิลด์ที่ให้มาเท่านั้น
เนื่องจากคำขอ PUT รวมถึงเอนทิตีทั้งหมดหากคุณออกคำขอเดียวกันซ้ำแล้วซ้ำอีกจึงควรมีผลลัพธ์เดียวกันเสมอ (ข้อมูลที่คุณส่งเป็นข้อมูลทั้งหมดของเอนทิตีในขณะนี้) ดังนั้น PUT จึงเป็น idempotent
ใช้ PUT ผิด
จะเกิดอะไรขึ้นถ้าคุณใช้ข้อมูล PATCH ข้างต้นในคำขอ PUT
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
PUT /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"email": "skwee357@gmail.com" // new email address... and nothing else!
}
(ฉันสมมติว่าเพื่อจุดประสงค์ของคำถามนี้ว่าเซิร์ฟเวอร์ไม่มีฟิลด์ที่ต้องระบุและจะอนุญาตให้สิ่งนี้เกิดขึ้น ... ซึ่งอาจไม่เป็นจริงในกรณีนี้)
เนื่องจากเราใช้ PUT แต่ให้มาเท่านั้นemail
ตอนนี้เป็นสิ่งเดียวในเอนทิตีนี้ ส่งผลให้ข้อมูลสูญหาย
ตัวอย่างนี้มีไว้เพื่อเป็นตัวอย่าง - ไม่เคยทำเช่นนี้ คำขอ PUT นี้เป็น idempotent ทางเทคนิค แต่นั่นไม่ได้หมายความว่ามันไม่ใช่ความคิดที่แย่มาก
PATCH จะเป็น idempotent ได้อย่างไร?
ในตัวอย่างข้างต้น PATCH เป็น idempotent คุณทำการเปลี่ยนแปลง แต่ถ้าคุณทำการเปลี่ยนแปลงแบบเดิมซ้ำแล้วซ้ำอีกมันจะให้ผลลัพธ์เหมือนเดิมเสมอ: คุณเปลี่ยนที่อยู่อีเมลให้เป็นค่าใหม่
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // email address was changed
}
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address... again
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // nothing changed since last GET
}
ตัวอย่างดั้งเดิมของฉันได้รับการแก้ไขเพื่อความถูกต้อง
ตอนแรกฉันมีตัวอย่างที่ฉันคิดว่าแสดงให้เห็นว่าไม่ใช่ idempotency แต่พวกเขาทำให้เข้าใจผิด / ไม่ถูกต้อง ฉันจะเก็บตัวอย่าง แต่ใช้พวกเขาเพื่อแสดงสิ่งที่แตกต่าง: ที่เอกสาร PATCH หลายตัวกับเอนทิตีเดียวกันปรับเปลี่ยนคุณสมบัติที่แตกต่างกันอย่าทำให้ PATCHes ไม่ใช่ idempotent
สมมติว่าในช่วงเวลาที่ผ่านมามีการเพิ่มผู้ใช้ นี่คือสถานะที่คุณเริ่มต้น
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@olddomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
หลังจาก PATCH คุณมีเอนทิตีที่แก้ไข:
PATCH /users/1
{"email": "skwee357@newdomain.com"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com", // the email changed, yay!
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
หากคุณใช้ PATCH ซ้ำหลายครั้งคุณจะได้รับผลลัพธ์เดิมต่อไป: อีเมลถูกเปลี่ยนเป็นค่าใหม่ A เข้า, ออกมา, ดังนั้นนี่คือ idempotent
อีกหนึ่งชั่วโมงต่อมาหลังจากที่คุณไปทำกาแฟและพักสมองคนอื่นมาพร้อมกับแพทช์ของตัวเอง ดูเหมือนว่าที่ทำการไปรษณีย์ได้ทำการเปลี่ยนแปลงบางอย่าง
PATCH /users/1
{"zip": "12345"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com", // still the new email you set
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345" // and this change as well
}
เนื่องจาก PATCH นี้จากที่ทำการไปรษณีย์ไม่เกี่ยวข้องกับอีเมลจึงมีเพียงรหัสไปรษณีย์เท่านั้นหากมีการใช้ซ้ำหลายครั้งจึงจะได้รับผลลัพธ์เดียวกัน: รหัสไปรษณีย์ถูกตั้งค่าเป็นค่าใหม่ ไปในออกมาดังนั้นนี้เป็นยัง idempotent
ในวันถัดไปคุณตัดสินใจที่จะส่งแพตช์ของคุณอีกครั้ง
PATCH /users/1
{"email": "skwee357@newdomain.com"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345"
}
แพทช์ของคุณมีผลเช่นเดียวกันกับเมื่อวาน: ตั้งค่าที่อยู่อีเมล เข้าไปข้างใน, A ออกมา, ดังนั้นนี่คือ idempotent เช่นกัน.
สิ่งที่ฉันทำผิดในคำตอบเดิมของฉัน
ฉันต้องการวาดความแตกต่างที่สำคัญ (สิ่งที่ฉันผิดในคำตอบเดิมของฉัน) เซิร์ฟเวอร์จำนวนมากจะตอบสนองต่อคำขอ REST ของคุณโดยส่งสถานะเอนทิตีใหม่พร้อมการแก้ไขของคุณ (ถ้ามี) ดังนั้นเมื่อคุณได้รับการตอบกลับนี้มันแตกต่างจากที่คุณได้รับเมื่อวานนี้เนื่องจากรหัสไปรษณีย์ไม่ใช่ที่คุณได้รับครั้งล่าสุด อย่างไรก็ตามคำขอของคุณไม่เกี่ยวข้องกับรหัสไปรษณีย์เฉพาะกับอีเมลเท่านั้น ดังนั้นเอกสาร PATCH ของคุณยังคงเป็น idempotent - อีเมลที่คุณส่งเป็น PATCH ในขณะนี้คือที่อยู่อีเมลในเอนทิตี
ดังนั้นเมื่อ PATCH ไม่ใช่ idempotent แล้ว
สำหรับการรักษาเต็มรูปแบบของคำถามนี้ผมอีกครั้งดูคุณคำตอบของเจสัน Hoetger ฉันจะทิ้งมันไว้ที่นั้นเพราะฉันคิดว่าฉันไม่สามารถตอบส่วนนี้ได้ดีกว่าเขา