JSON Toolkit

JSON চুক্তির সংস্করণায়ন: সামঞ্জস্য, ডেপ্রিকেশন ও ডেটা মাইগ্রেশন

সংক্ষেপে: ফিল্ড যোগ করা স্বয়ংক্রিয়ভাবে নিরাপদ নয়। সামঞ্জস্যের সীমানা, সংস্করণ ফিল্ড, ডেপ্রিকেশন উইন্ডো, দুই-লেখা মাইগ্রেশন, চুক্তি টেস্ট ও ক্যানারি মেট্রিক।

উন্নত~10 মিনিট পড়াপ্রকাশ 2026-10-31আপডেট 2026-10-31
JSON চুক্তির সংস্করণায়ন, সামঞ্জস্য ও ডেটা মাইগ্রেশনের কভার

মূল কথা: সংস্করণ বিবর্তন চালান expand, backfill, তারপর contract হিসেবে, আর সংস্করণটি ডকুমেন্টের ভেতরেই রাখুন; কিছু প্রকাশের আগে নিশ্চিত করুন ভোক্তারা অজানা ফিল্ড ও এনাম মান সহ্য করে, আর ডেপ্রিকেশন freeze, announce, observe, remove — এই ক্রমে নিন।

সংস্করণায়নের কঠিন অংশ কোড বদলানো নয়; কঠিন অংশ হলো আপনি জানেন না কে এখনও পুরোনো ফিল্ড পড়ে। সামঞ্জস্য কখনো একমুখী প্রতিশ্রুতি নয় — এটি প্রকাশক ও অজানা ভোক্তার মধ্যে চলমান সমন্বয়।

১. সামঞ্জস্যের সীমানা: সংযোজন স্বয়ংক্রিয়ভাবে নিরাপদ নয়

পাঠ্যবই নতুন ঐচ্ছিক ফিল্ডকে "পশ্চাৎ-সামঞ্জস্যপূর্ণ" বলে, কিন্তু তা কেবল তখনই খাটে যখন ভোক্তারা অজানা ফিল্ড সহ্য করে। ফিল্ড যোগ করা অন্তত তিন পরিস্থিতিতে ক্লায়েন্ট ভাঙে:

  • ভোক্তা এমন স্কিমা দিয়ে যাচাই করে যা additionalProperties: false সেট করে;
  • ভোক্তা অবজেক্ট নির্দিষ্ট ক্রমে হাঁটে, বা কি দিয়ে নয় বরং অবস্থান দিয়ে মান পড়ে;
  • নতুন ফিল্ড পুরোনো নাম পুনরায় ব্যবহার করে অর্থ বদলায়, যেমন status-কে "অর্ডার স্টেটাস" থেকে "ফুলফিলমেন্ট স্টেটাস"-এ প্রশস্ত করা।

নিরাপদ সংযোজনে তিন পূর্বশর্ত: ফিল্ড ঐচ্ছিক, এর অর্থ স্বাধীন, আর ভোক্তারা অজানা কি উপেক্ষা করে। সংস্করণ সংখ্যা নিয়ে তর্কের আগেই তিনটি নিশ্চিত করুন।

{
  "schemaVersion": 2,
  "orderId": "ord-8842",
  "status": "paid",
  "refundDeadline": "2026-11-30T00:00:00+08:00",
  "channel": "wechat"
}

এখানে refundDeadline ও channel ঐচ্ছিক v2 সংযোজন, অথচ orderId ও status মূল সেমান্টিক্স ধরে রাখে। schemaVersion কেবল HTTP হেডারে না থেকে ডকুমেন্টের সঙ্গে সংরক্ষিত।

২. যে ভাঙনকারী পরিবর্তন প্রকাশ করা যায় না

নিচের পরিবর্তনগুলোকে নতুন সংস্করণ বা দুই-লেখা রূপান্তর দরকার এমন লাল রেখা ধরুন। ডক হালনাগাদ আপনাকে বাঁচাবে না:

পরিবর্তন সামঞ্জস্য ভাঙে কেন গ্রহণযোগ্য বিকল্প
ফিল্ড সরানো ভোক্তা সঙ্গে সঙ্গে undefined পড়ে ডেপ্রিকেট করুন, দেখুন, উইন্ডোর পরে সরান
ফিল্ড টাইপ বদলানো "1" হয় 1; কঠোর টাইপযুক্ত ক্লায়েন্ট সরাসরি ব্যর্থ, শিথিলগুলো প্রায়ই ভ্যালিডেশনে ব্যর্থ নতুন ফিল্ড-নাম যোগ করুন ও রূপান্তরকালে দুইটিতে লিখুন
বাধ্যতামূলক ফিল্ড যোগ পুরোনো কলার পাঠায় না, তাই ভ্যালিডেশন সবসময় ব্যর্থ ডিফল্ট দিন, বা কেবল নতুন সংস্করণে বাধ্যতামূলক করুন
এনাম মান-সেট বদলানো ভোক্তার switch-এ ফলব্যাক শাখা থাকে না আগে ক্লায়েন্টকে অজানা মান গ্রহণ শেখান, তারপর এনাম প্রসারিত করুন
ফিল্ডের অর্থ বদলানো টাইপ বৈধ থাকে অথচ আচরণ বদলায় — ডিবাগ করা সবচেয়ে কঠিন নতুন ফিল্ড ধরুন ও পুরোনো সেমান্টিক্স জমিয়ে রাখুন
নেস্টিং গভীরতা বদলানো প্রতিটি অ্যাক্সেস পাথ বদলাতে হয় পুরোনো আকৃতি রাখুন ও সমতল ফিল্ড বা expand প্যারামিটার দিন

অর্থ বদলানো সবচেয়ে বিপজ্জনক সারি: স্কিমা ভ্যালিডেশন সবুজ থাকে, মনিটরিং কোনো এরর বলে না, কেবল ব্যবসায়িক মেট্রিক সরে। অর্থের যেকোনো পরিবর্তন নতুন ফিল্ড হিসেবে প্রকাশ করা উচিত।

৩. সংস্করণ কোথায় থাকে: ডকুমেন্ট, URL, মিডিয়া টাইপ

তিন প্রচলিত অবস্থান ভিন্ন উদ্দেশ্য সার্ভ করে:

অবস্থান উদাহরণ উপযুক্ত সীমাবদ্ধতা
ডকুমেন্ট ফিল্ড "schemaVersion": 2 ডেটাবেস, মেসেজ কিউ, অফলাইন ব্যাকআপ পরিবহন স্তর এর উপর রুট করতে পারে না
URL পাথ /v2/orders/8842 পাবলিক API রিসোর্স ঠিকানা সংস্করণের সঙ্গে বাঁধে ও মাইগ্রেশন দামি করে
মিডিয়া টাইপ application/vnd.acme.order.v2+json কন্টেন্ট নেগোশিয়েশন দরকার এমন অভ্যন্তরীণ সার্ভিস কলারকে Accept ঠিকভাবে সেট করতে হয়

ব্যবহারিক শ্রমবিভাজন: তারে মিডিয়া-টাইপ নেগোশিয়েশন ব্যবহার করুন, আর ডকুমেন্টে সবসময় সংস্করণ ফিল্ড সংরক্ষণ করুন। কিউয়ের পুরোনো মেসেজ URL বদলালে আপগ্রেড হবে না, ব্যাকআপ ফাইলও হবে না। কেবল ডকুমেন্টের ভেতরের schemaVersion রিপ্লে, ব্যাকফিল ও অডিটকে ভিত্তি দেয়।

GET /orders/ord-8842 HTTP/1.1
Host: api.bbdrug.com
Accept: application/vnd.acme.order.v2+json

URL-এ সংস্করণ রাখলে অন্তত পুরো ডেপ্রিকেশন উইন্ডোতে পুরোনো পাথ পুরোনো আকৃতি ফেরত দিক, চুপচাপ নতুন বাস্তবায়নে ইশারা না করে। বিস্তৃত API চুক্তি সিদ্ধান্তের জন্য দেখুন JSON API ডেটা চুক্তি নকশার মূল বিষয়।

৪. ডেপ্রিকেশন উইন্ডো খোলা

ডেপ্রিকেশন একক ঘোষণা নয়; শুরু, পর্যবেক্ষণকাল ও শেষ নিয়ে একটি উইন্ডো। কার্যকর ছন্দ:

  1. Freeze: পুরোনো ফিল্ড লেখা বন্ধ করুন, কিন্তু পড়া চালু রাখুন।
  2. Announce: ডক, চেঞ্জলগ ও রেসপন্স হেডার — তিন জায়গায় একসঙ্গে সরানোর তারিখ চিহ্নিত করুন।
  3. Observe: প্রতি কলারের পুরোনো ফিল্ড পড়া গুনুন, যতক্ষণ না পরপর দুই সময়কালে শূন্য থাকে।
  4. Remove: উইন্ডো বন্ধ, পড়া শূন্য ও রিগ্রেশন টেস্ট পাসের পরেই ফিল্ড ও তার সামঞ্জস্য কোড মুছুন।

Sunset রেসপন্স হেডার (RFC 8594) ক্লায়েন্টকে মেশিন-পাঠযোগ্য সরানোর তারিখ দেয়। Deprecation এখনও IETF খসড়া: ব্যবহারযোগ্য, কিন্তু প্রতিটি ক্লায়েন্ট বুঝবে ধরে নেবেন না। এর সঙ্গে মাইগ্রেশন গাইডে ইশারা করা Link হেডার জোড়া দিন:

Deprecation: true
Sunset: Wed, 31 Mar 2027 23:59:59 GMT
Link: <https://bbdrug.com/docs/deprecations/order-status>; rel="deprecation"; type="text/html"

উইন্ডোর দৈর্ঘ্য দলের প্রথা। সাধারণ তলানি একটি রিলিজ চক্র বা ৯০ দিন, যেটি দীর্ঘ। পাবলিক API বেশি পাক, অভ্যন্তরীণ সার্ভিস কম পেতে পারে, কিন্তু প্রতিটি কলারের আপগ্রেডের দরকারি সময়ের চেয়ে কখনো কম নয়।

৫. ডেপ্রিকেট করার আগে ভোক্তা খুঁজুন

ডেপ্রিকেট করার আগে "কে এখনও এটি পড়ে?"-র উত্তর দিতে পারতে হবে। নাহলে উইন্ডো আত্মপ্রতারণা। প্রমাণ নির্ভরযোগ্যতা অনুযায়ী সাজান:

  • গেটওয়ে লগ: appId, User-Agent ও ক্লায়েন্ট সংস্করণ অনুযায়ী পুরোনো-ফিল্ড অ্যাক্সেস একত্র করুন।
  • ডিস্ট্রিবিউটেড ট্রেস: সিরিয়ালাইজেশন স্তরে পুরোনো ফিল্ড ট্যাগ করুন যাতে ট্রেস সরাসরি কলিং সার্ভিসের নাম দেখায়।
  • কিউ ভোক্তা রেজিস্ট্রি: ভোক্তা গোষ্ঠীর তালিকা কোড সার্চের চেয়ে নির্ভরযোগ্য।
  • রিপোজিটরি সার্চ: rg কেবল আপনার নিজের কোড ঢাকে; বাইরের কলারের জন্য তবু লগ দরকার।
rg --type ts --type js 'legacyStatus|statusCode' src/ --stats

সার্চে হিট পাওয়া প্রমাণ করে "এই রিপোজিটরিতে কেউ এটি পড়ে", "আর কেউ না" নয়। গেটওয়ে লগ দিন ধরে এক্সপোর্ট করে নিশ্চিত করুন পুরোনো-ফিল্ড পড়া শূন্যে আছে, তারপর কিছু সরান।

৬. ডেটা মাইগ্রেশন: expand, backfill, contract

মার্টিন ফাউলারের Parallel Change তিন ধাপ বর্ণনা করে: expand (পুরোনো ও নতুন ফিল্ড একসঙ্গে থাকে এবং দুই সংস্করণই পড়া যায়), backfill (ঐতিহাসিক রেকর্ড নতুন আকৃতিতে সরানো), আর contract (পুরোনো ফিল্ড ও সামঞ্জস্য শাখা সরানো)। ক্রম উল্টাবেন না, নাহলে রোলব্যাক পথ থাকবে না।

// Migration skeleton: cursor by primary key, conditional writes for idempotency
async function migrateBatch(collection, cursor, batchSize = 500) {
  const records = await collection
    .find({ _id: { $gt: cursor } })
    .limit(batchSize)
    .toArray();

  if (records.length === 0) {
    return { nextCursor: cursor, done: true };
  }

  const operations = records
    .filter((record) => record.schemaVersion < 2)
    .map((record) => ({
      updateOne: {
        filter: { _id: record._id, schemaVersion: record.schemaVersion },
        update: {
          $set: {
            schemaVersion: 2,
            status: record.legacyStatus ?? record.status,
            refundDeadline: record.refundDeadline ?? null,
          },
        },
      },
    }));

  if (operations.length > 0) {
    await collection.bulkWrite(operations);
  }

  return { nextCursor: records.at(-1)._id, done: false };
}

সহজে বাদ পড়া বিবরণ:

  • ইডেম্পোটেন্সি: filter-এ পুরোনো সংস্করণ সংখ্যা রাখুন, যাতে পুনরায় চালালে ইতিমধ্যে মাইগ্রেট করা রেকর্ড কখনো আবার লেখা না হয়।
  • ব্যাচ ও চেকপয়েন্ট: _id cursor দিয়ে এগোন, চেকপয়েন্ট রাখুন, ব্যর্থতার পর সেখান থেকেই আবার শুরু করুন।
  • রিহার্সাল: প্রোডাকশন স্ন্যাপশটের বিরুদ্ধে স্ক্রিপ্ট চালিয়ে আগে-পরে রেকর্ড সংখ্যা ও চেকসাম তুলনা করুন।
  • রোলব্যাক: নতুন ফিল্ড ভ্যালিডেশন পাস করা পর্যন্ত পুরোনো ফিল্ড রাখুন; ফ্ল্যাগ দিয়ে পড়ার পথ বদলান, কখনো শুধু schemaVersion সম্পাদনা করে ফিরে যাবেন না।
  • ভ্যালিডেশন: মাইগ্রেশনের পর ব্যবসায়িক বাধ্যবাধকতা চালান (অ-ঋণাত্মক পরিমাণ, বৈধ অবস্থা রূপান্তর), কেবল JSON পার্স চেক নয়।

কনফিগারেশন ডেটাও একই প্রক্রিয়া অনুসরণ করে; এই ফাইলগুলো কীভাবে সাজাবেন জানতে দেখুন কনফিগারেশন, ফিক্সচার ও লোকাল ডেটার জন্য JSON সামলানো।

৭. চুক্তি টেস্ট: সামঞ্জস্য CI-তে বসান

সামঞ্জস্য কোড রিভিউয়ের স্মৃতির উপর নির্ভর করতে পারে না; টেস্ট দরকার। দুই স্তরই যথেষ্ট:

  1. আকৃতি টেস্ট: JSON Schema দিয়ে রেসপন্স যাচাই করুন, কিন্তু ভোক্তার ব্যবহৃত স্কিমায় additionalProperties: false সেট করবেন না।
  2. ভোক্তা-উপসেট টেস্ট: পুরোনো ভোক্তা সত্যিই যে ফিল্ডগুলোর উপর নির্ভর করে সেগুলো উপ-স্কিমায় বের করুন আর দাবি করুন নতুন রেসপন্স তবু তা পূরণ করে।
// Contract test: new responses must still satisfy the old consumer subset
import Ajv from 'ajv';
import assert from 'node:assert/strict';
import { test } from 'node:test';

const ajv = new Ajv({ strict: false });
const validateLegacySubset = ajv.compile(legacyConsumerSubsetSchema);

test('a v2 response still satisfies the v1 consumer subset', () => {
  const response = buildOrderResponse({ schemaVersion: 2 });
  const { schemaVersion, ...legacyFields } = response;
  const isValid = validateLegacySubset(legacyFields);

  assert.equal(isValid, true, JSON.stringify(validateLegacySubset.errors));
});

উপ-স্কিমা কেবল পুরোনো ভোক্তা যে ফিল্ড ও টাইপ পড়ে তা ঘোষণা করে আর প্রতিটি নতুন ফিল্ড ঢুকতে দেয়। এতে সরানো ফিল্ড ও টাইপ পরিবর্তন ধরা পড়ে, কিন্তু সংযোজনে ভুয়া অ্যালার্ম হয় না। কেস ডেটা ফিক্সচার ডিরেক্টরিতে সাজানো ও ডিফযোগ্য রাখুন; সেই ফাইলে মার্জ কনফ্লিক্টের জন্য দেখুন JSON ফাইলে Git কনফ্লিক্ট: union ভাঙা JSON লেখে, কি দিয়ে মার্জ করুন।

৮. ক্যানারি রোলআউট: মাত্রায় স্কেল, মেট্রিকে থামুন

সামঞ্জস্য সমস্যা ইউনিট টেস্টে কম দেখা যায়; দেখা যায় ক্যানারিতে। সংস্পর্শ সংকীর্ণ থেকে প্রশস্ত করুন:

মাত্রা অভ্যাস কী যাচাই করে
টেন্যান্ট হোয়াইটলিস্ট আগে অভ্যন্তরীণ বা বন্ধুত্বপূর্ণ গ্রাহকের জন্য নতুন পড়ার পথ চালু করুন বাস্তব জগতের ডেটা-আকৃতির পার্থক্য
ক্লায়েন্ট সংস্করণ কেবল ইতিমধ্যে নতুন আকৃতিতে আপগ্রেড হওয়া ক্লায়েন্টের জন্য চালু করুন পুরোনো ও নতুন ক্লায়েন্ট পাশাপাশি চলা
ট্রাফিক শতাংশ ৫% → ২৫% → ১০০%, প্রতি ধাপে একটি পূর্ণ ব্যবসায়িক চক্র দেখা লং-টেইল কল ও ক্যাশে হিট আচরণ

অন্তত তিন মেট্রিক দেখুন: API এরর হার, চুক্তি ভ্যালিডেশন ব্যর্থতার হার, আর পুরোনো ও নতুন ফিল্ডের পড়ার অনুপাত। স্বয়ংক্রিয় থামার শর্ত ঠিক করুন, যেমন 5xx ১%-এর বেশি বা পুরোনো-ফিল্ড পড়া হঠাৎ বেড়ে যাওয়া, আর ফ্ল্যাগ রোলব্যাক করুন। ক্যানারির সময় দুই-লেখা চালু রাখুন যাতে রোলব্যাকে কেবল পড়ার পথ বদলাতে হয়, ডেটা সারাতে না হয়।

৯. প্রকাশের আগে সামঞ্জস্য চেকলিস্ট

  • [ ] নতুন ফিল্ড ঐচ্ছিক, সেমান্টিকভাবে স্বাধীন, আর পুরোনো নাম পুনরায় ব্যবহার করে না।
  • [ ] ভোক্তারা অজানা ফিল্ড ও অজানা এনাম মান ফলব্যাক দিয়ে সামলায়।
  • [ ] সংস্করণ ফিল্ড ডকুমেন্টের সঙ্গে সংরক্ষিত আর তারে নেগোশিয়েবল।
  • [ ] ডেপ্রিকেশন উইন্ডোতে ঘোষণা, Sunset হেডার ও পর্যবেক্ষণ আছে।
  • [ ] পুরোনো-ফিল্ড পড়া লগ বা ট্রেস দিয়ে নির্দিষ্ট কলারে ট্রেস করা যায়।
  • [ ] মাইগ্রেশন স্ক্রিপ্ট ইডেম্পোটেন্ট, ব্যাচড, রিজিউমযোগ্য, আর রোলব্যাক পথ আছে।
  • [ ] চুক্তি টেস্ট পুরোনো ভোক্তা উপসেট ঢাকে এবং CI-তে চলে।
  • [ ] ক্যানারিতে স্পষ্ট সংস্পর্শ-মাত্রা ও থামার মেট্রিক আছে।

সাধারণ প্রশ্ন

ঐচ্ছিক ফিল্ড যোগ করা কি সবসময় পশ্চাৎ-সামঞ্জস্যপূর্ণ?

না। এটি কেবল তখনই সামঞ্জস্যপূর্ণ যখন ভোক্তারা অজানা ফিল্ড উপেক্ষা করে ও কঠোর স্কিমা ভ্যালিডেশন এড়ায়। additionalProperties: false ব্যবহারকারী বা অবস্থান দিয়ে পড়া ভোক্তা তবু ব্যর্থ হবে। প্রকাশের আগে চুক্তি টেস্ট দিয়ে তাদের সহনশীলতা যাচাই করুন।

schemaVersion URL-এ থাকা উচিত নাকি রেসপন্স বডিতে?

তারে মিডিয়া টাইপ বা URL পাথ দিয়ে সংস্করণ নেগোশিয়েট হতে পারে, কিন্তু সংরক্ষিত রেকর্ডের ভেতরে schemaVersion থাকতেই হবে। কিউ, অফলাইন ব্যাকআপ ও অডিট কখনো URL পার হয় না, তাই কেবল ডকুমেন্টের ভেতরের সংস্করণ রিপ্লে ও ব্যাকফিল সমর্থন করে।

ডেপ্রিকেটেড ফিল্ড সঙ্গে সঙ্গে মুছে ফেলা যায়?

না। লেখা বন্ধ করুন, সরানোর তারিখ ঘোষণা করুন, প্রতি কলারের পড়া গুনুন। উইন্ডো বন্ধ ও পড়া শূন্যে থাকার পরেই মুছুন। মোছার আগে পূর্ণ রিগ্রেশন চালিয়ে নিশ্চিত করুন কোনো লুকানো cron জব বা রিপোর্ট এখনও তা পড়ে না।

মাইগ্রেশন স্ক্রিপ্ট মাঝপথে ব্যর্থ হলে কী হবে?

এটিকে ইডেম্পোটেন্ট জব হিসেবে নকশা করুন: শর্তযুক্ত আপডেট, প্রাইমারি-কি cursor ও চেকপয়েন্ট। ব্যর্থতার পর চেকপয়েন্ট থেকে আবার শুরু করুন, আর পুনরাবৃত্ত রানে দুইবার লেখা যাবে না। রোলব্যাকের জন্য পুরোনো ফিল্ড ও স্ন্যাপশট রাখুন; কেবল schemaVersion ফিরিয়ে দেবেন না।

ডেটা মাইগ্রেশনের ক্রম কীভাবে সাজাব?

Parallel Change অনুসরণ করুন: আগে expand যাতে পুরোনো ও নতুন ফিল্ড একসঙ্গে থাকে এবং দুই সংস্করণই পড়া যায়, ঐতিহাসিক রেকর্ড নতুন আকৃতিতে backfill করুন, তারপর পুরোনো ফিল্ড ও সামঞ্জস্য শাখা সরিয়ে contract করুন। ক্রম উল্টাবেন না, নাহলে রোলব্যাক পথ হারাবেন, আর মাইগ্রেশন স্ক্রিপ্ট ইডেম্পোটেন্ট, ব্যাচড ও রিজিউমযোগ্য রাখুন।