প্রোডাকশনে JSON Patch: পারমাণবিকতা, ইডেম্পোটেন্সি ও সমবর্তী সংঘর্ষ
সংক্ষেপে: JSON Patch কেবল একটি পরিবর্তন ফরম্যাট; পারমাণবিকতা, ইডেম্পোটেন্সি ও সমবর্তীতা তবু আপনার দায়িত্ব। test পূর্বশর্ত, পাথ এস্কেপিং, রোলব্যাক ও ETag সমন্বয়।

মূল কথা: আগে প্যাচের লক্ষ্য পাথ ও অনুমতি যাচাই করুন, তারপর test বা If-Match দিয়ে কনকারেন্সি দাবি যোগ করুন; প্যাচ একটি কপিতে প্রয়োগ করুন যাতে ব্যর্থতায় কিছুই অর্ধ-লেখা না থাকে।
একটি
PATCHরিকোয়েস্টের আসল বিপদ ভুল ফিল্ড লেখা নয়। বিপদ হলো লেখা সফল মনে করা, অথচ নীরবে অন্যের পরিবর্তন ওভাররাইট করা। JSON Patch আপনাকে টুল দেয়, কিন্তু নিরাপত্তা নকশা তবু আপনার।
১. RFC 6902 অপারেশন সেট দ্রুত পুনরালোচনা
JSON Patch পরিবর্তন বর্ণনা করে অপারেশনের অ্যারেতে, মিডিয়া টাইপ application/json-patch+json। ছয় অপারেশন অ্যারের ক্রমে চলে:
| অপারেশন | প্রভাব | মূল বাধ্যবাধকতা |
|---|---|---|
add |
সদস্য যোগ বা অ্যারে এলিমেন্ট ঢোকায় | অ্যারে ইনডেক্স দৈর্ঘ্যের সমান হতে পারে; - মানে শেষ |
remove |
সদস্য বা এলিমেন্ট সরায় | লক্ষ্য পাথ অবশ্যই থাকতে হবে |
replace |
বিদ্যমান মান বদলায় | লক্ষ্য পাথ অবশ্যই থাকতে হবে |
move |
নোড সরায় | নিজের চাইল্ড পাথে নোড সরানো যাবে না |
copy |
নোড কপি করে | উৎস পাথ অবশ্যই থাকতে হবে |
test |
মান সমতা দাবি করে | ব্যর্থ হলে পুরো প্যাচ প্রয়োগ হয় না |
অনেকে কেবল প্রথম তিনটি মনে রাখে আর ভুলে যায় test-ই অন্ধ লেখাকে শর্তযুক্ত লেখায় বদলায়। সম্পূর্ণ অপারেশন সেমান্টিক্স ও অ্যারেতে সীমানা আলোচিত JSON Pointer, Patch ও Merge Patch-এ।
২. test দিয়ে প্যাচকে শর্তযুক্ত হালনাগাদে বদলানো
একটি আদর্শ অপ্টিমিস্টিক-লকিং প্যাচ লেখা দিয়ে নয়, সংস্করণ দাবি দিয়ে শুরু হয়:
{"op":"test","path":"/version","value":1}
এটিকে সম্পূর্ণ প্যাচে বসান:
[
{ "op": "test", "path": "/version", "value": 1 },
{ "op": "replace", "path": "/version", "value": 2 },
{ "op": "replace", "path": "/status", "value": "paid" }
]
লজিক: নিশ্চিত করো ডকুমেন্টের সংস্করণ এখন ১, তারপর তা ২ করো। অন্য রিকোয়েস্ট ইতিমধ্যে সংস্করণ ২-তে তুললে প্রথম test ব্যর্থ হয়, পুরো প্যাচ প্রয়োগ হয় না, আর সার্ভার সংঘর্ষ ফেরত দেয়। এটি ডিস্ট্রিবিউটেড লক না এনেই হারানো হালনাগাদ ঠেকায়।
কয়েকটি নকশা বিষয়:
- প্রতিটি লেখার আগে
testদিন। আগের লেখা ইতিমধ্যে কার্যকর হলে দাবি অর্থহীন; - সার্ভারের তৈরি সংস্করণ সংখ্যা বা ডাইজেস্টে দাবি করুন, ক্লায়েন্টের অবাধে বদলানো সাধারণ ফিল্ডে নয়;
- প্রতিটি সফল লেখায় সংস্করণ বাড়ান; নাহলে একই পুরোনো সংস্করণ ব্যবহারকারী দুই রিকোয়েস্টই পাস করে;
test-এরvalueডকুমেন্ট মানের সঙ্গে ঠিক একই JSON টাইপ হতে হবে।1ও"1"সমান নয়।
৩. পাথ এস্কেপিং ও প্রোটোটাইপ দূষণ
JSON Pointer এস্কেপিং নিয়ম সরল: ~ লিখুন ~0 আর / লিখুন ~1, আর /-এর আগে ~ প্রতিস্থাপন করুন, নাহলে দ্বিগুণ এস্কেপ হয়।
Key "a/b" → /a~1b
Key "m~n" → /m~0n
Key "a~/b" → /a~0~1b
বাইরের প্যাচে ঝুঁকি সাধারণত এস্কেপিং নয়, বিপজ্জনক কি। JavaScript-এ __proto__, constructor ও prototype-এর মতো কি প্রোটোটাইপ দূষণ ঘটাতে পারে, সাধারণ add দিয়েও:
[
{ "op": "add", "path": "/__proto__/polluted", "value": true }
]
ডেনিলিস্টের বদলে হোয়াইটলিস্ট ব্যবহার করুন:
- প্যাচকে কেবল ব্যবসায়িক ফিল্ড, যেমন
/statusবা/items/0/quantity, বদলাতে দিন; - প্রতিটি
pathওfromরেগুলার এক্সপ্রেশন বা পাথ প্রিফিক্স দিয়ে যাচাই করুন, অ্যারে ইনডেক্স ছাড়া অপ্রত্যাশিত স্তর প্রত্যাখ্যান করুন; - JSON
Object.create(null)বাMapকন্টেইনারে পার্স করুন যাতে দূষিত প্রোটোটাইপ চেইনের প্রভাব কমে; - অপারেশন সংখ্যা ও প্রতিটি
value-এর বাইট আকার ক্যাপ করুন।
৪. পারমাণবিক প্রয়োগ ও রোলব্যাক
RFC 6902 পারমাণবিক কার্যকর বাধ্যতামূলক করে, কিন্তু পারমাণবিকতা একটি সেমান্টিক দাবি, প্রতিটি লাইব্রেরির ডিফল্ট নয়। fast-json-patch-এর মতো লাইব্রেরি mutateDocument সত্য হলে ডকুমেন্ট জায়গায় বদলায়, আর ব্যর্থতা আংশিক পরিবর্তন রেখে যায়।
import { applyPatch, validate } from 'fast-json-patch';
/**
* Apply a patch immutably and return the original document on failure.
* @param document The original document, which callers must not mutate
* @param patch The JSON Patch operation array to apply
* @returns The result, including a success flag and the new document
*/
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 };
}
}
তিনটি বাস্তবায়ন সুপারিশ:
- আগে গভীর কপি বা structured clone, কপিতে প্রয়োগ করুন, সফল হলে তবেই রেফারেন্স অদলবদল করুন;
- লাইব্রেরির স্বয়ংক্রিয় রোলব্যাকের উপর নির্ভর করবেন না; এক্সসেপশনে আচরণ বাস্তবায়নভেদে আলাদা;
- কার্যকরের আগে যাচাই করুন:
validateকেবল কাঠামো দেখে, অথচ সেমান্টিক সংঘর্ষে তবুtestদরকার; কোনোটিই অন্যটিকে প্রতিস্থাপন করে না।
প্যাচ একাধিক রিসোর্স জুড়ে থাকলে, যেমন অর্ডার ও তার ইনভেন্টরি একসঙ্গে বদলানো, JSON Patch পারমাণবিকতা কেবল একটি ডকুমেন্টের ভেতরে খাটে। ডকুমেন্ট ছাড়িয়ে ডেটাবেস লেনদেন বা ক্ষতিপূরণমূলক ক্রিয়া দরকার।
৫. ইডেম্পোটেন্সি: PATCH স্বাভাবিকভাবে ইডেম্পোটেন্ট নয়
HTTP সেমান্টিক্স PUT ও DELETE-কে ইডেম্পোটেন্ট সংজ্ঞায়িত করে, কিন্তু PATCH অগত্যা নয়। একই JSON Patch পুনরাবৃত্তি সম্পূর্ণ ভিন্ন ফল দিতে পারে:
add /items/-প্রতিবার অ্যারের শেষে যোগ করে, তাই রিট্রাই ডুপ্লিকেট তৈরি করে;replace /versionসংস্করণ ১-কে ২ করে আর রিট্রাইতে আবার সফল হয়, যদিও অপারেশন ইতিমধ্যে প্রয়োগ হয়েছে;remove /tempদ্বিতীয় রানে ব্যর্থ হয় কারণ পাথ আর নেই।
ইডেম্পোটেন্সি অর্জনের তিন প্রচলিত পথ:
| পদ্ধতি | কীভাবে কাজ করে | উপযুক্ত |
|---|---|---|
| সংস্করণ পূর্বশর্ত | প্রথম অপারেশন test /version, তারপর বৃদ্ধি |
স্পষ্ট সংস্করণযুক্ত রিসোর্স |
| ইডেম্পোটেন্সি কি | ক্লায়েন্ট Idempotency-Key পাঠায়; সার্ভার ফল ক্যাশ করে |
পেমেন্ট, অর্ডার ও অন্যান্য লেখা |
| লক্ষ্য-অবস্থা প্যাচ | add-এর বদলে replace, তাই ফল কেবল চূড়ান্ত মানের উপর নির্ভর করে |
কনফিগারেশন ও স্টেটাস ফিল্ড |
সবচেয়ে খারাপ প্যাটার্ন হলো add ও remove মিশিয়ে রিট্রাই নিরাপদ ধরে নেওয়া প্যাচ। প্যাচ কিছু যোগ বা মুছলে অবশ্যই সংস্করণ দাবি বা ইডেম্পোটেন্সি কি-এর সঙ্গে জোড়া দিতে হবে।
৬. সমবর্তী সংঘর্ষ: ETag ও If-Match
প্যাচের ভেতরের test ছাড়াও HTTP একটি স্ট্যান্ডার্ড ব্যবস্থা দেয়: সার্ভার ETag ফেরত দেয়, আর ক্লায়েন্ট পরের রিকোয়েস্টে 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"}]
রিসোর্স বদলালে সার্ভার 412 Precondition Failed ফেরত দেয়, আর ক্লায়েন্ট রিট্রাইয়ের আগে আবার ফেচ করে। If-Match: * মানে "রিসোর্স থাকলেই চালাও", যা ওভাররাইট আটকাতে চাইলে উপযুক্ত নয়।
test ও If-Match-এর পার্থক্য:
If-MatchHTTP স্তরে বডি পার্সের আগে যাচাই হয়, তাই সস্তা;testপ্যাচ সেমান্টিক স্তরে যাচাই হয় ও সার্ভার-পক্ষের ETag সমর্থনের উপর নির্ভর না করে যেকোনো ফিল্ডে দাবি করতে পারে;- দুটো মিলিয়ে ব্যবহার করা যায়, কিন্তু কেবল একটি বাস্তবায়ন করে এন্ডপয়েন্ট সমবর্তীতায় নিরাপদ দাবি করবেন না।
৭. লাইব্রেরির পার্থক্য ও বাছাই
| বাস্তবায়ন | ভাষা | পারমাণবিকতা | test সমর্থন |
নোট |
|---|---|---|---|---|
fast-json-patch |
JavaScript | ঐচ্ছিক (ডিফল্টে নতুন ডকুমেন্ট ফেরত) | হ্যাঁ | বিস্তৃত ইকোসিস্টেম; mutateDocument খেয়াল রাখুন |
jsonpatch |
Python | এক্সসেপশনে রোলব্যাক | হ্যাঁ | স্ট্যান্ডার্ডের কাছাকাছি, স্পষ্ট এরর |
json-patch |
Go | কপিতে কাজ করে | হ্যাঁ | প্রায়ই JSON Merge Patch-এর সঙ্গে ব্যবহৃত |
zjsonpatch |
Java | হ্যাঁ | হ্যাঁ | Jackson ইন্টিগ্রেশন সুবিধাজনক |
কেবল "ছয় অপারেশনই সমর্থন করে" দেখে লাইব্রেরি বাছবেন না। তিনটি নিশ্চিত করুন: ব্যর্থতা রোলব্যাক করে কি না, test কঠোরভাবে তুলনা করে কি না, আর রেঞ্জের বাইরের অ্যারে add এরর দেয় কি না। শিথিল বাস্তবায়ন একই প্যাচকে পরিবেশভেদে আলাদা আচরণ করায়।
৮. প্রকাশের আগে নিরাপত্তা চেকলিস্ট
- [ ] এন্ডপয়েন্ট
application/json-patch+jsonঘোষণা করে; - [ ] প্যাচ কপিতে প্রয়োগ হয়, ব্যর্থতা কখনো মূল ডকুমেন্ট দূষিত করে না;
- [ ] বাইরের প্যাচে প্রতিটি
pathওfromহোয়াইটলিস্ট চেক পাস করে; - [ ]
__proto__,constructorওprototype-এর মতো বিপজ্জনক কি প্রত্যাখ্যাত; - [ ] অপারেশন সংখ্যা ও একক মানের আকার ক্যাপ করা;
- [ ]
testবাIf-Matchকনকারেন্সি কন্ট্রোল দেয়; - [ ] প্যাচ অডিট লগে লেখা হয় — অভিনেতা, টাইমস্ট্যাম্প ও সম্পূর্ণ অপারেশন অ্যারেসহ;
- [ ] প্যাচ ডকুমেন্ট ছাড়ালে লেনদেন সীমানা স্পষ্ট।
প্যাচ স্বাভাবিকভাবেই "কী বদলেছে"-র সম্পূর্ণ বর্ণনা, যা একে স্বাভাবিক অডিট রেকর্ড বানায়। API স্তরে এরর কোড ও ইডেম্পোটেন্সির জন্য JSON API ডিজাইন চালিয়ে যান। প্যাচ দুই ব্র্যাঞ্চ মার্জ থেকে এলে সংঘর্ষ সেমান্টিক্সও দরকার, আলোচিত JSON ফাইলে Git মার্জ-এ।
সাধারণ প্রশ্ন
ব্যর্থ test অপারেশন কী স্টেটাস কোড ফেরত দেবে?
সংঘর্ষ সংস্করণ দাবি থেকে এলে 409 Conflict ও 412 Precondition Failed — দুটোই যুক্তিযুক্ত। If-Match-এর সঙ্গে সামঞ্জস্য রাখতে 412-কে অগ্রাধিকার দিন, আর রেসপন্স বডিতে বর্তমান সংস্করণ দিন যাতে ক্লায়েন্ট রিট্রাই করতে পারে।
add ও remove-যুক্ত প্যাচ নিরাপদে রিট্রাই করা যায়?
সোজা নয়। অ্যারের শেষে add ডেটা ডুপ্লিকেট করে, আর remove দ্বিতীয় রানে ব্যর্থ হয় কারণ পাথ নেই। সংস্করণ পূর্বশর্ত বা ইডেম্পোটেন্সি কি যোগ করুন যাতে রিট্রাই একই অপারেশন হিসেবে চেনা যায়।
JSON Patch কি ডকুমেন্ট-ছাড়ানো লেনদেন নিশ্চিত করতে পারে?
না। RFC 6902 পারমাণবিকতা একটি JSON ডকুমেন্টে প্রযোজ্য। অর্ডার, ইনভেন্টরি বা অন্য রিসোর্স জড়ালে সামঞ্জস্যের জন্য ডেটাবেস লেনদেন, Saga ক্ষতিপূরণ, বা মেসেজ কিউ ব্যবহার করুন।
প্যাচ কখনো __proto__-তে লেখা উচিত নয় কেন?
কারণ বেশিরভাগ JavaScript বাস্তবায়ন ডকুমেন্ট সাধারণ অবজেক্টে রাখে, __proto__ লেখা প্রোটোটাইপ চেইন বদলে পরবর্তী প্রতিটি অবজেক্টকে প্রভাবিত করতে পারে। পাথ হোয়াইটলিস্ট সবচেয়ে নির্ভরযোগ্য প্রতিরক্ষা; কেবল লাইব্রেরির অভ্যন্তরীণ ফিল্টারের উপর নির্ভর করবেন না।
PATCH রিকোয়েস্ট ইডেম্পোটেন্ট কীভাবে করব?
তিন প্রচলিত পথ: প্যাচ test /version দিয়ে শুরু করে সফলতায় সংস্করণ বাড়ানো, সার্ভারকে Idempotency-Key-এর জন্য ফল ক্যাশ করতে দেওয়া, বা add-এর বদলে replace ব্যবহার করে ফল কেবল চূড়ান্ত মানের উপর নির্ভরশীল করা। প্যাচ কিছু যোগ বা মুছলে অবশ্যই সংস্করণ দাবি বা ইডেম্পোটেন্সি কি-এর সঙ্গে জোড়া দিতে হবে; সোজা রিট্রাই করা যাবে না।
test ও If-Match — দুটোই ব্যবহার করা উচিত?
দুটোই ব্যবহার করা বাধ্যতামূলক নয়, কিন্তু কেবল একটি বাস্তবায়ন করলে এন্ডপয়েন্ট সমবর্তীতায় নিরাপদ হয় না। If-Match HTTP স্তরে বডি পার্সের আগে যাচাই হয়, তাই সস্তা; test প্যাচ সেমান্টিক স্তরে যাচাই হয় ও সার্ভার-পক্ষের ETag সমর্থনের উপর নির্ভর না করে যেকোনো ফিল্ডে দাবি করতে পারে।