प्रोडक्शन में JSON Patch: Atomicity, Idempotency और Concurrent Conflicts
मुख्य बात: JSON Patch केवल एक change प्रारूप है; atomicity, idempotency और concurrency अभी भी आपका काम है। इसमें test preconditions, path escaping, rollback और ETag coordination शामिल हैं।

सारांश: पहले patch के लक्षित पथ और अनुमतियाँ जाँचें, फिर test या If-Match से concurrency assertion जोड़ें; patch को एक प्रति पर लागू करें ताकि विफलता पर कुछ भी आधा-अधूरा न छूटे।
PATCHrequest का असली खतरा गलत फ़ील्ड लिखना नहीं है। खतरा यह मान लेना है कि लिखावट सफल हो गई, जबकि चुपचाप किसी और का बदलाव overwrite हो गया। JSON Patch आपको उपकरण देता है, लेकिन safety design अभी भी आपका ही काम है।
1. RFC 6902 operation समूह की त्वरित समीक्षा
JSON Patch बदलावों को operations के array के रूप में बताता है, जिसका media type application/json-patch+json है। छह operations array के क्रम में निष्पादित होते हैं:
| Operation | प्रभाव | मुख्य बाधा |
|---|---|---|
add |
सदस्य जोड़ें या array element डालें | array index लंबाई के बराबर हो सकता है; - का अर्थ अंत |
remove |
सदस्य या element हटाएँ | लक्ष्य पथ मौजूद होना चाहिए |
replace |
मौजूदा मान बदलें | लक्ष्य पथ मौजूद होना चाहिए |
move |
node स्थानांतरित करें | node को उसके अपने child पथ में नहीं ले जा सकते |
copy |
node की प्रतिलिपि बनाएँ | स्रोत पथ मौजूद होना चाहिए |
test |
मान की समानता जाँचें | विफल होने पर पूरा patch लागू नहीं होता |
कई लोग केवल पहले तीन याद रखते हैं और भूल जाते हैं कि test ही एक अंधेरे write को conditional write बनाता है। पूरी operation semantics और array सीमाएँ JSON Pointer, Patch और Merge Patch में कवर की गई हैं।
2. test से Patch को conditional update बनाना
एक विशिष्ट optimistic-locking patch लिखने से नहीं, बल्कि version assertion से शुरू होता है:
{"op":"test","path":"/version","value":1}
इसे एक पूरे patch में रखें:
[
{ "op": "test", "path": "/version", "value": 1 },
{ "op": "replace", "path": "/version", "value": 2 },
{ "op": "replace", "path": "/status", "value": "paid" }
]
तर्क यह है: पुष्टि करें कि दस्तावेज़ का version अभी 1 है, फिर उसे 2 कर दें। अगर कोई अन्य request पहले ही version को 2 तक बढ़ा चुका है, तो पहला test विफल हो जाता है, पूरा patch लागू नहीं होता, और server conflict लौटाता है। यह distributed lock जोड़े बिना lost updates रोकता है।
कुछ design बिंदु:
- हर write से पहले
testरखें। अगर कोई पहला write पहले ही प्रभावी हो चुका है, तो assertion का कोई अर्थ नहीं; - server द्वारा उत्पन्न version number या digest पर assertion करें, किसी ऐसे सामान्य फ़ील्ड पर नहीं जिसे client स्वेच्छा से बदल सके;
- हर सफल write पर version बढ़ाएँ; वरना एक ही पुराने version वाले दो requests दोनों पास हो जाएँगे;
testकाvalueदस्तावेज़ के मान के JSON type के बिल्कुल बराबर होना चाहिए।1और"1"बराबर नहीं हैं।
3. पथ escaping और prototype pollution
JSON Pointer के escaping नियम सरल हैं: ~ को ~0 और / को ~1 लिखें, और / से पहले ~ बदलें, वरना double escaping हो जाती है।
Key "a/b" → /a~1b
Key "m~n" → /m~0n
Key "a~/b" → /a~0~1b
बाहरी patches के साथ जोखिम सामान्यतः escaping नहीं, बल्कि खतरनाक keys होता है। JavaScript में __proto__, constructor और prototype जैसी keys prototype pollution भड़का सकती हैं, सामान्य add के ज़रिए भी:
[
{ "op": "add", "path": "/__proto__/polluted", "value": true }
]
Denylist के बजाय allowlist इस्तेमाल करें:
- Patches को केवल business फ़ील्ड बदलने दें, जैसे
/statusया/items/0/quantity; - हर
pathऔरfromको regular expression या path prefix के विरुद्ध जाँचें, और array indices के अलावा अप्रत्याशित स्तर अस्वीकार करें; - JSON को
Object.create(null)याMapcontainers में parse करें ताकि polluted prototype chain का प्रभाव कम हो; - Operations की संख्या और हर
valueके byte आकार की सीमा तय करें।
4. Atomic application और rollback
RFC 6902 atomic निष्पादन की अपेक्षा करता है, लेकिन atomicity एक semantic अपेक्षा है, हर library का default नहीं। fast-json-patch जैसी libraries mutateDocument के true होने पर दस्तावेज़ को जगह पर ही बदल देती हैं, और विफलता पर आंशिक बदलाव छूट सकते हैं।
import { applyPatch, validate } from 'fast-json-patch';
/**
* Patch को immutably लागू करें और विफलता पर मूल दस्तावेज़ लौटाएँ।
* @param document मूल दस्तावेज़, जिसे caller को नहीं बदलना चाहिए
* @param patch लागू करने के लिए JSON Patch operation array
* @returns परिणाम, जिसमें success flag और नया दस्तावेज़ शामिल हैं
*/
function applySafely(document: unknown, patch: unknown[]) {
const errors = validate(patch, document);
if (errors) {
return { ok: false, document };
}
try {
const result = applyPatch(document, patch, false, false);
return { ok: true, document: result.newDocument };
} catch {
return { ok: false, document };
}
}
तीन implementation सुझाव:
- पहले deep copy या structured-clone करें, प्रति पर लागू करें, और सफलता के बाद ही reference बदलें;
- library के स्वतः rollback पर निर्भर न रहें; exceptions पर व्यवहार implementations में अलग होता है;
- निष्पादन से पहले validate करें:
validateकेवल संरचना जाँचता है, जबकि semantic conflicts के लिए अभी भीtestचाहिए; दोनों एक-दूसरे का विकल्प नहीं हैं।
अगर कोई patch कई resources तक फैला हो, जैसे एक साथ order और उसका inventory बदलना, तो JSON Patch की atomicity केवल एक दस्तावेज़ के भीतर लागू होती है। दस्तावेज़ों के पार database transaction या compensating actions चाहिए।
5. Idempotency: PATCH स्वभाव से idempotent नहीं है
HTTP semantics PUT और DELETE को idempotent परिभाषित करते हैं, लेकिन PATCH ज़रूरी नहीं। एक ही JSON Patch दोहराने पर पूरी तरह अलग परिणाम मिल सकते हैं:
add /items/-हर बार array के अंत में जोड़ता है, इसलिए retries duplicates बना देते हैं;replace /versionversion 1 को 2 करता है और retry पर फिर सफल हो जाता है, भले ही operation पहले ही लागू हो चुका हो;remove /tempदूसरी बार विफल होता है क्योंकि पथ अब मौजूद नहीं है।
Idempotency पाने के तीन आम तरीके हैं:
| तरीका | कैसे काम करता है | किसके लिए सर्वोत्तम |
|---|---|---|
| Version precondition | पहला operation test /version, फिर increment |
जिन resources का स्पष्ट version हो |
| Idempotency key | Client Idempotency-Key भेजता है; server परिणाम cache करता है |
Payments, orders और अन्य writes |
| Target-state patch | add के बजाय replace, ताकि परिणाम केवल अंतिम मान पर निर्भर हो |
Configuration और status फ़ील्ड |
सबसे बुरा पैटर्न वह patch है जो add और remove मिलाता है और retries को सुरक्षित मान लेता है। अगर कोई patch कुछ जोड़ता या हटाता है, तो उसे version assertion या idempotency key के साथ जोड़ना ही होगा।
6. Concurrent conflicts: ETag और If-Match
Patch के अंदर test के अलावा HTTP एक मानक तंत्र देता है: server ETag लौटाता है, और client अगली request पर If-Match भेजता है।
GET /orders/42 HTTP/1.1
Host: api.example.com
HTTP/1.1 200 OK
ETag: "a3f9c1"
Content-Type: application/json
PATCH /orders/42 HTTP/1.1
Host: api.example.com
If-Match: "a3f9c1"
Content-Type: application/json-patch+json
[{"op":"replace","path":"/status","value":"paid"}]
अगर resource बदल गया है, तो server 412 Precondition Failed लौटाता है, और client फिर से fetch करके retry करता है। If-Match: * का अर्थ है "जब तक resource मौजूद है तब तक निष्पादित करो", जो overwrites रोकने की ज़रूरत पड़ने पर उपयुक्त नहीं है।
test और If-Match का अंतर:
If-MatchHTTP परत पर body parsing से पहले जाँचा जाता है, इसलिए यह सस्ता है;testpatch की semantic परत पर जाँचा जाता है और server-side ETag समर्थन पर निर्भर किए बिना किसी भी फ़ील्ड पर assertion कर सकता है;- इन्हें जोड़ा जा सकता है, लेकिन केवल एक लागू करके यह दावा न करें कि endpoint concurrency के तहत सुरक्षित है।
7. Library अंतर और चयन
| Implementation | भाषा | Atomicity | test समर्थन |
टिप्पणियाँ |
|---|---|---|---|---|
fast-json-patch |
JavaScript | वैकल्पिक (डिफ़ॉल्ट रूप से नया दस्तावेज़ लौटाता है) | हाँ | व्यापक ecosystem; mutateDocument पर ध्यान दें |
jsonpatch |
Python | exceptions पर rollback | हाँ | मानक के निकट, स्पष्ट errors के साथ |
json-patch |
Go | प्रति पर काम करता है | हाँ | अक्सर JSON Merge Patch के साथ इस्तेमाल |
zjsonpatch |
Java | हाँ | हाँ | सुविधाजनक Jackson integration |
किसी library को केवल "सभी छह operations समर्थित हैं" के आधार पर न चुनें। तीन बातें पक्की करें: विफलता पर rollback होता है या नहीं, test सख्ती से तुलना करता है या नहीं, और array में सीमा से बाहर का add error उठाता है या नहीं। ढीली implementations एक ही patch को अलग-अलग वातावरणों में अलग व्यवहार दिलाती हैं।
8. लॉन्च से पहले safety checklist
- [ ] Endpoint
application/json-patch+jsonघोषित करता है; - [ ] Patches प्रति पर लागू होते हैं, और विफलता मूल दस्तावेज़ को कभी दूषित नहीं करती;
- [ ] बाहरी patches में हर
pathऔरfromallowlist जाँच पास करता है; - [ ]
__proto__,constructorऔरprototypeजैसी खतरनाक keys अस्वीकार होती हैं; - [ ] Operation संख्या और एकल मान के आकार की सीमा तय है;
- [ ]
testयाIf-Matchconcurrency नियंत्रण देता है; - [ ] Patches audit log में actor, timestamp और पूरे operation array के साथ लिखे जाते हैं;
- [ ] Patch कई दस्तावेज़ों तक फैले तो transaction सीमाएँ स्पष्ट हैं।
Patch स्वभाव से "क्या बदला" का पूरा विवरण होता है, जिससे यह स्वाभाविक audit record बनता है। API परत पर error codes और idempotency के लिए JSON API Design पढ़ें। अगर patch दो branches को merge करने से आता है, तो आपको conflict semantics भी चाहिए, जो JSON फ़ाइलों के लिए Git Merging में कवर हैं।
FAQ
विफल test operation कौन-सा status code लौटाना चाहिए?
अगर conflict version assertion से आता है, तो 409 Conflict और 412 Precondition Failed दोनों उचित हैं। If-Match के साथ एकरूपता बनाए रखने के लिए 412 पसंद करें, और response body में वर्तमान version शामिल करें ताकि client retry कर सके।
add और remove वाला patch सुरक्षित रूप से retry हो सकता है?
सीधे नहीं। Array के अंत में add डेटा की नकल बना देता है, और remove दूसरी बार विफल होता है क्योंकि पथ जा चुका होता है। Version precondition या idempotency key जोड़ें ताकि retries को एक ही operation माना जाए।
क्या JSON Patch क्रॉस-दस्तावेज़ transactions की गारंटी दे सकता है?
नहीं। RFC 6902 की atomicity एक JSON दस्तावेज़ पर लागू होती है। जब orders, inventory या अन्य resources शामिल हों, तो consistency के लिए database transactions, Saga compensation या message queue इस्तेमाल करें।
Patch को __proto__ पर कभी क्यों नहीं लिखना चाहिए?
क्योंकि अधिकांश JavaScript implementations दस्तावेज़ को सामान्य objects में रखती हैं, __proto__ लिखने से prototype chain बदल सकती है और बाद का हर object प्रभावित हो सकता है। Path allowlist सबसे भरोसेमंद बचाव है; केवल library की आंतरिक filtering पर निर्भर न रहें।
PATCH request को idempotent कैसे बनाऊँ?
तीन आम तरीके हैं: patch को test /version से शुरू करें और सफलता पर version बढ़ाएँ, server Idempotency-Key के लिए परिणाम cache करे, या add के बजाय replace इस्तेमाल करें ताकि परिणाम केवल अंतिम मान पर निर्भर हो। अगर patch कुछ जोड़ता या हटाता है, तो उसे version assertion या idempotency key के साथ जोड़ना ही होगा; उसे सिर्फ़ retry नहीं किया जा सकता।
क्या test और If-Match दोनों इस्तेमाल करना चाहिए?
दोनों का उपयोग ज़रूरी नहीं, लेकिन केवल एक लागू करने से endpoint concurrency के तहत सुरक्षित नहीं हो जाता। If-Match HTTP परत पर body parsing से पहले जाँचा जाता है, इसलिए सस्ता है; test patch की semantic परत पर जाँचा जाता है और server-side ETag समर्थन पर निर्भर किए बिना किसी भी फ़ील्ड पर assertion कर सकता है।