بڑے پیمانے پر میراثی ہجرتیں اکثر حتمی منتقلی سے بہت پہلے ناکام ہو جاتی ہیں۔
ناکامیاں عام طور پر اس وقت شروع ہوتی ہیں جب ایک ہجرت ایک واقعہ پر مشتمل ہوتی ہے۔ درخواست کو منتقل کریں۔ ڈیٹا بیس کو منتقل کریں۔ تمام صارفین کو منتقل کریں۔ ٹریفک کو تبدیل کریں۔ اپنے پرانے سسٹم کو بند کر دیں۔
یہ ایک خطرناک مفروضہ بناتا ہے۔ خیال یہ ہے کہ پرانے نظام اور نئے نظام کو ایک ہی وقت میں جگہوں کا تبادلہ کرنا چاہیے۔
عام طور پر نہیں۔
اگر آپ پہلے سے ہی میراثی رویے کو سمجھتے ہیں، اس کی حفاظت کرتے ہیں، خصوصیت کے ٹیسٹوں کے ساتھ اس کی حفاظت کرتے ہیں، نقل مکانی کے لیے موافق حدود بناتے ہیں، اور پرانے اور نئے نفاذ کا موازنہ کرتے ہیں۔
آپ ایک وقت میں ایک خصوصیت کو منتقل کر سکتے ہیں۔ پھر مسئلہ بالکل بدل جاتا ہے۔
بجائے:
legacy monolith
↓
complete rewrite
↓
big-bang cutover
آپ درج ذیل سمتوں میں جا سکتے ہیں:
legacy monolith
↓
one capability extracted
↓
small percentage of traffic
↓
observe
↓
expand
↓
repeat
مقصد نقل مکانی کو سست کرنا نہیں ہے۔ مقصد یہ ہے کہ ہر تبدیلی کو چھوٹا، قابل مشاہدہ اور الٹایا جا سکے۔
اس ٹیوٹوریل میں، میں آپ کو دکھاؤں گا کہ کس طرح ایک میراثی یک سنگی کو بتدریج منتقل کرنا ہے بذریعہ:
-
منتقلی کے پہلے محفوظ ٹکڑے کا انتخاب کرنا
-
میراثی کوڈ اور نئے کوڈ کے درمیان حدود کی وضاحت کریں۔
-
نفاذ کے درمیان روٹنگ کی درخواست کریں۔
-
Strangler Fig پیٹرن کا استعمال کرتے ہوئے
-
ٹکنالوجی کی تہہ کے بجائے کاروباری قابلیت سے ہجرت کریں۔
-
پرانے اور نئے نفاذ کو ایک ساتھ رکھنا
-
ترقی پسند ٹریفک کا تعارف
-
مکمل کٹ اوور سے پہلے خرابی کا پتہ لگانا
-
رول بیک پاتھ ڈیزائن
-
ڈیٹا کی ملکیت کو احتیاط سے ہینڈل کریں۔
-
منتقل شدہ میراثی سلوک کو ہٹا دیں۔
-
بڑھتی ہوئی منتقلی کے لیے AI کو خودکار دوبارہ لکھنے میں تبدیل کیے بغیر استعمال کریں۔
مثالیں TypeScript کا استعمال کرتی ہیں، لیکن یہ نقطہ نظر زیادہ تر زبانوں، رن ٹائمز اور فن تعمیر پر لاگو ہوتا ہے۔
مقصد آسان ہے۔ خیال یہ ہے کہ ہجرت کو کسی ایک، ناقابل واپسی واقعہ کی بجائے کنٹرول شدہ تبدیلیوں کا ایک سلسلہ بنایا جائے۔
شرطیں
پیروی کرنے کے لیے، آپ کو درج ذیل سے واقف ہونا ہوگا:
-
TypeScript یا اس سے ملتی جلتی زبان
-
API اور سروس کی حدود
-
انضمام کی جانچ
-
انحصار انجکشن
-
روٹنگ اور ریورس پراکسی
-
ڈیٹا بیس ٹرانزیکشن
-
مشاہدہ
-
انکریمنٹل ری فیکٹرنگ
-
میراثی جدید کاری
آپ کو ان خصوصیات کے رویے کو بھی سمجھ لینا چاہیے جو آپ منتقل کر رہے ہیں۔
مثالی طور پر، آپ مندرجہ ذیل جانتے ہیں:
اگر آپ ابھی تک اس مقام تک نہیں پہنچے ہیں، تو ہجرت کرنا قبل از وقت ہو سکتا ہے۔
انڈیکس
بگ بینگ مائیگریشن خطرناک کیوں ہے؟
میراثی کامرس کی درخواست کا تصور کریں۔
ان میں شامل ہیں:
customers
orders
payments
inventory
shipping
invoicing
notifications
reporting
جدید کاری کے منصوبے میں کہا گیا ہے:
replace the monolith
یہ ایک پروجیکٹ کی طرح لگتا ہے۔
عملی طور پر، اس کا مطلب بدلنا ہو سکتا ہے:
runtime
framework
database
deployment model
API contracts
authentication
networking
observability
data model
business logic
external integrations
ایک ہی وقت میں.
حتمی کٹ اوور کی ناکامی کی ممکنہ وجوہات کی تعداد بہت زیادہ ہے۔
مثال کے طور پر:
Did pricing change?
Did the database migration lose data?
Is the payment provider failing?
Did authentication behave differently?
Did the new runtime change date handling?
Did a timeout become shorter?
Did an event stop being published?
Did the new deployment configuration fail?
یہ بگ بینگ ہجرت کے ساتھ اہم مسائل میں سے ایک ہے۔ بہت سارے متغیرات ایک ساتھ تبدیل ہوتے ہیں۔
بڑھتی ہوئی منتقلی ہر قدم پر تبدیل ہونے والے متغیرات کی تعداد کو کم کرنے کی کوشش کرتی ہے۔
مائیگریشن سلائسز کے بارے میں سوچیں، ایپلیکیشنز کے نہیں۔
پوچھنے کے بجائے:
میں اس یک سنگی کو کیسے منتقل کروں؟
پوچھیں:
سب سے چھوٹی معنی خیز کاروباری صلاحیت کیا ہے جس پر ہم آزادانہ طور پر کام کر سکتے ہیں؟
مثال کے طور پر:
Calculate Order Total
Generate Invoice
Send Order Confirmation
Create Shipment
Renew Subscription
Approve Customer
نقل مکانی کے ٹکڑے میں مثالی طور پر ہونا چاہئے:
clear input
clear output
known side effects
understood dependencies
observable behavior
a rollback path
یہ آپ کو آگے بڑھنے کے لیے کچھ ٹھوس دیتا ہے۔
مثال کے طور پر:
Generate Invoice
یہ اس طرح ہو سکتا ہے:
input:
orderId
behavior:
load order
calculate taxes
generate invoice number
create invoice
side effects:
store invoice
publish invoice.created
output:
invoice
ہجرت کرنا اس سے کہیں زیادہ آسان ہے:
billing module
یا:
src/services/
کاروباری افعال فولڈرز کے مقابلے میں منتقلی کی بہتر اکائیاں بناتے ہیں۔
اپنی پہلی خصوصیت کو احتیاط سے منتخب کریں۔
پہلا ٹکڑا اہم ہے۔
میں عام طور پر سسٹم کی سب سے اہم خصوصیات کے ساتھ شروع کرنے سے گریز کرتا ہوں۔
آپ اپنے ہجرت کے نقطہ نظر کی توثیق کرنے کے لیے کافی معنی خیز چیز چاہتے ہیں، لیکن اتنا خطرناک نہیں کہ غلطیوں کے تباہ کن نتائج برآمد ہوں۔
مفید پہلے ٹکڑوں میں اکثر شامل ہوتے ہیں:
moderate traffic
limited external dependencies
clear behavior
good test coverage
few transactional boundaries
low blast radius
مثال کے طور پر:
Generate Customer Statement
اس سے بہتر پہلا ہجرت کا امیدوار کیا ہو سکتا ہے:
Authorize Payment
پہلی ہجرت جزوی طور پر ایک تکنیکی کام ہے، لیکن یہ ایک سیکھنے کی مشق بھی ہے۔
ہم چیک کر رہے ہیں:
routing
deployment
observability
rollback
data access
testing
team workflow
مزید اہم افعال پر پیٹرن کو لاگو کرنے سے پہلے.
وراثت اور نئے کے درمیان سرحدیں بنانا
فرض کریں کہ آپ کی میراثی درخواست ہے:
async function generateInvoice(
orderId: string
) {
// legacy implementation
}
ہجرت سے پہلے حدود کا تعارف کروائیں۔
interface InvoiceGenerator {
generate(
orderId: string
): Promise;
}
میراث کا نفاذ یہ ہے:
class LegacyInvoiceGenerator
implements InvoiceGenerator {
async generate(
orderId: string
): Promise {
// existing behavior
}
}
نیا نفاذ یہ ہے:
class NewInvoiceGenerator
implements InvoiceGenerator {
async generate(
orderId: string
): Promise {
// migrated behavior
}
}
اب کال کرنے والے کو یہ جاننے کی ضرورت نہیں ہے کہ کون سا عمل فعال ہے۔
یہ ایک اہم خصوصیت پیدا کرتا ہے۔
replace implementation
without replacing caller
یہ بڑھتی ہوئی نقل مکانی کی بنیادوں میں سے ایک ہے۔
Strangler Fig پیٹرن کا استعمال کرتے ہوئے
اضافی تبدیلی کو بیان کرنے کا ایک عام طریقہ Strangler Fig پیٹرن ہے۔
ایک ساتھ پوری ایپلیکیشن کو تبدیل کرنے کے بجائے، موجودہ سسٹمز کے ارد گرد نیا رویہ بتدریج بنتا ہے۔
تصوراتی طور پر:
incoming request
│
↓
router
/ \
/ \
legacy path new path
سب سے پہلے:
legacy: 100%
new: 0%
بعد میں:
legacy: 95%
new: 5%
پھر:
legacy: 50%
new: 50%
آخر میں:
legacy: 0%
new: 100%
اس مقام پر آپ اس خصوصیت کے پچھلے نفاذ کو ہٹا سکتے ہیں۔
اہم بات یہ ہے کہ تبدیلی آہستہ آہستہ ہوتی ہے۔ میراثی ایپلی کیشنز سسٹم کے حصے فراہم کرتی رہتی ہیں، جبکہ نئے نفاذ دوسرے حصوں پر قبضہ کرتے ہیں۔
ہجرت کرنے والی خصوصیات، ٹیکنالوجی کی پرتیں نہیں۔
نقل مکانی کی ایک پرکشش حکمت عملی یہ ہے:
move database
then move services
then move APIs
then move UI
اس کے نتیجے میں تمام خصوصیات کے لیے پرانے اور نئے دونوں فن تعمیرات پر محیط طویل عرصہ ہو سکتا ہے۔
مثال کے طور پر:
new API
↓
legacy service
↓
new database
↓
legacy event publisher
یہ کبھی کبھی ناگزیر ہے.
تاہم، جب بھی ممکن ہو میں عمودی سلائسوں کو ترجیح دیتا ہوں۔
عمودی ٹکڑا اس طرح لگتا ہے:
Generate Invoice
request
↓
application logic
↓
persistence
↓
events
↓
response
قابلیت ایک واحد مربوط اکائی کے طور پر حرکت کر سکتی ہے۔
پھر:
Create Shipment
وہ الگ الگ منتقل کر سکتے ہیں.
پھر:
Renew Subscription
وغیرہ
یہ آپ کو پہلے منتقل شدہ خصوصیات پر کام کرنے کی اجازت دیتا ہے۔ یہ عارضی انٹر سسٹم انحصار کو بھی کم کرتا ہے۔
وراثت اور نئے نفاذ کو ایک ساتھ رکھنا
بڑھتی ہوئی منتقلی کے دوران بقائے باہمی عام ہے۔
درج ذیل علامات وقت کے ساتھ ظاہر ہو سکتی ہیں۔
LegacyInvoiceGenerator
NewInvoiceGenerator
دونوں کو تعینات کر دیا گیا ہے۔
یہ حادثاتی طور پر نقل نہیں ہے۔ یہ ہماری نقل مکانی کی حکمت عملی کا حصہ ہے۔
اہم سوال یہ ہے کہ درخواست ان میں سے کس طرح انتخاب کرتی ہے۔
آپ استعمال کر سکتے ہیں:
feature flag
tenant
user group
request header
region
percentage rollout
specific account IDs
مثال کے طور پر:
class InvoiceRouter {
constructor(
private readonly legacy:
InvoiceGenerator,
private readonly migrated:
InvoiceGenerator
) {}
async generate(
orderId: string,
useMigrated: boolean
) {
if (useMigrated) {
return this.migrated.generate(
orderId
);
}
return this.legacy.generate(
orderId
);
}
}
یہ جان بوجھ کر آسان ہے۔ اہم حصہ یہ ہے کہ روٹنگ واضح ہے۔ آپ کو معلوم ہوگا کہ ہر درخواست کو کس نفاذ نے سنبھالا ہے۔
واضح طور پر ٹریفک کا راستہ
نقل مکانی کی منطق سے بچیں جس کا مشاہدہ کرنا مشکل ہے۔
مثال کے طور پر:
try {
return await newService.call();
} catch {
return legacyService.call();
}
یہ لچکدار دکھائی دے سکتا ہے، لیکن یہ ناکامی کو چھپا سکتا ہے۔
فرض کریں کہ نیا نفاذ 40٪ وقت میں ناکام ہوجاتا ہے۔ اگر تمام خرابیاں خود بخود وراثت میں واپس آجاتی ہیں، تو صارفین کے لیے کوئی مسئلہ نہیں ہوسکتا ہے۔ لیکن نقل مکانی صحت مند نہیں ہے۔
مسئلہ یہ ہے کہ پہلا ورژن دو فیصلوں کو ملا دیتا ہے۔ نفاذ جن کی درخواست کی جانی چاہئے۔ اور اگر یہ عمل ناکام ہو جائے تو کیا ہوگا؟. کیونکہ فال بیک اندرونی طور پر ہوتا ہے۔ catchنقل مکانی کرنے والے راستے کوئی قابل پیمائش واضح روٹنگ سگنل پیدا کیے بغیر بار بار ناکام ہو سکتے ہیں۔
ایک بہتر طریقہ یہ ہے کہ پہلے روٹنگ کے فیصلے کریں، انہیں ریکارڈ کریں، اور پھر اپنی پسند کے نفاذ کو کال کریں۔ یہ ہجرت کی پالیسی کو غلطی سے نمٹنے سے الگ کرتا ہے اور اس بات کا واضح ریکارڈ فراہم کرتا ہے کہ ہر راستے پر واقعی کتنی ٹریفک پہنچی ہے۔
مثال کے طور پر:
const route =
migrationPolicy.route(request);
metrics.increment(
`invoice.route.${route}`
);
if (route === "migrated") {
return migrated.generate(
request.orderId
);
}
return legacy.generate(
request.orderId
);
اب آپ پیمائش کر سکتے ہیں:
requests routed to legacy
requests routed to migrated
migration failures
fallback count
latency
business outcomes
نظام کے رویے کی اعلیٰ ترین سطح پر منتقلی قابل مشاہدہ ہونی چاہیے۔
اندرونی یا کم خطرے والی ٹریفک کے ساتھ شروع کریں۔
صارفین کی بڑی تعداد کو منتقل شدہ راستوں سے نیچے لانے سے پہلے محفوظ ٹریفک کے ساتھ شروع کریں۔
مثال کے طور پر:
development
test environments
internal users
staff accounts
test tenants
specific low-risk customers
یہ آپ کو چیک کرنے کی اجازت دیتا ہے:
deployment
routing
observability
data access
external integrations
failure handling
خطرہ کم ہے۔
اس کے بعد آپ اسے بڑھا سکتے ہیں۔
مثال کے طور پر:
internal users
↓
1% production
↓
5%
↓
10%
↓
25%
↓
50%
↓
100%
صحیح تناسب اہم نہیں ہیں، لیکن اصول ہے.
ہر ایک اضافہ کیا جانا چاہئے کیونکہ پچھلے مرحلے میں کافی ثبوت تیار کیے گئے ہیں۔
آہستہ آہستہ پیداواری ٹریفک میں اضافہ
فرض کریں کہ آپ کی مندرجہ ذیل صورت حال ہے:
10,000 invoice requests/day
ہر درخواست کو موڑنے کے بجائے:
legacy → new
ایک وقت میں ایک راستہ:
1%
پہلے
یہ تقریباً اس طرح ہے:
100 real requests/day
ہجرت کے راستے سے
اب مانیٹر کریں:
error rate
latency
output differences
side effects
customer-visible failures
business metrics
ایک بار جب آپ کا سسٹم ٹھیک سے کام کر لے تو اپنی ٹریفک میں اضافہ کریں۔ اگر نہیں، تو منتقل شدہ روٹنگ کو کم یا غیر فعال کریں۔
منتقلی ایک کنٹرول شدہ تجربہ بن جاتا ہے۔ یہ کٹ اوور ایونٹ سے بہت مختلف ہے۔
رول آؤٹ سے پہلے اور دوران تفریق ٹیسٹنگ کا استعمال کریں۔
اس سیریز کے پچھلے مضامین تفریق کی جانچ پر مرکوز تھے۔ یہ تکنیک یہاں خاص طور پر مفید ہے۔
تفریق ٹیسٹنگ کا مطلب ہے موجودہ اور منتقل شدہ نفاذ کو ایک ہی ان پٹ کے ساتھ چلانا اور قابل مشاہدہ رویے کا موازنہ کرنا۔ فنکشن پر منحصر ہے، اس میں واپسی کی قدریں، غلطیاں، حالت کی تبدیلیاں، اور ضمنی اثرات شامل ہو سکتے ہیں۔
مقصد یہ ثابت کرنا نہیں ہے کہ نفاذ داخلی طور پر ایک جیسے ہیں۔ مقصد یہ ہے کہ بامعنی رویے کے فرق کا پتہ لگانا اس سے پہلے کہ یہ اختلافات تمام پروڈکشن ٹریفک تک پہنچ جائیں۔
ریئل ٹائم روٹنگ سے پہلے، آپ موازنہ کر سکتے ہیں:
same input
↓
legacy result
same input
↓
new result
آپ رول آؤٹ کے دوران حقیقی ٹریفک کا نمونہ بھی لے سکتے ہیں اور محفوظ ہونے پر رویے کا موازنہ کر سکتے ہیں۔
مثال کے طور پر:
real request
│
├────→ active implementation
│
└────→ shadow implementation
پھر موازنہ کریں۔
output
errors
side effects
business state
یہ آپ کو اپنے ٹریفک میں اضافہ شروع کرنے سے پہلے ثبوت فراہم کرتا ہے۔
لانچ کا فیصلہ اس پر مبنی ہو سکتا ہے:
divergence
error rate
latency
business outcomes
بجائے:
یہ ٹھیک لگتا ہے۔
سمال اینڈ ٹو اینڈ انوائس مائیگریشن کی مثال
جب آپ انفرادی ٹکڑوں کو ایک ساتھ کام کرتے ہوئے دیکھتے ہیں تو یہ سمجھنا آسان ہوتا ہے۔
یہاں ایک جان بوجھ کر چھوٹی، میموری میں موجود مثال ہے جو انوائس کی فعالیت پر مبنی ہے جسے ہم پورے مضمون میں استعمال کرتے رہے ہیں۔ اس میں اصل ڈیٹا بیس، ریورس پراکسی، قطار، یا تقسیم کے پلیٹ فارم شامل نہیں ہیں۔ نقطہ یہ ہے کہ منتقلی کے کنٹرول کے بہاؤ کو ایک جگہ پر دکھایا جائے۔
اشتراک کے معاہدے کے ساتھ شروع کریں۔
type InvoiceInput = {
orderId: string;
subtotal: number;
};
type Invoice = {
orderId: string;
total: number;
};
interface InvoiceGenerator {
generate(
input: InvoiceInput
): Promise;
}
میراثی عمل درآمد انوائس کی کل کا حساب مندرجہ ذیل کرتا ہے:
class LegacyInvoiceGenerator
implements InvoiceGenerator {
async generate(
input: InvoiceInput
): Promise {
return {
orderId: input.orderId,
total: input.subtotal * 1.21,
};
}
}
اب تصور کریں کہ آپ نے اس فعالیت کو ایک نئے نفاذ میں منتقل کر دیا ہے۔
class MigratedInvoiceGenerator
implements InvoiceGenerator {
async generate(
input: InvoiceInput
): Promise {
const tax =
input.subtotal * 0.21;
return {
orderId: input.orderId,
total: input.subtotal + tax,
};
}
}
کوڈ مختلف ہے، لیکن مطلوبہ سلوک ایک ہی ہے۔
اگلا، ہم deterministic رول آؤٹ فنکشن کی وضاحت کرتے ہیں۔ اس مثال میں ہم ہر ایک کو تفویض کریں گے: orderId 0 سے 99 تک بالٹی میں تبدیل ہوتا ہے، لہذا ایک ہی ترتیب ہمیشہ اسی راستے پر چلتی ہے۔
function bucketFor(
value: string
): number {
const sum = [...value].reduce(
(total, char) =>
total + char.charCodeAt(0),
0
);
return sum % 100;
}
function shouldUseMigrated(
orderId: string,
percentage: number
): boolean {
return (
bucketFor(orderId) < percentage
);
}
اگر percentage ہے 10تقریباً 10% IDs منتقل شدہ راستوں کو تفویض کیے گئے ہیں۔
اب آئیے چند چھوٹی ان میموری میٹرکس شامل کرتے ہیں۔
const metrics = {
legacyRequests: 0,
migratedRequests: 0,
mismatches: 0,
};
اس کے بعد آپ میراث اور منتقلی کے نفاذ کو ایک منتقلی سے آگاہ انٹری پوائنٹ کے پیچھے رکھتے ہیں۔
class IncrementalInvoiceService {
migratedEnabled = true;
rolloutPercentage = 10;
constructor(
private readonly legacy:
InvoiceGenerator,
private readonly migrated:
InvoiceGenerator
) {}
async generate(
input: InvoiceInput
): Promise {
const legacyResult =
await this.legacy.generate(
structuredClone(input)
);
const migratedResult =
await this.migrated.generate(
structuredClone(input)
);
if (
migratedResult.orderId !==
legacyResult.orderId ||
migratedResult.total !==
legacyResult.total
) {
metrics.mismatches += 1;
}
const useMigrated =
this.migratedEnabled &&
shouldUseMigrated(
input.orderId,
this.rolloutPercentage
);
if (useMigrated) {
metrics.migratedRequests += 1;
return migratedResult;
}
metrics.legacyRequests += 1;
return legacyResult;
}
}
یہ چھوٹی سی خدمت مضمون کے کئی خیالات کو یکجا کرتی ہے۔
سب سے پہلے، دونوں نفاذ کو ایک ہی ان پٹ کے ساتھ چلائیں اور نتائج کا موازنہ کریں۔ ایسا کرنا محفوظ ہے کیونکہ یہ مثال مکمل طور پر میموری میں ہے اور اس کے کوئی بیرونی مضر اثرات نہیں ہیں۔
دوسرا، ہم درخواستوں کے صرف ایک حصے کو منتقل شدہ نتائج تک پہنچاتے ہیں۔
تیسرا، ہم ان درخواستوں کی تعداد کو ریکارڈ کرتے ہیں جنہوں نے ہر راستہ استعمال کیا اور کتنی رویے میں تضادات واقع ہوئے۔
آپ یہ کچھ درخواستوں کے ساتھ کر سکتے ہیں:
const service =
new IncrementalInvoiceService(
new LegacyInvoiceGenerator(),
new MigratedInvoiceGenerator()
);
for (let i = 1; i <= 100; i++) {
await service.generate({
orderId: `order-${i}`,
subtotal: 1000,
});
}
console.log(metrics);
آپ اس طرح کچھ دیکھ سکتے ہیں:
legacyRequests: 89
migratedRequests: 11
mismatches: 0
بالٹی فنکشن جان بوجھ کر آسان ہے، لہذا اگر آپ کے پاس صرف 100 ان پٹ ہیں، تو عین مطابق تقسیم بالکل 90/10 نہیں ہوسکتی ہے۔ اہم بات یہ ہے کہ روٹنگ تعییناتی، قابل پیمائش اور کنٹرول شدہ ہے۔ rolloutPercentage.
جب منتقلی عمل میں اختلافات کا سامنا کرنا شروع ہوتا ہے، تو تضاد کاؤنٹر ایک قابل مشاہدہ سگنل فراہم کرتا ہے۔
اگر آپ فیصلہ کرتے ہیں کہ رول آؤٹ کو روکنے کی ضرورت ہے، تو واضح طور پر رول بیک کیا جاتا ہے۔
service.migratedEnabled = false;
اس وقت سے، کوئی بھی جواب جو واپس آئے گا وہ میراث کے نفاذ سے واپس آئے گا۔
یہ جان بوجھ کر آسان کی گئی مثال ہے۔ پیداواری نظام کو زیادہ مضبوط روٹنگ، حقیقی دنیا کے میٹرکس، غلطی سے نمٹنے، مستقل حالت، اور ضمنی اثرات سے محتاط طریقے سے نمٹنے کی ضرورت ہوتی ہے۔
آپ کو کسی بھی عمل کو آنکھ بند کر کے نہیں چلنا چاہیے، خاص طور پر اگر آپ انوائسز بنا رہے ہیں اور ای میل کر رہے ہیں، دو پروڈکشن ڈیٹا بیس کو لکھ رہے ہیں، صارفین کو بل کر رہے ہیں، یا بیرونی طور پر دکھائی دینے والے واقعات پوسٹ کر رہے ہیں۔ ان صورتوں میں، شیڈو پاتھ کے لیے ریکارڈنگ اڈاپٹر، الگ تھلگ انفراسٹرکچر، یا اصل اثر کو نقل کیے بغیر رویے کا موازنہ کرنے کے لیے دوسرے طریقہ کار کی ضرورت ہوتی ہے۔
تاہم، کنٹرول لوپ ایک ہی ہے.
same input
↓
compare legacy and migrated behavior
↓
route a small percentage
↓
observe
↓
expand or roll back
یہ بڑھتی ہوئی منتقلی کی سب سے چھوٹی مفید شکل ہے۔
اس سے پہلے کہ آپ کو ضرورت ہو ڈیزائن رول بیک کریں۔
کسی واقعے کے دوران رول بیکس کو ڈیزائن نہیں کیا جانا چاہئے۔ ٹریفک کو منتقل کرنے سے پہلے، پوچھیں کہ اگر منتقل شدہ راستہ ناکام ہو جاتا ہے تو کیا ہوتا ہے۔
روٹنگ لیول کی منتقلی کے لیے، رول بیک سیدھا ہو سکتا ہے۔
migration flag = false
ٹریفک کی واپسی:
legacy implementation
مثال کے طور پر:
if (
featureFlags.useNewInvoices
) {
return migrated.generate(
orderId
);
}
return legacy.generate(orderId);
اگر منتقلی کا راستہ غلط برتاؤ کرتا ہے:
useNewInvoices = false
رول بیک تقریباً فوری ہے۔
تاہم، مندرجہ ذیل صورتوں میں رول بیکس زیادہ پیچیدہ ہو جاتے ہیں:
data format changes
new data is written
events differ
external systems are updated
legacy code cannot read new records
ان صورتوں میں، رول بیک کے لیے فیچر کے جھنڈے کو پلٹانے سے زیادہ کی ضرورت پڑ سکتی ہے۔ آپ کو ایک پسماندہ مطابقت پذیر اسکیما کی ضرورت ہو سکتی ہے تاکہ دونوں ورژن ایک ہی ریکارڈ کو پڑھ سکیں اور بیرونی ضمنی اثرات، دوبارہ چلنے کے قابل واقعات، کوآرڈینیشن آپریشنز کے لیے معاوضہ دینے والے آپریشنز، یا مختصر مدت کے دوران جب میراثی نظام نئے راستے کے ذریعے لکھے گئے ڈیٹا کو استعمال کر سکے۔
ہائی رسک ہجرت کے لیے، یہ رول بیک باؤنڈریز کی پہلے سے وضاحت کرنا بھی مددگار ثابت ہو سکتا ہے۔ مثال کے طور پر، ٹریفک اس وقت تک وراثت میں واپس آ سکتی ہے جب تک کہ نیا سکیما ورژن نہیں بن جاتا، یا کسی مخصوص بیرونی ایونٹ کے بعد بحالی کے لیے سادہ رول بیک کے بجائے معاوضے کی ضرورت ہوتی ہے۔ اہم حصہ یہ جاننا ہے کہ کب ایک رول بیک کو تبدیل کیا جا سکتا ہے اور کب ایک مختلف بحالی کی حکمت عملی پر جانا ہے۔
یہی وجہ ہے کہ تعیناتی سے پہلے رول بیک ڈیزائن کرنا ضروری ہے۔
ڈیٹا کی منتقلی کو الگ مسئلہ سمجھیں۔
اگرچہ ایپلیکیشن کی منتقلی اور ڈیٹا کی منتقلی کا تعلق ہے، وہ ایک ہی مسئلہ نہیں ہیں۔
ہم کہتے ہیں کہ آپ کا میراثی نظام ذخیرہ ہے:
{
"customer_type": "P",
"status": 2
}
جب کہ نیا نظام بچاتا ہے:
{
"customerType": "PREMIUM",
"status": "APPROVED"
}
اب آپ کو جواب دینا چاہیے:
Which database is authoritative?
Can both systems read the same data?
Do we transform on read?
Do we migrate records in batches?
Do we replicate changes?
When does ownership change?
یہ فیصلے واضح ہونے چاہئیں۔ بصورت دیگر، درخواست کی منتقلی کامیاب دکھائی دے سکتی ہے جبکہ ڈیٹا کی حدود مبہم رہیں۔
دوہری تحریر سے محتاط رہیں
ایک عام تبادلوں کی حکمت عملی یہ ہے:
write to legacy database
+
write to new database
اسے ڈبل رائٹ کہا جاتا ہے، اور یہ آسان لگتا ہے۔
مثال کے طور پر:
await legacyOrders.save(order);
await newOrders.save(order);
لیکن کیا ہوتا ہے جب:
legacy write succeeds
new write fails
اب دونوں نظام ایک دوسرے سے مطابقت نہیں رکھتے۔
یا:
legacy write fails
new write succeeds
ایک ہی مسئلہ۔
ڈبل لکھنا تقسیم کی مستقل مزاجی کے مسائل کا سبب بنتا ہے۔
اگر آپ اسے استعمال کرتے ہیں، تو آپ کو درج ذیل کے بارے میں سوچنا چاہیے:
retries
idempotency
reconciliation
ordering
partial failure
monitoring
کبھی کبھی ایک محفوظ طریقہ یہ ہے:
single authoritative write
↓
change event
↓
replication
یا ٹرانزیکشنل آؤٹ باکس۔
کوئی عالمگیر حل نہیں ہے۔ اہم بات یہ ہے کہ ہم دوہری تحریروں کو نقل مکانی کی معمولی تکنیک کے طور پر نہیں مانتے۔
فیصلہ کریں کہ آپ کے ڈیٹا کا مالک کون ہے۔
بقائے باہمی کے دوران ڈیٹا کی ملکیت مبہم ہو سکتی ہے۔
تصور کریں:
legacy system writes customers
new system writes invoices
both systems read orders
یہ بالکل معقول ہو سکتا ہے، لیکن اسے دستاویزی شکل دینے کی ضرورت ہے۔
ہر منتقل شدہ خصوصیت کے لیے، درج ذیل کی وضاحت کریں:
system of record
write owner
readers
replication direction
consistency expectations
مثال کے طور پر:
Invoices
Write owner:
new system
Source of truth:
new database
Legacy access:
read-only adapter
Replication:
new → legacy reporting store
فن تعمیر کی اب ایک واضح سمت ہے۔
ملکیت کے قوانین کے بغیر، ہجرت کے نتیجے میں اکثر مستقل مطابقت پذیری کے مسائل پیدا ہوتے ہیں۔
کاروباری رویے کے ساتھ ساتھ انفراسٹرکچر کا بھی مشاہدہ کریں۔
رول آؤٹ کے دوران، ٹیم کثرت سے نگرانی کرے گی:
CPU
memory
latency
HTTP 500s
database connections
وہ اہم ہیں۔ لیکن یہ اکیلا کافی نہیں ہے۔
فرض کریں:
HTTP 200 rate = 99.99%
جبکہ:
invoice totals are wrong
انفراسٹرکچر کی نگرانی کا کہنا ہے کہ:
healthy
لیکن کاروباری نظام صحت مند نہیں ہے۔
ہجرت کے مشاہدے میں ڈومین سگنلز شامل ہونا چاہیے۔
مثال کے طور پر:
orders processed
payments authorized
invoices generated
discount distribution
failed renewals
average invoice total
events published
اگر آپ عام کاروباری رویے کو جانتے ہیں تو، غیر معمولی تبدیلیاں منتقلی کے نقائص کو ظاہر کر سکتی ہیں جو تکنیکی میٹرکس سے محروم ہیں۔
منتقلی کا ٹکڑا مکمل ہونے پر تعین کریں۔
ٹریفک کے 100% تک پہنچنے کے بعد فیچرز کو مکمل طور پر منتقل نہیں کیا جائے گا۔
اسے مکمل قرار دینے سے پہلے، آئیے درج ذیل کو چیک کریں:
100% traffic on new path
acceptable error rate
acceptable latency
behavioral differences resolved
side effects verified
data ownership established
rollback window completed
legacy callers removed
legacy writes stopped
observability in place
پھر سوال پوچھیں۔
وراثت کے نفاذ کو اب بھی کس مقصد کے لیے استعمال کیا جاتا ہے؟
اگر نہیں، تو اسے ہٹا دیں.
دونوں نفاذ کو مستقل طور پر فعال کرنے سے یہ پیدا ہوتا ہے:
maintenance cost
confusion
duplicate bugs
unclear ownership
future migration debt
بڑھتی ہوئی منتقلی کو بالآخر نظام کو مستقل طور پر نقل کرنے کے بجائے اسے آسان بنانا چاہیے۔
میراثی راستوں کو ہٹا دیں۔
یہ مرحلہ اکثر تاخیر کا شکار ہوتا ہے۔
ٹیم ٹریفک کو منتقل کرتی ہے لیکن پرانے راستوں کو برقرار رکھتی ہے۔
just in case
چند ماہ بعد:
nobody knows whether it is still used
حذف کرنے سے پہلے، درج ذیل کو چیک کریں:
routing metrics show zero traffic
no callers depend on it
data dependencies are removed
rollback period is complete
operational documentation is updated
پھر ہٹائیں:
legacy implementation
legacy feature flags
legacy database access
unused integration code
temporary compatibility layers
حذف کرنا نقل مکانی کا حصہ ہے۔
ہجرت جو موجودہ فن تعمیر کو ہٹائے بغیر نئے فن تعمیر کا اضافہ کرتی ہے اس کی پیچیدگی کو کم کرنے کے بجائے بڑھا سکتی ہے۔
بڑھتی ہوئی منتقلی کے دوران AI کا استعمال کیسے کریں۔
AI اس عمل کے بہت سے حصوں میں مدد کر سکتا ہے۔
مثال کے طور پر، ہم میراثی کوڈ کی بنیاد کا معائنہ کر سکتے ہیں اور چیزوں کے جواب دینے میں مدد کر سکتے ہیں جیسے:
Which modules implement this capability?
Which callers depend on it?
Which database tables does it touch?
Which external services does it call?
Which side effects occur?
Which feature flags already exist?
Which paths need adapters?
کچھ مفید اشارے میں شامل ہیں:
Analyze the Generate Invoice capability.
Identify:
1. entry points,
2. business rules,
3. persistence dependencies,
4. external integrations,
5. side effects,
6. callers,
7. data ownership,
8. possible migration seams.
Do not redesign the system.
Return evidence for each finding using file paths
and relevant code references.
AI منتقلی کی تبدیلیوں کا موازنہ کرنے میں بھی آپ کی مدد کر سکتا ہے۔
مثال کے طور پر:
Compare the legacy and migrated implementations.
Identify possible behavioral differences in:
- return values,
- errors,
- side effects,
- persistence,
- event ordering,
- retries,
- idempotency,
- transaction boundaries.
Do not assume the new implementation is correct.
یہ مفید ہے کیونکہ نقل مکانی کے لیے بہت زیادہ تکراری تجزیہ کی ضرورت ہوتی ہے، اور AI اس تجزیہ کو تیز کر سکتا ہے۔
AI کو ہجرت کو دوبارہ لکھنے میں تبدیل نہ ہونے دیں۔
ناکامی کے عام طریقے ہیں:
آپ پوچھتے ہیں:
براہ کرم اس میراثی خصوصیت کو منتقل کرنے میں ہماری مدد کریں۔
ماڈل اس طرح جواب دیتا ہے:
new architecture
new domain model
new API
new event model
new database schema
new validation layer
new framework
اس وقت، آپ اب کسی ایک خصوصیت کو منتقل نہیں کر رہے ہیں، آپ اسے دوبارہ ڈیزائن کر رہے ہیں۔
کبھی کبھی دوبارہ ڈیزائن ضروری ہوتا ہے، لیکن اسے جان بوجھ کر ہونا پڑتا ہے۔
بڑھتی ہوئی منتقلی کے دوران، واضح رکاوٹوں کے ساتھ اشارے کو ترجیح دی جاتی ہے۔
مثال کے طور پر:
Migrate this capability without intentionally changing
observable behavior.
Preserve:
- inputs,
- outputs,
- errors,
- side effects,
- ordering where relevant,
- transactional behavior.
Only introduce the minimum structural changes required
to run it in the target environment.
List any behavior you cannot preserve with confidence.
یہ تبدیلی کو تنگ رکھتا ہے۔
AI کو مکینیکل کوششوں کو کم کرنے میں مدد کرنی چاہیے۔ پروجیکٹ کا دائرہ خود بخود نہیں بڑھایا جانا چاہیے۔
عملی بڑھتے ہوئے منتقلی کا ورک فلو
میں جو ورک فلو استعمال کروں گا وہ درج ذیل ہے:
1. اپنی صلاحیتوں کو سمجھیں۔
شناخت کریں:
inputs
outputs
rules
side effects
dependencies
unknowns
2. موجودہ طرز عمل کی خصوصیت
اپنے اہم اعمال کی حفاظت کریں:
characterization tests
integration tests
contract tests
3. نقل مکانی کے لیے ری فیکٹرنگ
بنانا:
seams
adapters
explicit dependencies
clear orchestration
جان بوجھ کر اپنے رویے کو تبدیل کیے بغیر۔
4. ایک نئے نفاذ کی تعمیر
ہدف کے ماحول میں خصوصیت کو نافذ کریں۔ قابل مشاہدہ معاہدوں کو واضح رکھیں۔
5. پرانے اور نئے کو الگ الگ جانچیں۔
موازنہ کریں:
outputs
errors
side effects
business state
نمائندہ مثالیں استعمال کریں۔
6. واضح روٹنگ کا تعارف
درخواست کو منتخب کرنے کی اجازت دیں:
legacy
or
migrated
قابل مشاہدہ ہجرت کی پالیسیوں کے ذریعے۔
7. محفوظ نقل و حمل کے ساتھ شروع کریں۔
استعمال کریں:
internal users
test tenants
selected customers
8. اپنی ٹریفک میں بتدریج اضافہ کریں۔
مثال کے طور پر:
1%
5%
10%
25%
50%
100%
صرف اس صورت میں جب ثبوت اگلے مرحلے کی حمایت کرتا ہے۔
9. تکنیکی اور کاروباری اشارے کی نگرانی کریں۔
دونوں کا مشاہدہ کریں۔
system health
business behavior
10. رول بیک ممکن رکھیں
اپنے وراثت کے راستے پر واپس آنا تیز اور آسان بنائیں۔
11. ڈیٹا کی ملکیت کی منتقلی۔
واضح طور پر وضاحت کرتا ہے کہ کون سا نظام مالک ہے:
writes
reads
replication
12. میراثی راستوں کو ہٹا دیں۔
ہجرت کے مستحکم ہونے کے بعد:
delete old implementation
remove temporary routing
remove obsolete dependencies
پھر درج ذیل خصوصیات کو منتخب کریں:
بڑھتی ہوئی منتقلی سے کیا حل نہیں ہوتا
بڑھتی ہوئی منتقلی خطرے کو کم کرتی ہے لیکن پیچیدگی کو ختم نہیں کرتی ہے۔
آپ کو اب بھی درج ذیل چیزوں کا خیال رکھنے کی ضرورت پڑ سکتی ہے:
distributed transactions
shared databases
old schemas
tight coupling
unsupported runtimes
poor test coverage
organizational ownership
regulatory constraints
کچھ نظام جزوی منتقلی کو انتہائی مشکل بنا دیتے ہیں۔
مثال کے طور پر:
highly stateful systems
strongly coupled desktop applications
large transactional batch systems
systems with shared global state
کچھ صورتوں میں، منتقلی کی حد کو بڑا کرنے کی ضرورت پڑ سکتی ہے۔
اصول وہی رہتے ہیں۔
سب سے چھوٹی الٹ جانے والی تبدیلیاں کریں جو مفید منتقلی کی پیشرفت پیدا کرتی ہیں۔
اضافہ کا مطلب ہمیشہ چھوٹا نہیں ہوتا ہے۔ اس کا مطلب ہے کنٹرول شدہ۔
مکمل میراثی جدید کاری ورک فلو
یہ مضمون اس کام کے فلو کو ختم کرتا ہے جو ہم نے اس سلسلے میں قائم کیا ہے۔
ہم نے بنیادی مسئلہ سے آغاز کیا۔
آپ پراجیکٹ کو غلطی سے دوبارہ لکھے بغیر میراثی ایپلیکیشن کو جدید کیسے بناتے ہیں؟
پہلا قدم سمجھنا تھا۔
Legacy system
↓
investigate
↓
map behavior and dependencies
پھر کردار نگاری ۔
observed behavior
↓
tests
↓
behavioral safety net
پھر اسے ری ایکٹر کریں۔
entangled capability
↓
seams and boundaries
↓
migration-friendly structure
پھر تفریق کی جانچ۔
legacy implementation
+
new implementation
↓
behavior comparison
آخر میں، بڑھتی ہوئی منتقلی.
Understand
↓
Characterize
↓
Refactor
↓
Migrate
↓
Compare
↓
Route
↓
Observe
↓
Expand
↓
Remove legacy
آرڈر اہم ہے۔
افہام و تفہیم کو چھوڑنا غلط رویے کی منتقلی کا باعث بن سکتا ہے۔
اگر آپ کردار نگاری کو چھوڑ دیتے ہیں تو، رویے میں تبدیلیاں کسی کا دھیان نہیں جا سکتی ہیں۔
اگر آپ ری فیکٹرنگ کو چھوڑ دیتے ہیں، تو آپ کی منتقلی کی حدیں بہت بڑی ہو سکتی ہیں۔
موازنہ کو چھوڑنا اختلافات کو چھپا دیتا ہے۔
اگر آپ انکریمنٹل رول آؤٹ کو چھوڑ دیتے ہیں، تو آپ کو مکمل دھماکے کے رداس کے ساتھ مسائل نظر آئیں گے۔
ہر قدم مختلف قسم کی غیر یقینی صورتحال کو کم کرتا ہے۔
نتیجہ
میراثی ایپلیکیشنز کو جدید بنانے کے لیے ہر چیز کو ایک ساتھ تبدیل کرنے کی ضرورت نہیں ہے۔
زیادہ تر معاملات میں، ایک محفوظ حکمت عملی یہ ہے کہ موجودہ اور نئے نفاذ کو عارضی طور پر ایک ساتھ رہنے کے لیے راستہ بنایا جائے۔
ایک خصوصیت کو منتقل کریں اور پھر موازنہ کریں۔
تھوڑی سی ٹریفک کو روٹ کریں اور دیکھیں کہ کیا ہوتا ہے۔
اگر ثبوت اس کی تائید کرتے ہیں تو ٹریفک میں اضافہ کریں، اور اگر نہیں تو واپس جائیں۔
واضح طور پر ملکیت منتقل کریں اور پھر موجودہ راستے کو ہٹا دیں۔
اور دہرائیں۔
مکمل ورک فلو مندرجہ ذیل ہے:
Understand
↓
Characterize
↓
Refactor
↓
Migrate incrementally
↓
Compare behavior
↓
Progressively route traffic
↓
Observe
↓
Remove legacy
AI ہر قدم کو تیز تر بنا سکتا ہے۔
اس سے آپ کو کوڈ کا نقشہ بنانے، انحصار کی شناخت کرنے، اڈاپٹر بنانے، نفاذ کا موازنہ کرنے، غلطیوں کا تجزیہ کرنے اور منتقلی کے فرق کو چیک کرنے میں مدد مل سکتی ہے۔
لیکن رفتار اعتماد کے برابر نہیں ہے۔
اہم فیصلوں کے لیے اب بھی انجینئرنگ فیصلے کی ضرورت ہے۔
What behavior matters?
What can change?
What should remain compatible?
What is the migration boundary?
What evidence is enough?
When is rollback necessary?
When can the legacy path be removed?
یہ کوڈ جنریشن کا سوال نہیں ہے۔ یہ ہجرت کا فیصلہ ہے۔
اور یہ اس پوری سیریز کا سب سے بڑا سبق ہے۔
AI کے ساتھ، نیا کوڈ بنانا تیزی سے سستا ہوتا جاتا ہے۔ لیکن اس کا مطلب یہ نہیں ہے کہ میراثی جدیدیت معمولی ہے۔ آپ کے کوڈ کے فیصلوں کا معیار زیادہ اہم ہو جاتا ہے۔
اس کی وجہ یہ ہے کہ سب سے محفوظ ہجرتیں وہ ہیں جو زیادہ تر سافٹ ویئر کو تبدیل نہیں کرتی ہیں۔ یہ آپ کو اپنے سسٹم میں تبدیلیاں کرنے کی اجازت دیتا ہے جب کہ مسلسل یہ جانتے ہوئے کہ کیا تبدیل ہوا ہے، یہ کیوں تبدیل ہوا ہے، اور آیا اس کا استعمال جاری رکھنا محفوظ ہے۔