بی پی ایم اسٹائل انضمام

بی پی ایم اسٹائل انضمام

ہائے حبر!

ہماری کمپنی ERP-کلاس سافٹ ویئر سلوشنز کی تیاری میں مہارت رکھتی ہے، جس کا بڑا حصہ لین دین کے نظاموں کے زیر قبضہ ہے جس میں کاروباری منطق اور دستاویز کا بہاؤ لا EDMS ہے۔ ہماری مصنوعات کے موجودہ ورژن JavaEE ٹیکنالوجیز پر مبنی ہیں، لیکن ہم مائیکرو سروسز کے ساتھ بھی فعال طور پر تجربہ کر رہے ہیں۔ اس طرح کے حل کے سب سے زیادہ مشکل علاقوں میں سے ایک ملحقہ ڈومینز سے تعلق رکھنے والے مختلف سب سسٹمز کا انضمام ہے۔ انضمام کے مسائل نے ہمیشہ ہمارے لیے ایک بہت بڑا سر درد دیا ہے، قطع نظر اس کے کہ ہم جو بھی تعمیراتی انداز، ٹیکنالوجی کے ڈھیر اور فریم ورک استعمال کرتے ہیں، لیکن حال ہی میں ایسے مسائل کو حل کرنے میں پیش رفت ہوئی ہے۔

اس مضمون میں جو میں آپ کی توجہ مبذول کرواتا ہوں، میں اس تجربے اور تعمیراتی تحقیق کے بارے میں بات کروں گا جو کہ NPO کرسٹا کے مخصوص علاقے میں ہے۔ ہم ایک ایپلی کیشن ڈویلپر کے نقطہ نظر سے انضمام کے مسئلے کے آسان حل کی ایک مثال بھی دیکھیں گے اور معلوم کریں گے کہ اس سادگی کے پیچھے کیا چھپا ہے۔

دستبرداری

آرٹیکل میں بیان کردہ تعمیراتی اور تکنیکی حل میری طرف سے مخصوص کاموں کے تناظر میں ذاتی تجربے کی بنیاد پر تجویز کیے گئے ہیں۔ یہ حل آفاقی ہونے کا دعوی نہیں کرتے ہیں اور استعمال کی دیگر شرائط کے تحت بہترین نہیں ہوسکتے ہیں۔

بی پی ایم کا اس سے کیا تعلق ہے؟

اس سوال کا جواب دینے کے لیے، ہمیں اپنے حل کے لاگو کردہ مسائل کی تفصیلات میں تھوڑا گہرائی میں جانے کی ضرورت ہے۔ ہمارے عام لین دین کے نظام میں کاروباری منطق کا بنیادی حصہ یوزر انٹرفیس کے ذریعے ڈیٹا بیس میں ڈیٹا داخل کرنا، اس ڈیٹا کی دستی اور خودکار تصدیق کرنا، اسے کچھ ورک فلو کے ذریعے انجام دینا، اسے کسی دوسرے سسٹم/تجزیاتی ڈیٹا بیس/آرکائیو میں شائع کرنا، رپورٹس تیار کرنا ہے۔ . اس طرح، صارفین کے لیے نظام کا کلیدی کام ان کے داخلی کاروباری عمل کو خودکار بنانا ہے۔

سہولت کے لیے، ہم مواصلت میں "دستاویز" کی اصطلاح استعمال کرتے ہیں جیسے کہ ایک مشترکہ کلید کے ذریعے متحد ڈیٹا کے سیٹ کے کچھ تجرید کے طور پر جس سے کسی خاص ورک فلو کو "منسلک" کیا جا سکتا ہے۔
لیکن انضمام کی منطق کا کیا ہوگا؟ بہر حال، انضمام کا کام سسٹم کے فن تعمیر سے پیدا ہوتا ہے، جسے گاہک کی درخواست پر نہیں بلکہ مکمل طور پر مختلف عوامل کے زیر اثر حصوں میں "کاٹ" جاتا ہے:

  • کانوے کے قانون کے تابع؛
  • دیگر مصنوعات کے لیے پہلے تیار کردہ سب سسٹمز کو دوبارہ استعمال کرنے کے نتیجے میں؛
  • معمار کی صوابدید پر، غیر فعال ضروریات کی بنیاد پر۔

مرکزی ورک فلو کی کاروباری منطق سے انضمام کی منطق کو الگ کرنے کا ایک بہت بڑا فتنہ ہے، تاکہ کاروباری منطق کو انضمام کے نمونوں سے آلودہ نہ کیا جائے اور ایپلیکیشن ڈویلپر کو سسٹم کے آرکیٹیکچرل لینڈ اسکیپ کی خصوصیات کو جاننے کی ضرورت سے بچایا جائے۔ اس نقطہ نظر کے بہت سے فوائد ہیں، لیکن مشق اس کی غیر موثریت کو ظاہر کرتی ہے:

  • انضمام کے مسائل کو حل کرنا عام طور پر مرکزی ورک فلو کے نفاذ میں محدود توسیعی پوائنٹس کی وجہ سے مطابقت پذیر کالوں کی صورت میں آسان ترین اختیارات پر واپس آتا ہے (ہم وقت ساز انضمام کے نقصانات ذیل میں زیر بحث ہیں)؛
  • انضمام کے نمونے اب بھی بنیادی کاروباری منطق میں داخل ہوتے ہیں جب کسی دوسرے سب سسٹم سے فیڈ بیک کی ضرورت ہوتی ہے۔
  • ایپلیکیشن ڈویلپر انضمام کو نظر انداز کرتا ہے اور ورک فلو کو تبدیل کرکے اسے آسانی سے توڑ سکتا ہے۔
  • صارف کے نقطہ نظر سے سسٹم مکمل طور پر ختم ہو جاتا ہے، سب سسٹمز کے درمیان "سیمز" نمایاں ہو جاتے ہیں، اور صارف کے بے کار آپریشن ظاہر ہوتے ہیں، جس سے ڈیٹا کی ایک سب سسٹم سے دوسرے میں منتقلی شروع ہو جاتی ہے۔

ایک اور نقطہ نظر یہ ہے کہ انضمام کے تعاملات کو بنیادی کاروباری منطق اور ورک فلو کا ایک لازمی حصہ سمجھیں۔ ایپلیکیشن ڈویلپر کی قابلیت کو آسمان چھونے سے روکنے کے لیے، نئے انضمام کے تعاملات کو تخلیق کرنا آسان اور آسان ہونا چاہیے، حل کو منتخب کرنے کے کم سے کم مواقع کے ساتھ۔ ایسا کرنا اس سے کہیں زیادہ مشکل ہے جتنا لگتا ہے: ٹول اتنا طاقتور ہونا چاہیے کہ صارف کو اس کے استعمال کے لیے مطلوبہ مختلف قسم کے اختیارات فراہم کر سکے، بغیر اسے "خود کو پاؤں میں گولی مارنے" کی اجازت دی جائے۔ بہت سے سوالات ہیں جن کا جواب ایک انجینئر کو انضمام کے کاموں کے تناظر میں دینا چاہیے، لیکن جن کے بارے میں ایک ایپلیکیشن ڈویلپر کو اپنے روزمرہ کے کام میں نہیں سوچنا چاہیے: لین دین کی حدود، مستقل مزاجی، جوہری، سیکورٹی، اسکیلنگ، لوڈ اور وسائل کی تقسیم، روٹنگ، مارشلنگ، ڈسٹری بیوشن اور سوئچنگ سیاق و سباق وغیرہ۔ ایپلیکیشن ڈویلپرز کو کافی آسان حل ٹیمپلیٹس پیش کرنا ضروری ہے جس میں اس طرح کے تمام سوالات کے جواب پہلے ہی پوشیدہ ہیں۔ یہ ٹیمپلیٹس کافی حد تک محفوظ ہونے چاہئیں: کاروباری منطق اکثر بدلتی رہتی ہے، جس سے غلطیاں متعارف ہونے کا خطرہ بڑھ جاتا ہے، غلطیوں کی قیمت کافی حد تک کم رہنی چاہیے۔

لیکن بی پی ایم کا اس سے کیا لینا دینا؟ ورک فلو کو لاگو کرنے کے لیے بہت سے اختیارات ہیں...
درحقیقت، کاروباری عمل کا ایک اور نفاذ ہمارے حلوں میں بہت مقبول ہے - ریاستی منتقلی کے خاکے کی اعلانیہ تعریف اور ٹرانزیشن کے لیے کاروباری منطق کے ساتھ ہینڈلرز کے کنکشن کے ذریعے۔ اس صورت میں، ریاست جو کاروباری عمل میں "دستاویز" کی موجودہ پوزیشن کا تعین کرتی ہے وہ خود "دستاویز" کا ایک وصف ہے۔

بی پی ایم اسٹائل انضمام
یہ وہی ہے جو ایک پروجیکٹ کے آغاز میں عمل کی طرح لگتا ہے۔

اس نفاذ کی مقبولیت نسبتاً سادگی اور لکیری کاروباری عمل کو بنانے کی رفتار کی وجہ سے ہے۔ تاہم، جیسا کہ سافٹ ویئر کے نظام مسلسل پیچیدہ ہوتے جاتے ہیں، کاروباری عمل کا خودکار حصہ بڑھتا اور پیچیدہ ہوتا جاتا ہے۔ سڑنے، عمل کے حصوں کے دوبارہ استعمال کے ساتھ ساتھ برانچنگ کے عمل کی ضرورت ہے تاکہ ہر شاخ کو متوازی طور پر انجام دیا جائے۔ اس طرح کے حالات میں، ٹول تکلیف دہ ہو جاتا ہے، اور ریاست کی منتقلی کا خاکہ اپنے معلوماتی مواد کو کھو دیتا ہے (انضمام کے تعاملات خاکہ میں بالکل بھی ظاہر نہیں ہوتے ہیں)۔

بی پی ایم اسٹائل انضمام
تقاضوں کی وضاحت کے کئی تکرار کے بعد یہ عمل ایسا لگتا ہے۔

اس صورتحال سے نکلنے کا راستہ انجن کا انضمام تھا۔ jBPM انتہائی پیچیدہ کاروباری عمل کے ساتھ کچھ مصنوعات میں۔ مختصر مدت میں، اس حل کو کچھ کامیابی ملی: اشارے میں کافی معلوماتی اور متعلقہ خاکہ کو برقرار رکھتے ہوئے پیچیدہ کاروباری عمل کو نافذ کرنا ممکن ہو گیا۔ بی پی ایم این 2.

بی پی ایم اسٹائل انضمام
ایک پیچیدہ کاروباری عمل کا ایک چھوٹا سا حصہ

طویل مدتی میں، حل توقعات پر پورا نہیں اترا: بصری ٹولز کے ذریعے کاروباری عمل کو تخلیق کرنے میں محنت کی زیادہ شدت نے قابل قبول پیداواری اشاریوں کو حاصل کرنے کی اجازت نہیں دی، اور یہ آلہ خود ڈویلپرز میں سب سے زیادہ ناپسندیدہ بن گیا۔ انجن کے اندرونی ڈھانچے کے بارے میں بھی شکایات تھیں، جس کی وجہ سے بہت سے "پیچ" اور "بیساکھیوں" کی ظاہری شکل سامنے آئی۔

jBPM استعمال کرنے کا اہم مثبت پہلو کاروباری عمل مثال کی اپنی مستقل حالت رکھنے کے فوائد اور نقصانات کے بارے میں آگاہی تھا۔ ہم نے سگنلز اور پیغامات کے ذریعے متضاد تعاملات کا استعمال کرتے ہوئے مختلف ایپلی کیشنز کے درمیان پیچیدہ انضمام پروٹوکول کو لاگو کرنے کے لیے ایک پراسیس اپروچ استعمال کرنے کا امکان بھی دیکھا۔ ایک مستقل ریاست کی موجودگی اس میں اہم کردار ادا کرتی ہے۔

مندرجہ بالا کی بنیاد پر، ہم یہ نتیجہ اخذ کر سکتے ہیں: BPM طرز میں عمل کا نقطہ نظر ہمیں تیزی سے پیچیدہ کاروباری عملوں کو خودکار بنانے، ان عملوں میں ہم آہنگی کے ساتھ انضمام کی سرگرمیوں کو فٹ کرنے اور لاگو شدہ عمل کو ایک مناسب اشارے میں بصری طور پر ظاہر کرنے کی صلاحیت کو برقرار رکھنے کے لیے کاموں کی ایک وسیع رینج کو حل کرنے کی اجازت دیتا ہے۔

انضمام پیٹرن کے طور پر ہم وقت ساز کالوں کے نقصانات

ہم وقت ساز انضمام سے مراد سب سے آسان بلاکنگ کال ہے۔ ایک سب سسٹم سرور سائڈ کے طور پر کام کرتا ہے اور مطلوبہ طریقہ کے ساتھ API کو بے نقاب کرتا ہے۔ ایک اور سب سسٹم کلائنٹ کی طرف کام کرتا ہے اور صحیح وقت پر کال کرتا ہے اور نتیجہ کا انتظار کرتا ہے۔ سسٹم کے فن تعمیر پر منحصر ہے، کلائنٹ اور سرور سائڈز یا تو ایک ہی ایپلیکیشن اور پروسیس میں، یا مختلف میں واقع ہوسکتے ہیں۔ دوسری صورت میں، آپ کو کچھ RPC نفاذ کو لاگو کرنے اور پیرامیٹرز کی مارشلنگ اور کال کا نتیجہ فراہم کرنے کی ضرورت ہے۔

بی پی ایم اسٹائل انضمام

اس انضمام کے پیٹرن کے نقصانات کا کافی بڑا مجموعہ ہے، لیکن یہ اپنی سادگی کی وجہ سے عملی طور پر بہت وسیع پیمانے پر استعمال ہوتا ہے۔ عمل درآمد کی رفتار آپ کو موہ لیتی ہے اور آپ کو مجبور کرتی ہے کہ آپ اسے بار بار استعمال کرنے پر مجبور کر دیں، ڈیڈ لائن کو دبانے، حل کو تکنیکی قرض کے طور پر ریکارڈ کریں۔ لیکن یہ بھی ہوتا ہے کہ ناتجربہ کار ڈویلپر اسے غیر شعوری طور پر استعمال کرتے ہیں، محض منفی نتائج کا ادراک نہیں کرتے۔

سب سسٹم کنیکٹیویٹی میں سب سے زیادہ واضح اضافے کے علاوہ، "بڑھتی" اور "تڑھائی" لین دین کے ساتھ کم واضح مسائل بھی ہیں۔ درحقیقت، اگر کاروباری منطق کچھ تبدیلیاں کرتی ہے، تو لین دین سے گریز نہیں کیا جا سکتا، اور لین دین، بدلے میں، ان تبدیلیوں سے متاثر ہونے والے کچھ ایپلیکیشن وسائل کو روکتا ہے۔ یعنی، جب تک ایک سب سسٹم دوسرے سے جواب کا انتظار نہیں کرتا، وہ لین دین کو مکمل کرنے اور تالے کو ہٹانے کے قابل نہیں ہوگا۔ یہ مختلف اثرات کے خطرے کو نمایاں طور پر بڑھاتا ہے:

  • سسٹم کی ردعمل ختم ہو گئی ہے، صارفین سوالات کے جوابات کے لیے طویل انتظار کرتے ہیں۔
  • سرور عام طور پر صارف کی درخواستوں کا جواب دینا بند کر دیتا ہے کیونکہ بہت زیادہ دھاگے کے پول کی وجہ سے: تھریڈز کی اکثریت کسی لین دین کے زیر قبضہ وسائل پر مقفل ہوتی ہے۔
  • تعطل ظاہر ہونا شروع ہو جاتا ہے: ان کے وقوع پذیر ہونے کا امکان لین دین کی مدت، کاروباری منطق کی مقدار اور لین دین میں شامل تالے پر منحصر ہوتا ہے۔
  • ٹرانزیکشن ٹائم آؤٹ کی خرابیاں ظاہر ہوتی ہیں؛
  • سرور OutOfMemory کے ساتھ "ناکام" ہو جاتا ہے اگر ٹاسک کو بڑی مقدار میں ڈیٹا کی پروسیسنگ اور تبدیلی کی ضرورت ہو، اور ہم وقت ساز انضمام کی موجودگی پروسیسنگ کو "ہلکے" لین دین میں تقسیم کرنا بہت مشکل بنا دیتی ہے۔

آرکیٹیکچرل نقطہ نظر سے، انضمام کے دوران بلاکنگ کالز کا استعمال انفرادی سب سسٹم کے معیار پر کنٹرول کے نقصان کا باعث بنتا ہے: ایک سب سسٹم کے ہدف کوالٹی اشاریوں کو دوسرے سب سسٹم کے معیار کے اشاریوں سے الگ کر کے یقینی بنانا ناممکن ہے۔ اگر ذیلی نظام مختلف ٹیموں کے ذریعہ تیار کیے جاتے ہیں، تو یہ ایک بڑا مسئلہ ہے۔

چیزیں اور بھی دلچسپ ہو جاتی ہیں اگر انٹیگریٹ کیے جانے والے سب سسٹمز مختلف ایپلی کیشنز میں ہوں اور آپ کو دونوں طرف ہم وقت ساز تبدیلیاں کرنے کی ضرورت ہو۔ ان تبدیلیوں کی لین دین کو کیسے یقینی بنایا جائے؟

اگر تبدیلیاں الگ الگ لین دین میں کی جاتی ہیں، تو آپ کو قابل اعتماد استثنیٰ ہینڈلنگ اور معاوضہ فراہم کرنے کی ضرورت ہوگی، اور یہ ہم آہنگی کے انضمام کا بنیادی فائدہ - سادگی کو مکمل طور پر ختم کر دیتا ہے۔

تقسیم شدہ لین دین بھی ذہن میں آتے ہیں، لیکن ہم انہیں اپنے حل میں استعمال نہیں کرتے: قابل اعتمادی کو یقینی بنانا مشکل ہے۔

لین دین کے مسئلے کے حل کے طور پر "ساگا"

مائیکرو سروسز کی بڑھتی ہوئی مقبولیت کے ساتھ، کی مانگ ساگا پیٹرن.

یہ پیٹرن طویل لین دین کے مذکورہ بالا مسائل کو مکمل طور پر حل کرتا ہے، اور کاروباری منطق کی طرف سے نظام کی حالت کو منظم کرنے کی صلاحیتوں کو بھی بڑھاتا ہے: ناکام لین دین کے بعد معاوضہ سسٹم کو اس کی اصل حالت میں واپس نہیں لا سکتا، لیکن فراہم کرتا ہے۔ ڈیٹا پروسیسنگ کا متبادل راستہ۔ یہ آپ کو ڈیٹا پراسیسنگ کے کامیابی سے مکمل ہونے والے مراحل کو دہرانے سے بچنے کی بھی اجازت دیتا ہے جب آپ اس عمل کو "اچھے" انجام تک پہنچانے کی کوشش کرتے ہیں۔

دلچسپ بات یہ ہے کہ یک سنگی نظاموں میں یہ نمونہ اس وقت بھی متعلقہ ہوتا ہے جب بات ڈھیلے سے جوڑے ہوئے سب سسٹمز کے انضمام کی ہو اور طویل عرصے سے چلنے والے لین دین اور متعلقہ وسائل کے تالے کی وجہ سے ہونے والے منفی اثرات دیکھے جاتے ہیں۔

BPM طرز میں ہمارے کاروباری عمل کے سلسلے میں، "ساگاس" کو لاگو کرنا بہت آسان ہے: "ساگا" کے انفرادی مراحل کو کاروباری عمل کے اندر سرگرمیوں کے طور پر بیان کیا جا سکتا ہے، اور کاروباری عمل کی مستقل حالت بھی۔ "ساگا" کی اندرونی حالت کا تعین کرتا ہے۔ یعنی ہمیں کسی اضافی کوآرڈینیشن میکانزم کی ضرورت نہیں ہے۔ آپ کو صرف ایک پیغام بروکر کی ضرورت ہے جو نقل و حمل کے طور پر "کم از کم ایک بار" ضمانتوں کی حمایت کرتا ہے۔

لیکن اس حل کی اپنی "قیمت" بھی ہے:

  • کاروباری منطق زیادہ پیچیدہ ہو جاتی ہے: معاوضے پر کام کرنے کی ضرورت ہے۔
  • مکمل مستقل مزاجی کو ترک کرنا ضروری ہو گا، جو یک سنگی نظاموں کے لیے خاص طور پر حساس ہو سکتا ہے۔
  • فن تعمیر قدرے پیچیدہ ہو جاتا ہے، اور میسج بروکر کی اضافی ضرورت ظاہر ہوتی ہے۔
  • اضافی مانیٹرنگ اور ایڈمنسٹریشن ٹولز کی ضرورت ہوگی (اگرچہ عام طور پر یہ اچھا ہے: سسٹم سروس کا معیار بڑھے گا)۔

یک سنگی نظاموں کے لیے، "Sag" کے استعمال کا جواز اتنا واضح نہیں ہے۔ مائیکرو سروسز اور دیگر SOA کے لیے، جہاں غالباً پہلے سے ہی ایک بروکر موجود ہے، اور پروجیکٹ کے آغاز میں پوری مستقل مزاجی کی قربانی دی جاتی ہے، اس پیٹرن کو استعمال کرنے کے فوائد نقصانات سے نمایاں طور پر بڑھ سکتے ہیں، خاص طور پر اگر کاروباری منطق میں ایک آسان API موجود ہو۔ سطح

مائیکرو سروسز میں کاروباری منطق کو سمیٹنا

جب ہم نے مائیکرو سروسز کے ساتھ تجربہ کرنا شروع کیا تو ایک معقول سوال پیدا ہوا: ڈومین کے ڈیٹا کی برقراری کو یقینی بنانے والی سروس کے سلسلے میں ڈومین بزنس منطق کو کہاں رکھا جائے؟

مختلف BPMSs کے فن تعمیر کو دیکھتے ہوئے، کاروباری منطق کو استقامت سے الگ کرنا مناسب معلوم ہو سکتا ہے: پلیٹ فارم اور ڈومین سے آزاد مائیکرو سروسز کی ایک پرت بنائیں جو ڈومین بزنس منطق کو انجام دینے کے لیے ایک ماحول اور کنٹینر بنائے، اور ڈومین ڈیٹا کی استقامت کو اس طرح ڈیزائن کریں۔ بہت سادہ اور ہلکے وزن والے مائیکرو سروسز کی ایک الگ پرت۔ اس معاملے میں کاروباری عمل استقامت کی پرت کی خدمات کا آرکسٹریشن انجام دیتے ہیں۔

بی پی ایم اسٹائل انضمام

اس نقطہ نظر کا ایک بہت بڑا فائدہ ہے: آپ پلیٹ فارم کی فعالیت کو جتنا چاہیں بڑھا سکتے ہیں، اور اس سے پلیٹ فارم مائیکرو سروسز کی صرف متعلقہ پرت ہی "موٹی" بن جائے گی۔ کسی بھی ڈومین سے کاروباری عمل اس کے اپ ڈیٹ ہوتے ہی پلیٹ فارم کی نئی فعالیت کو فوری طور پر استعمال کرنے کے قابل ہو جاتے ہیں۔

مزید تفصیلی مطالعہ نے اس نقطہ نظر کے اہم نقصانات کا انکشاف کیا:

  • ایک پلیٹ فارم سروس جو بہت سے ڈومینز کی کاروباری منطق کو ایک ساتھ انجام دیتی ہے ناکامی کے ایک نقطہ کے طور پر بہت زیادہ خطرات کا باعث بنتی ہے۔ کاروباری منطق میں متواتر تبدیلیاں غلطیوں کے خطرے کو بڑھاتی ہیں جس کی وجہ سے سسٹم کی وسیع ناکامی ہوتی ہے۔
  • کارکردگی کے مسائل: کاروباری منطق اپنے ڈیٹا کے ساتھ ایک تنگ اور سست انٹرفیس کے ذریعے کام کرتی ہے۔
    • ڈیٹا کو ایک بار پھر مارشل کیا جائے گا اور نیٹ ورک اسٹیک کے ذریعے پمپ کیا جائے گا۔
    • سروس کے بیرونی API کی سطح پر پیرامیٹرائزنگ کی درخواستوں کے لیے ناکافی صلاحیتوں کی وجہ سے ایک ڈومین سروس اکثر کاروباری منطق کے لیے ضرورت سے زیادہ ڈیٹا فراہم کرے گی۔
    • کاروباری منطق کے کئی آزاد ٹکڑے بار بار پروسیسنگ کے لیے ایک ہی ڈیٹا کی دوبارہ درخواست کر سکتے ہیں (اس مسئلے کو سیشن کے اجزاء شامل کر کے کم کیا جا سکتا ہے جو ڈیٹا کو کیش کرتے ہیں، لیکن یہ فن تعمیر کو مزید پیچیدہ بناتا ہے اور ڈیٹا کی مطابقت اور کیشے کی غلط کاری کے مسائل پیدا کرتا ہے)؛
  • لین دین کے مسائل:
    • مستقل حالت کے ساتھ کاروباری عمل، جو پلیٹ فارم سروس کے ذریعہ محفوظ کیا جاتا ہے، ڈومین ڈیٹا سے مطابقت نہیں رکھتا، اور اس مسئلے کو حل کرنے کے کوئی آسان طریقے نہیں ہیں۔
    • ڈومین ڈیٹا کو ٹرانزیکشن سے باہر بلاک کرنا: اگر ڈومین بزنس منطق کو پہلے موجودہ ڈیٹا کی درستگی کی جانچ کرنے کے بعد تبدیلیاں کرنے کی ضرورت ہے، تو یہ ضروری ہے کہ پروسیس شدہ ڈیٹا میں مسابقتی تبدیلی کے امکان کو خارج کر دیا جائے۔ بیرونی ڈیٹا بلاک کرنے سے مسئلے کو حل کرنے میں مدد مل سکتی ہے، لیکن اس طرح کے حل سے اضافی خطرات لاحق ہوتے ہیں اور سسٹم کی مجموعی اعتبار کو کم کر دیتے ہیں۔
  • اپ ڈیٹ کرتے وقت اضافی مشکلات: بعض صورتوں میں، استقامت کی خدمت اور کاروباری منطق کو ہم آہنگی سے یا سخت ترتیب میں اپ ڈیٹ کرنے کی ضرورت ہے۔

بالآخر، ہمیں بنیادی باتوں پر واپس جانا پڑا: ڈومین ڈیٹا اور ڈومین بزنس منطق کو ایک مائیکرو سروس میں سمیٹیں۔ یہ نقطہ نظر مائیکرو سروس کے نظام کے ایک لازمی جزو کے طور پر تصور کو آسان بناتا ہے اور مندرجہ بالا مسائل کو جنم نہیں دیتا ہے۔ یہ بھی مفت میں نہیں دیا جاتا:

  • کاروباری منطق (خاص طور پر، کاروباری عمل کے حصے کے طور پر صارف کی سرگرمیاں فراہم کرنے کے لیے) اور API پلیٹ فارم کی خدمات کے ساتھ تعامل کے لیے API معیاری کاری کی ضرورت ہے۔ API کی تبدیلیوں، آگے اور پیچھے کی مطابقت پر زیادہ محتاط توجہ کی ضرورت ہے۔
  • ایسی ہر مائیکرو سروس کے حصے کے طور پر کاروباری منطق کے کام کو یقینی بنانے کے لیے اضافی رن ٹائم لائبریریوں کو شامل کرنا ضروری ہے، اور یہ ایسی لائبریریوں کے لیے نئی ضروریات کو جنم دیتا ہے: ہلکا پن اور کم از کم عبوری انحصار؛
  • بزنس لاجک ڈویلپرز کو لائبریری کے ورژن کی نگرانی کرنے کی ضرورت ہے: اگر ایک طویل عرصے سے مائیکرو سروس کو حتمی شکل نہیں دی گئی ہے، تو اس میں اکثر لائبریریوں کا پرانا ورژن ہوگا۔ یہ ایک نئی خصوصیت شامل کرنے میں ایک غیر متوقع رکاوٹ ہو سکتی ہے اور اگر ورژن کے درمیان غیر مطابقت پذیر تبدیلیاں ہوں تو اس طرح کی سروس کی پرانی کاروباری منطق کو لائبریریوں کے نئے ورژن میں منتقل کرنے کی ضرورت پڑ سکتی ہے۔

بی پی ایم اسٹائل انضمام

اس طرح کے فن تعمیر میں پلیٹ فارم سروسز کی ایک پرت بھی موجود ہوتی ہے، لیکن یہ پرت اب ڈومین بزنس منطق کو انجام دینے کے لیے کنٹینر نہیں بناتی، بلکہ صرف اس کا ماحول، معاون "پلیٹ فارم" افعال فراہم کرتی ہے۔ ایسی پرت کی ضرورت نہ صرف ڈومین مائیکرو سروسز کی ہلکی پھلکی نوعیت کو برقرار رکھنے کے لیے ہے، بلکہ نظم و نسق کو مرکزی بنانے کے لیے بھی ہے۔

مثال کے طور پر، کاروباری عمل میں صارف کی سرگرمیاں کام پیدا کرتی ہیں۔ تاہم، کاموں کے ساتھ کام کرتے وقت، صارف کو عمومی فہرست میں تمام ڈومینز کے کاموں کو دیکھنا چاہیے، جس کا مطلب ہے کہ متعلقہ پلیٹ فارم ٹاسک رجسٹریشن سروس ہونی چاہیے، جو ڈومین بزنس منطق سے پاک ہو۔ ایسے سیاق و سباق میں کاروباری منطق کی انکیپسولیشن کو برقرار رکھنا کافی مشکل ہے، اور یہ اس فن تعمیر کا ایک اور سمجھوتہ ہے۔

ایپلیکیشن ڈویلپر کی نظروں سے کاروباری عمل کا انضمام

جیسا کہ اوپر ذکر کیا گیا ہے، ایک ایپلیکیشن ڈویلپر کو متعدد ایپلی کیشنز کے تعامل کو لاگو کرنے کی تکنیکی اور انجینئرنگ خصوصیات سے خلاصہ ہونا چاہئے تاکہ کوئی اچھی ترقی کی پیداوار پر اعتماد کر سکے۔

آئیے ایک مشکل انضمام کے مسئلے کو حل کرنے کی کوشش کرتے ہیں، خاص طور پر مضمون کے لیے ایجاد کیا گیا ہے۔ یہ ایک "گیم" کام ہوگا جس میں تین ایپلیکیشنز شامل ہوں گی، جہاں ان میں سے ہر ایک مخصوص ڈومین نام کی وضاحت کرتا ہے: "app1"، "app2"، "app3"۔

ہر ایپلیکیشن کے اندر، کاروباری عمل شروع کیے جاتے ہیں جو انٹیگریشن بس کے ذریعے "گیند کھیلنا" شروع کرتے ہیں۔ "بال" نام والے پیغامات گیند کے طور پر کام کریں گے۔

کھیل کے قواعد:

  • پہلا کھلاڑی شروع کرنے والا ہے۔ وہ دوسرے کھلاڑیوں کو کھیل میں مدعو کرتا ہے، کھیل شروع کرتا ہے اور اسے کسی بھی وقت ختم کر سکتا ہے۔
  • دوسرے کھلاڑی کھیل میں اپنی شرکت کا اعلان کرتے ہیں، ایک دوسرے اور پہلے کھلاڑی کو "جانیں"؛
  • گیند حاصل کرنے کے بعد، کھلاڑی دوسرے حصہ لینے والے کھلاڑی کو منتخب کرتا ہے اور گیند کو اس کے پاس دیتا ہے۔ ٹرانسمیشنز کی کل تعداد شمار کی جاتی ہے۔
  • ہر کھلاڑی میں "توانائی" ہوتی ہے جو اس کھلاڑی کے گیند کے ہر پاس کے ساتھ کم ہوتی ہے۔ جب توانائی ختم ہو جاتی ہے، کھلاڑی اپنے استعفیٰ کا اعلان کرتے ہوئے کھیل چھوڑ دیتا ہے۔
  • اگر کھلاڑی اکیلا رہ جاتا ہے، تو وہ فوری طور پر اپنی روانگی کا اعلان کرتا ہے۔
  • جب تمام کھلاڑی ختم ہو جاتے ہیں، پہلا کھلاڑی کھیل ختم ہونے کا اعلان کرتا ہے۔ اگر وہ کھیل کو جلد چھوڑ دیتا ہے، تو وہ اسے مکمل کرنے کے لیے کھیل کی پیروی کرتا رہتا ہے۔

اس مسئلے کو حل کرنے کے لیے، میں اپنے DSL کو کاروباری عمل کے لیے استعمال کروں گا، جو ہمیں کم سے کم بوائلر پلیٹ کے ساتھ کوٹلن میں منطق کو واضح طور پر بیان کرنے کی اجازت دیتا ہے۔

پہلے کھلاڑی (عرف گیم کا آغاز کرنے والا) کا کاروباری عمل app1 ایپلیکیشن میں کام کرے گا:

کلاس ابتدائی کھلاڑی

import ru.krista.bpm.ProcessInstance
import ru.krista.bpm.runtime.ProcessImpl
import ru.krista.bpm.runtime.constraint.UniqueConstraints
import ru.krista.bpm.runtime.dsl.processModel
import ru.krista.bpm.runtime.dsl.taskOperation
import ru.krista.bpm.runtime.instance.MessageSendInstance

data class PlayerInfo(val name: String, val domain: String, val id: String)

class PlayersList : ArrayList<PlayerInfo>()

// Это класс экземпляра процесса: инкапсулирует его внутреннее состояние
class InitialPlayer : ProcessImpl<InitialPlayer>(initialPlayerModel) {
    var playerName: String by persistent("Player1")
    var energy: Int by persistent(30)
    var players: PlayersList by persistent(PlayersList())
    var shotCounter: Int = 0
}

// Это декларация модели процесса: создается один раз, используется всеми
// экземплярами процесса соответствующего класса
val initialPlayerModel = processModel<InitialPlayer>(name = "InitialPlayer",
                                                     version = 1) {

    // По правилам, первый игрок является инициатором игры и должен быть единственным
    uniqueConstraint = UniqueConstraints.singleton

    // Объявляем активности, из которых состоит бизнес-процесс
    val sendNewGameSignal = signal<String>("NewGame")
    val sendStopGameSignal = signal<String>("StopGame")
    val startTask = humanTask("Start") {
        taskOperation {
            processCondition { players.size > 0 }
            confirmation { "Подключилось ${players.size} игроков. Начинаем?" }
        }
    }
    val stopTask = humanTask("Stop") {
        taskOperation {}
    }
    val waitPlayerJoin = signalWait<String>("PlayerJoin") { signal ->
        players.add(PlayerInfo(
                signal.data!!,
                signal.sender.domain,
                signal.sender.processInstanceId))
        println("... join player ${signal.data} ...")
    }
    val waitPlayerOut = signalWait<String>("PlayerOut") { signal ->
        players.remove(PlayerInfo(
                signal.data!!,
                signal.sender.domain,
                signal.sender.processInstanceId))
        println("... player ${signal.data} is out ...")
    }
    val sendPlayerOut = signal<String>("PlayerOut") {
        signalData = { playerName }
    }
    val sendHandshake = messageSend<String>("Handshake") {
        messageData = { playerName }
        activation = {
            receiverDomain = process.players.last().domain
            receiverProcessInstanceId = process.players.last().id
        }
    }
    val throwStartBall = messageSend<Int>("Ball") {
        messageData = { 1 }
        activation = { selectNextPlayer() }
    }
    val throwBall = messageSend<Int>("Ball") {
        messageData = { shotCounter + 1 }
        activation = { selectNextPlayer() }
        onEntry { energy -= 1 }
    }
    val waitBall = messageWaitData<Int>("Ball") {
        shotCounter = it
    }

    // Теперь конструируем граф процесса из объявленных активностей
    startFrom(sendNewGameSignal)
            .fork("mainFork") {
                next(startTask)
                next(waitPlayerJoin).next(sendHandshake).next(waitPlayerJoin)
                next(waitPlayerOut)
                        .branch("checkPlayers") {
                            ifTrue { players.isEmpty() }
                                    .next(sendStopGameSignal)
                                    .terminate()
                            ifElse().next(waitPlayerOut)
                        }
            }
    startTask.fork("afterStart") {
        next(throwStartBall)
                .branch("mainLoop") {
                    ifTrue { energy < 5 }.next(sendPlayerOut).next(waitBall)
                    ifElse().next(waitBall).next(throwBall).loop()
                }
        next(stopTask).next(sendStopGameSignal)
    }

    // Навешаем на активности дополнительные обработчики для логирования
    sendNewGameSignal.onExit { println("Let's play!") }
    sendStopGameSignal.onExit { println("Stop!") }
    sendPlayerOut.onExit { println("$playerName: I'm out!") }
}

private fun MessageSendInstance<InitialPlayer, Int>.selectNextPlayer() {
    val player = process.players.random()
    receiverDomain = player.domain
    receiverProcessInstanceId = player.id
    println("Step ${process.shotCounter + 1}: " +
            "${process.playerName} >>> ${player.name}")
}

کاروباری منطق کو عملی جامہ پہنانے کے علاوہ، مندرجہ بالا کوڈ کاروباری عمل کا ایک آبجیکٹ ماڈل تیار کر سکتا ہے، جسے ایک خاکہ کی شکل میں دیکھا جا سکتا ہے۔ ہم نے ابھی تک ویژولائزر کو لاگو نہیں کیا ہے، اس لیے ہمیں ڈرائنگ میں تھوڑا وقت گزارنا پڑا (یہاں میں نے نیچے دیے گئے کوڈ کے ساتھ ڈایاگرام کی مستقل مزاجی کو بہتر بنانے کے لیے گیٹس کے استعمال کے حوالے سے BPMN اشارے کو قدرے آسان کیا ہے):

بی پی ایم اسٹائل انضمام

app2 میں دوسرے کھلاڑی کا کاروباری عمل شامل ہوگا:

کلاس رینڈم پلیئر

import ru.krista.bpm.ProcessInstance
import ru.krista.bpm.runtime.ProcessImpl
import ru.krista.bpm.runtime.dsl.processModel
import ru.krista.bpm.runtime.instance.MessageSendInstance

data class PlayerInfo(val name: String, val domain: String, val id: String)

class PlayersList: ArrayList<PlayerInfo>()

class RandomPlayer : ProcessImpl<RandomPlayer>(randomPlayerModel) {

    var playerName: String by input(persistent = true, 
                                    defaultValue = "RandomPlayer")
    var energy: Int by input(persistent = true, defaultValue = 30)
    var players: PlayersList by persistent(PlayersList())
    var allPlayersOut: Boolean by persistent(false)
    var shotCounter: Int = 0

    val selfPlayer: PlayerInfo
        get() = PlayerInfo(playerName, env.eventDispatcher.domainName, id)
}

val randomPlayerModel = processModel<RandomPlayer>(name = "RandomPlayer", 
                                                   version = 1) {

    val waitNewGameSignal = signalWait<String>("NewGame")
    val waitStopGameSignal = signalWait<String>("StopGame")
    val sendPlayerJoin = signal<String>("PlayerJoin") {
        signalData = { playerName }
    }
    val sendPlayerOut = signal<String>("PlayerOut") {
        signalData = { playerName }
    }
    val waitPlayerJoin = signalWaitCustom<String>("PlayerJoin") {
        eventCondition = { signal ->
            signal.sender.processInstanceId != process.id 
                && !process.players.any { signal.sender.processInstanceId == it.id}
        }
        handler = { signal ->
            players.add(PlayerInfo(
                    signal.data!!,
                    signal.sender.domain,
                    signal.sender.processInstanceId))
        }
    }
    val waitPlayerOut = signalWait<String>("PlayerOut") { signal ->
        players.remove(PlayerInfo(
                signal.data!!,
                signal.sender.domain,
                signal.sender.processInstanceId))
        allPlayersOut = players.isEmpty()
    }
    val sendHandshake = messageSend<String>("Handshake") {
        messageData = { playerName }
        activation = {
            receiverDomain = process.players.last().domain
            receiverProcessInstanceId = process.players.last().id
        }
    }
    val receiveHandshake = messageWait<String>("Handshake") { message ->
        if (!players.any { message.sender.processInstanceId == it.id}) {
            players.add(PlayerInfo(
                    message.data!!, 
                    message.sender.domain, 
                    message.sender.processInstanceId))
        }
    }
    val throwBall = messageSend<Int>("Ball") {
        messageData = { shotCounter + 1 }
        activation = { selectNextPlayer() }
        onEntry { energy -= 1 }
    }
    val waitBall = messageWaitData<Int>("Ball") {
        shotCounter = it
    }

    startFrom(waitNewGameSignal)
            .fork("mainFork") {
                next(sendPlayerJoin)
                        .branch("mainLoop") {
                            ifTrue { energy < 5 || allPlayersOut }
                                    .next(sendPlayerOut)
                                    .next(waitBall)
                            ifElse()
                                    .next(waitBall)
                                    .next(throwBall)
                                    .loop()
                        }
                next(waitPlayerJoin).next(sendHandshake).next(waitPlayerJoin)
                next(waitPlayerOut).next(waitPlayerOut)
                next(receiveHandshake).next(receiveHandshake)
                next(waitStopGameSignal).terminate()
            }

    sendPlayerJoin.onExit { println("$playerName: I'm here!") }
    sendPlayerOut.onExit { println("$playerName: I'm out!") }
}

private fun MessageSendInstance<RandomPlayer, Int>.selectNextPlayer() {
    val player = if (process.players.isNotEmpty()) 
        process.players.random() 
    else 
        process.selfPlayer
    receiverDomain = player.domain
    receiverProcessInstanceId = player.id
    println("Step ${process.shotCounter + 1}: " +
            "${process.playerName} >>> ${player.name}")
}

خاکہ:

بی پی ایم اسٹائل انضمام

ایپ 3 ایپلی کیشن میں ہم ایک کھلاڑی کو تھوڑا مختلف طرز عمل کے ساتھ بنائیں گے: تصادفی طور پر اگلے کھلاڑی کو منتخب کرنے کے بجائے، وہ راؤنڈ رابن الگورتھم کے مطابق کام کرے گا:

کلاس راؤنڈ رابن پلیئر

import ru.krista.bpm.ProcessInstance
import ru.krista.bpm.runtime.ProcessImpl
import ru.krista.bpm.runtime.dsl.processModel
import ru.krista.bpm.runtime.instance.MessageSendInstance

data class PlayerInfo(val name: String, val domain: String, val id: String)

class PlayersList: ArrayList<PlayerInfo>()

class RoundRobinPlayer : ProcessImpl<RoundRobinPlayer>(roundRobinPlayerModel) {

    var playerName: String by input(persistent = true, 
                                    defaultValue = "RoundRobinPlayer")
    var energy: Int by input(persistent = true, defaultValue = 30)
    var players: PlayersList by persistent(PlayersList())
    var nextPlayerIndex: Int by persistent(-1)
    var allPlayersOut: Boolean by persistent(false)
    var shotCounter: Int = 0

    val selfPlayer: PlayerInfo
        get() = PlayerInfo(playerName, env.eventDispatcher.domainName, id)
}

val roundRobinPlayerModel = processModel<RoundRobinPlayer>(
        name = "RoundRobinPlayer", 
        version = 1) {

    val waitNewGameSignal = signalWait<String>("NewGame")
    val waitStopGameSignal = signalWait<String>("StopGame")
    val sendPlayerJoin = signal<String>("PlayerJoin") {
        signalData = { playerName }
    }
    val sendPlayerOut = signal<String>("PlayerOut") {
        signalData = { playerName }
    }
    val waitPlayerJoin = signalWaitCustom<String>("PlayerJoin") {
        eventCondition = { signal ->
            signal.sender.processInstanceId != process.id 
                && !process.players.any { signal.sender.processInstanceId == it.id}
        }
        handler = { signal ->
            players.add(PlayerInfo(
                    signal.data!!, 
                    signal.sender.domain, 
                    signal.sender.processInstanceId))
        }
    }
    val waitPlayerOut = signalWait<String>("PlayerOut") { signal ->
        players.remove(PlayerInfo(
                signal.data!!, 
                signal.sender.domain, 
                signal.sender.processInstanceId))
        allPlayersOut = players.isEmpty()
    }
    val sendHandshake = messageSend<String>("Handshake") {
        messageData = { playerName }
        activation = {
            receiverDomain = process.players.last().domain
            receiverProcessInstanceId = process.players.last().id
        }
    }
    val receiveHandshake = messageWait<String>("Handshake") { message ->
        if (!players.any { message.sender.processInstanceId == it.id}) {
            players.add(PlayerInfo(
                    message.data!!, 
                    message.sender.domain, 
                    message.sender.processInstanceId))
        }
    }
    val throwBall = messageSend<Int>("Ball") {
        messageData = { shotCounter + 1 }
        activation = { selectNextPlayer() }
        onEntry { energy -= 1 }
    }
    val waitBall = messageWaitData<Int>("Ball") {
        shotCounter = it
    }

    startFrom(waitNewGameSignal)
            .fork("mainFork") {
                next(sendPlayerJoin)
                        .branch("mainLoop") {
                            ifTrue { energy < 5 || allPlayersOut }
                                    .next(sendPlayerOut)
                                    .next(waitBall)
                            ifElse()
                                    .next(waitBall)
                                    .next(throwBall)
                                    .loop()
                        }
                next(waitPlayerJoin).next(sendHandshake).next(waitPlayerJoin)
                next(waitPlayerOut).next(waitPlayerOut)
                next(receiveHandshake).next(receiveHandshake)
                next(waitStopGameSignal).terminate()
            }

    sendPlayerJoin.onExit { println("$playerName: I'm here!") }
    sendPlayerOut.onExit { println("$playerName: I'm out!") }
}

private fun MessageSendInstance<RoundRobinPlayer, Int>.selectNextPlayer() {
    var idx = process.nextPlayerIndex + 1
    if (idx >= process.players.size) {
        idx = 0
    }
    process.nextPlayerIndex = idx
    val player = if (process.players.isNotEmpty()) 
        process.players[idx] 
    else 
        process.selfPlayer
    receiverDomain = player.domain
    receiverProcessInstanceId = player.id
    println("Step ${process.shotCounter + 1}: " +
            "${process.playerName} >>> ${player.name}")
}

بصورت دیگر، کھلاڑی کا رویہ پچھلے سے مختلف نہیں ہوتا، اس لیے خاکہ تبدیل نہیں ہوتا۔

اب ہمیں یہ سب چلانے کے لیے ایک ٹیسٹ کی ضرورت ہے۔ میں صرف ٹیسٹ کا کوڈ ہی دوں گا، تاکہ مضمون کو بوائلر پلیٹ کے ساتھ بے ترتیبی نہ ہو (درحقیقت، میں نے دوسرے کاروباری عملوں کے انضمام کو جانچنے کے لیے پہلے بنائے گئے ٹیسٹ ماحول کا استعمال کیا تھا):

ٹیسٹ گیم ()

@Test
public void testGame() throws InterruptedException {
    String pl2 = startProcess(app2, "RandomPlayer", playerParams("Player2", 20));
    String pl3 = startProcess(app2, "RandomPlayer", playerParams("Player3", 40));
    String pl4 = startProcess(app3, "RoundRobinPlayer", playerParams("Player4", 25));
    String pl5 = startProcess(app3, "RoundRobinPlayer", playerParams("Player5", 35));
    String pl1 = startProcess(app1, "InitialPlayer");
    // Теперь нужно немного подождать, пока игроки "познакомятся" друг с другом.
    // Ждать через sleep - плохое решение, зато самое простое. 
    // Не делайте так в серьезных тестах!
    Thread.sleep(1000);
    // Запускаем игру, закрывая пользовательскую активность
    assertTrue(closeTask(app1, pl1, "Start"));
    app1.getWaiting().waitProcessFinished(pl1);
    app2.getWaiting().waitProcessFinished(pl2);
    app2.getWaiting().waitProcessFinished(pl3);
    app3.getWaiting().waitProcessFinished(pl4);
    app3.getWaiting().waitProcessFinished(pl5);
}

private Map<String, Object> playerParams(String name, int energy) {
    Map<String, Object> params = new HashMap<>();
    params.put("playerName", name);
    params.put("energy", energy);
    return params;
}

آئیے ٹیسٹ چلائیں اور لاگ کو دیکھیں:

کنسول آؤٹ پٹ

Взята блокировка ключа lock://app1/process/InitialPlayer
Let's play!
Снята блокировка ключа lock://app1/process/InitialPlayer
Player2: I'm here!
Player3: I'm here!
Player4: I'm here!
Player5: I'm here!
... join player Player2 ...
... join player Player4 ...
... join player Player3 ...
... join player Player5 ...
Step 1: Player1 >>> Player3
Step 2: Player3 >>> Player5
Step 3: Player5 >>> Player3
Step 4: Player3 >>> Player4
Step 5: Player4 >>> Player3
Step 6: Player3 >>> Player4
Step 7: Player4 >>> Player5
Step 8: Player5 >>> Player2
Step 9: Player2 >>> Player5
Step 10: Player5 >>> Player4
Step 11: Player4 >>> Player2
Step 12: Player2 >>> Player4
Step 13: Player4 >>> Player1
Step 14: Player1 >>> Player4
Step 15: Player4 >>> Player3
Step 16: Player3 >>> Player1
Step 17: Player1 >>> Player2
Step 18: Player2 >>> Player3
Step 19: Player3 >>> Player1
Step 20: Player1 >>> Player5
Step 21: Player5 >>> Player1
Step 22: Player1 >>> Player2
Step 23: Player2 >>> Player4
Step 24: Player4 >>> Player5
Step 25: Player5 >>> Player3
Step 26: Player3 >>> Player4
Step 27: Player4 >>> Player2
Step 28: Player2 >>> Player5
Step 29: Player5 >>> Player2
Step 30: Player2 >>> Player1
Step 31: Player1 >>> Player3
Step 32: Player3 >>> Player4
Step 33: Player4 >>> Player1
Step 34: Player1 >>> Player3
Step 35: Player3 >>> Player4
Step 36: Player4 >>> Player3
Step 37: Player3 >>> Player2
Step 38: Player2 >>> Player5
Step 39: Player5 >>> Player4
Step 40: Player4 >>> Player5
Step 41: Player5 >>> Player1
Step 42: Player1 >>> Player5
Step 43: Player5 >>> Player3
Step 44: Player3 >>> Player5
Step 45: Player5 >>> Player2
Step 46: Player2 >>> Player3
Step 47: Player3 >>> Player2
Step 48: Player2 >>> Player5
Step 49: Player5 >>> Player4
Step 50: Player4 >>> Player2
Step 51: Player2 >>> Player5
Step 52: Player5 >>> Player1
Step 53: Player1 >>> Player5
Step 54: Player5 >>> Player3
Step 55: Player3 >>> Player5
Step 56: Player5 >>> Player2
Step 57: Player2 >>> Player1
Step 58: Player1 >>> Player4
Step 59: Player4 >>> Player1
Step 60: Player1 >>> Player4
Step 61: Player4 >>> Player3
Step 62: Player3 >>> Player2
Step 63: Player2 >>> Player5
Step 64: Player5 >>> Player4
Step 65: Player4 >>> Player5
Step 66: Player5 >>> Player1
Step 67: Player1 >>> Player5
Step 68: Player5 >>> Player3
Step 69: Player3 >>> Player4
Step 70: Player4 >>> Player2
Step 71: Player2 >>> Player5
Step 72: Player5 >>> Player2
Step 73: Player2 >>> Player1
Step 74: Player1 >>> Player4
Step 75: Player4 >>> Player1
Step 76: Player1 >>> Player2
Step 77: Player2 >>> Player5
Step 78: Player5 >>> Player4
Step 79: Player4 >>> Player3
Step 80: Player3 >>> Player1
Step 81: Player1 >>> Player5
Step 82: Player5 >>> Player1
Step 83: Player1 >>> Player4
Step 84: Player4 >>> Player5
Step 85: Player5 >>> Player3
Step 86: Player3 >>> Player5
Step 87: Player5 >>> Player2
Step 88: Player2 >>> Player3
Player2: I'm out!
Step 89: Player3 >>> Player4
... player Player2 is out ...
Step 90: Player4 >>> Player1
Step 91: Player1 >>> Player3
Step 92: Player3 >>> Player1
Step 93: Player1 >>> Player4
Step 94: Player4 >>> Player3
Step 95: Player3 >>> Player5
Step 96: Player5 >>> Player1
Step 97: Player1 >>> Player5
Step 98: Player5 >>> Player3
Step 99: Player3 >>> Player5
Step 100: Player5 >>> Player4
Step 101: Player4 >>> Player5
Player4: I'm out!
... player Player4 is out ...
Step 102: Player5 >>> Player1
Step 103: Player1 >>> Player3
Step 104: Player3 >>> Player1
Step 105: Player1 >>> Player3
Step 106: Player3 >>> Player5
Step 107: Player5 >>> Player3
Step 108: Player3 >>> Player1
Step 109: Player1 >>> Player3
Step 110: Player3 >>> Player5
Step 111: Player5 >>> Player1
Step 112: Player1 >>> Player3
Step 113: Player3 >>> Player5
Step 114: Player5 >>> Player3
Step 115: Player3 >>> Player1
Step 116: Player1 >>> Player3
Step 117: Player3 >>> Player5
Step 118: Player5 >>> Player1
Step 119: Player1 >>> Player3
Step 120: Player3 >>> Player5
Step 121: Player5 >>> Player3
Player5: I'm out!
... player Player5 is out ...
Step 122: Player3 >>> Player5
Step 123: Player5 >>> Player1
Player5: I'm out!
Step 124: Player1 >>> Player3
... player Player5 is out ...
Step 125: Player3 >>> Player1
Step 126: Player1 >>> Player3
Player1: I'm out!
... player Player1 is out ...
Step 127: Player3 >>> Player3
Player3: I'm out!
Step 128: Player3 >>> Player3
... player Player3 is out ...
Player3: I'm out!
Stop!
Step 129: Player3 >>> Player3
Player3: I'm out!

اس سب سے ہم کئی اہم نتائج اخذ کر سکتے ہیں:

  • ضروری ٹولز کے ساتھ، ایپلیکیشن ڈویلپرز کاروباری منطق میں خلل ڈالے بغیر ایپلی کیشنز کے درمیان انضمام کی بات چیت کر سکتے ہیں۔
  • انضمام کے کام کی پیچیدگی جس کے لیے انجینئرنگ کی صلاحیتوں کی ضرورت ہوتی ہے اگر اسے ابتدائی طور پر فریم ورک کے فن تعمیر میں شامل کیا جائے تو اسے فریم ورک کے اندر چھپایا جا سکتا ہے۔ کسی مسئلے کی مشکل کو چھپایا نہیں جا سکتا، اس لیے کوڈ میں کسی مشکل مسئلے کا حل اس کی طرح نظر آئے گا۔
  • انضمام کی منطق کو تیار کرتے وقت، انضمام کے تمام شرکاء کی حالت میں تبدیلیوں کی حتمی مستقل مزاجی اور لکیری قابلیت کی کمی کو مدنظر رکھنا ضروری ہے۔ یہ ہمیں منطق کو پیچیدہ بنانے پر مجبور کرتا ہے تاکہ اسے اس ترتیب سے غیر حساس بنایا جا سکے جس میں بیرونی واقعات رونما ہوتے ہیں۔ ہماری مثال میں، کھلاڑی کو گیم سے باہر ہونے کا اعلان کرنے کے بعد گیم میں حصہ لینے پر مجبور کیا جاتا ہے: دوسرے کھلاڑی اس وقت تک گیند کو اس کے پاس بھیجتے رہیں گے جب تک کہ اس کے باہر نکلنے کی اطلاع نہیں پہنچ جاتی اور تمام شرکاء کی طرف سے اس پر کارروائی نہیں کی جاتی۔ یہ منطق کھیل کے اصولوں کی پیروی نہیں کرتی ہے اور یہ منتخب کردہ فن تعمیر کے فریم ورک کے اندر سمجھوتہ کرنے والا حل ہے۔

اگلا، ہم اپنے حل کی مختلف پیچیدگیوں، سمجھوتوں اور دیگر نکات کے بارے میں بات کریں گے۔

تمام پیغامات ایک قطار میں ہیں۔

تمام مربوط ایپلی کیشنز ایک انٹیگریشن بس کے ساتھ کام کرتی ہیں، جو ایک بیرونی بروکر، پیغامات کے لیے ایک BPMQueue اور سگنلز (واقعات) کے لیے ایک BPMTopic موضوع کی شکل میں پیش کی جاتی ہے۔ تمام پیغامات کو ایک قطار میں ڈالنا بذات خود ایک سمجھوتہ ہے۔ کاروباری منطق کی سطح پر، اب آپ سسٹم کے ڈھانچے میں تبدیلی کیے بغیر اپنی پسند کے مطابق پیغامات کی بہت سی نئی اقسام متعارف کروا سکتے ہیں۔ یہ ایک اہم سادگی ہے، لیکن اس میں کچھ خطرات ہیں، جو ہمارے عام کاموں کے تناظر میں ہمارے لیے اتنے اہم نہیں لگتے تھے۔

بی پی ایم اسٹائل انضمام

تاہم، یہاں ایک باریکی ہے: ہر ایپلیکیشن اپنے ڈومین کے نام سے داخلی دروازے پر قطار سے "اپنے" پیغامات کو فلٹر کرتی ہے۔ اگر آپ کو سگنل کی "مرئیت کے دائرہ کار" کو ایک ہی ایپلیکیشن تک محدود کرنے کی ضرورت ہو تو ڈومین کو سگنلز میں بھی بیان کیا جا سکتا ہے۔ اس سے بس تھرو پٹ میں اضافہ ہونا چاہیے، لیکن کاروباری منطق کو اب ڈومین ناموں کے ساتھ کام کرنا چاہیے: پیغامات کو ایڈریس کرنے کے لیے - لازمی، سگنلز کے لیے - مطلوبہ۔

انٹیگریشن بس کی وشوسنییتا کو یقینی بنانا

وشوسنییتا کئی نکات پر مشتمل ہے:

  • منتخب کردہ میسج بروکر فن تعمیر کا ایک اہم جزو اور ناکامی کا ایک نقطہ ہے: اسے کافی حد تک غلطی برداشت کرنا چاہیے۔ آپ کو اچھی حمایت اور ایک بڑی کمیونٹی کے ساتھ، صرف وقت کے مطابق تجربہ شدہ نفاذات کا استعمال کرنا چاہیے۔
  • میسج بروکر کی اعلیٰ دستیابی کو یقینی بنانا ضروری ہے، جس کے لیے اسے مربوط ایپلی کیشنز سے جسمانی طور پر الگ کیا جانا چاہیے (اپلائیڈ کاروباری منطق کے ساتھ ایپلی کیشنز کی زیادہ دستیابی کو یقینی بنانا زیادہ مشکل اور مہنگا ہے)؛
  • بروکر "کم از کم ایک بار" ترسیل کی ضمانت فراہم کرنے کا پابند ہے۔ انضمام بس کے قابل اعتماد آپریشن کے لیے یہ ایک لازمی شرط ہے۔ "بالکل ایک بار" سطح کی ضمانتوں کی ضرورت نہیں ہے: کاروباری عمل، ایک اصول کے طور پر، پیغامات یا واقعات کی بار بار آمد کے لیے حساس نہیں ہوتے ہیں، اور خاص کاموں میں جہاں یہ ضروری ہے، کاروبار میں اضافی چیک شامل کرنا آسان ہوتا ہے۔ مسلسل کافی "مہنگی" " ضمانتیں استعمال کرنے کے بجائے منطق؛
  • پیغامات اور سگنل بھیجنا کاروباری عمل اور ڈومین ڈیٹا کی حالت میں ہونے والی تبدیلیوں کے ساتھ مجموعی لین دین میں شامل ہونا چاہیے۔ ترجیحی آپشن پیٹرن استعمال کرنا ہوگا۔ ٹرانزیکشنل آؤٹ باکس، لیکن اس کے لیے ڈیٹا بیس میں ایک اضافی ٹیبل اور ایک ریپیٹر کی ضرورت ہوگی۔ JEE ایپلی کیشنز میں، اسے مقامی JTA مینیجر کا استعمال کرتے ہوئے آسان بنایا جا سکتا ہے، لیکن منتخب بروکر سے کنکشن کا کام کرنے کے قابل ہونا ضروری ہے۔ XA;
  • آنے والے پیغامات اور واقعات کے ہینڈلرز کو ایک ایسے لین دین کے ساتھ بھی کام کرنا چاہیے جو کاروباری عمل کی حالت کو بدل دے: اگر اس طرح کے لین دین کو روک دیا جاتا ہے، تو پیغام کی وصولی کو منسوخ کر دیا جانا چاہیے۔
  • وہ پیغامات جو غلطیوں کی وجہ سے ڈیلیور نہیں کیے جا سکے انہیں علیحدہ اسٹوریج میں محفوظ کیا جانا چاہیے۔ ڈی ایل کیو (ڈیڈ لیٹر کیو) اس مقصد کے لیے، ہم نے ایک علیحدہ پلیٹ فارم مائیکرو سرویس بنایا ہے جو اس طرح کے پیغامات کو اپنے اسٹوریج میں محفوظ کرتا ہے، ان کی صفات (فوری گروپ بندی اور تلاش کے لیے) کے لحاظ سے انڈیکس کرتا ہے، اور پیغامات کو دیکھنے، منزل کے پتے پر دوبارہ بھیجنے اور حذف کرنے کے لیے API کو ظاہر کرتا ہے۔ سسٹم ایڈمنسٹریٹر اپنے ویب انٹرفیس کے ذریعے اس سروس کے ساتھ کام کر سکتے ہیں۔
  • بروکر کی ترتیبات میں، آپ کو ڈی ایل کیو میں پیغامات کے آنے کے امکانات کو کم کرنے کے لیے ڈیلیوری کی دوبارہ کوششوں اور ڈیلیوری کے درمیان تاخیر کی تعداد کو ایڈجسٹ کرنے کی ضرورت ہے (زیادہ سے زیادہ پیرامیٹرز کا حساب لگانا تقریباً ناممکن ہے، لیکن آپ تجرباتی طور پر کام کر سکتے ہیں اور آپریشن کے دوران انہیں ایڈجسٹ کر سکتے ہیں۔ );
  • DLQ اسٹور کی مسلسل نگرانی کی جانی چاہیے، اور نگرانی کے نظام کو سسٹم کے منتظمین کو خبردار کرنا چاہیے تاکہ جب غیر ڈیلیور شدہ پیغامات ہوں، تو وہ جلد از جلد جواب دے سکیں۔ یہ ناکامی یا کاروباری منطق کی غلطی کے "متاثرہ علاقے" کو کم کر دے گا۔
  • انٹیگریشن بس کو ایپلی کیشنز کی عارضی غیر موجودگی کے لیے غیر حساس ہونا چاہیے: کسی موضوع کی سبسکرپشنز پائیدار ہونی چاہئیں، اور ایپلیکیشن کا ڈومین نام منفرد ہونا چاہیے تاکہ جب ایپلی کیشن موجود نہ ہو، کوئی اور اس کے پیغامات پر کارروائی کرنے کی کوشش نہ کرے۔ قطار.

کاروباری منطق کے دھاگے کی حفاظت کو یقینی بنانا

کاروباری عمل کی ایک ہی مثال میں ایک ساتھ کئی پیغامات اور واقعات موصول ہو سکتے ہیں، جن کی کارروائی متوازی طور پر شروع ہو گی۔ ایک ہی وقت میں، ایک ایپلیکیشن ڈویلپر کے لیے، سب کچھ سادہ اور تھریڈ سے محفوظ ہونا چاہیے۔

کسی عمل کی کاروباری منطق ہر ایک بیرونی واقعہ پر کارروائی کرتی ہے جو اس کاروباری عمل کو انفرادی طور پر متاثر کرتی ہے۔ ایسے واقعات ہو سکتے ہیں:

  • کاروباری عمل کی مثال شروع کرنا؛
  • کاروباری عمل کے اندر سرگرمی سے متعلق صارف کی کارروائی؛
  • ایک پیغام یا سگنل کی رسید جس پر کاروباری عمل کی مثال سبسکرائب کی گئی ہے؛
  • کاروباری عمل کی مثال کے ذریعہ مقرر کردہ ٹائمر کو متحرک کرنا؛
  • API کے ذریعے کارروائی کو کنٹرول کریں (مثال کے طور پر عمل میں رکاوٹ)۔

اس طرح کا ہر واقعہ کاروباری عمل کی حالت کو بدل سکتا ہے: کچھ سرگرمیاں ختم ہو سکتی ہیں اور دوسری شروع ہو سکتی ہیں، اور مستقل خصوصیات کی قدریں بدل سکتی ہیں۔ کسی بھی سرگرمی کو بند کرنے کے نتیجے میں درج ذیل میں سے ایک یا زیادہ سرگرمیاں فعال ہو سکتی ہیں۔ وہ، بدلے میں، دوسرے واقعات کا انتظار کرنا چھوڑ سکتے ہیں یا، اگر انہیں کسی اضافی ڈیٹا کی ضرورت نہیں ہے، تو وہ اسی لین دین میں مکمل کر سکتے ہیں۔ لین دین کو بند کرنے سے پہلے، کاروباری عمل کی نئی حالت کو ڈیٹا بیس میں محفوظ کیا جاتا ہے، جہاں یہ اگلے بیرونی واقعے کا انتظار کرے گا۔

اگر آپ SELECT FOR UPDATE استعمال کرتے ہیں تو متعلقہ ڈیٹا بیس میں محفوظ کاروباری عمل کا مستقل ڈیٹا پروسیسنگ کو ہم آہنگ کرنے کے لیے ایک بہت ہی آسان نقطہ ہے۔ اگر ایک لین دین اس کو تبدیل کرنے کے لیے بنیاد سے کاروباری عمل کی حالت حاصل کرنے میں کامیاب ہو گیا، تو متوازی طور پر کوئی دوسرا لین دین دوسری تبدیلی کے لیے وہی حالت حاصل نہیں کر سکے گا، اور پہلی لین دین کی تکمیل کے بعد، دوسرا پہلے سے تبدیل شدہ حالت حاصل کرنے کی ضمانت۔

DBMS کی طرف مایوسی کے تالے کا استعمال کرتے ہوئے، ہم تمام ضروری تقاضوں کو پورا کرتے ہیں۔ ایسڈ، اور چلنے والی مثالوں کی تعداد میں اضافہ کرکے کاروباری منطق کے ساتھ ایپلیکیشن کی پیمائش کرنے کی صلاحیت کو بھی برقرار رکھیں۔

تاہم، مایوسی کے تالے ہمیں تعطل کا خطرہ دیتے ہیں، جس کا مطلب ہے کہ SELECT FOR UPDATE کو اب بھی کچھ معقول ٹائم آؤٹ تک محدود ہونا چاہیے اگر کاروباری منطق میں کچھ سنگین معاملات پر تعطل پیدا ہو جائے۔

ایک اور مسئلہ کاروباری عمل کے آغاز کی ہم آہنگی ہے۔ اگرچہ کاروباری عمل کی کوئی مثال نہیں ہے، ڈیٹا بیس میں کوئی حالت نہیں ہے، لہذا بیان کردہ طریقہ کام نہیں کرے گا۔ اگر آپ کو ایک مخصوص دائرہ کار میں کاروباری عمل مثال کی انفرادیت کو یقینی بنانے کی ضرورت ہے، تو آپ کو عمل کی کلاس اور متعلقہ دائرہ کار سے وابستہ کسی قسم کی مطابقت پذیری آبجیکٹ کی ضرورت ہوگی۔ اس مسئلے کو حل کرنے کے لیے، ہم ایک مختلف تالا لگانے کا طریقہ کار استعمال کرتے ہیں جو ہمیں کسی بیرونی سروس کے ذریعے URI فارمیٹ میں ایک کلید کے ذریعے مخصوص کردہ صوابدیدی وسائل پر لاک لینے کی اجازت دیتا ہے۔

ہماری مثالوں میں، InitialPlayer کاروباری عمل ایک اعلان پر مشتمل ہے۔

uniqueConstraint = UniqueConstraints.singleton

لہذا، لاگ میں متعلقہ کلید کے لاک کو لینے اور جاری کرنے کے بارے میں پیغامات شامل ہیں۔ دیگر کاروباری عملوں کے لیے ایسے کوئی پیغامات نہیں ہیں: unique Constraint سیٹ نہیں ہے۔

مستقل حالت کے ساتھ کاروباری عمل کے مسائل

بعض اوقات مستقل حالت میں رہنا نہ صرف مدد کرتا ہے بلکہ واقعی ترقی کی راہ میں رکاوٹ بھی بنتا ہے۔
مسائل اس وقت شروع ہوتے ہیں جب کاروباری منطق اور/یا کاروباری عمل کے ماڈل میں تبدیلیاں کرنے کی ضرورت ہوتی ہے۔ ایسی ہر تبدیلی کاروباری عمل کی پرانی حالت سے مطابقت نہیں رکھتی۔ اگر ڈیٹا بیس میں بہت سی لائیو مثالیں موجود ہیں، تو غیر مطابقت پذیر تبدیلیاں کرنے سے کافی پریشانی ہو سکتی ہے، جس کا ہمیں اکثر jBPM استعمال کرتے وقت سامنا کرنا پڑتا ہے۔

تبدیلیوں کی گہرائی پر منحصر ہے، آپ دو طریقوں سے کام کر سکتے ہیں:

  1. کاروباری عمل کی ایک نئی قسم بنائیں تاکہ پرانی میں غیر موافق تبدیلیاں نہ کی جائیں، اور نئی مثالیں شروع کرتے وقت پرانی کی بجائے اسے استعمال کریں۔ پرانی کاپیاں "پہلے کی طرح" کام کرتی رہیں گی۔
  2. کاروباری منطق کو اپ ڈیٹ کرتے وقت کاروباری عمل کی مستقل حالت کو منتقل کریں۔

پہلا طریقہ آسان ہے، لیکن اس کی حدود اور نقصانات ہیں، مثال کے طور پر:

  • بہت سے کاروباری عمل کے ماڈلز میں کاروباری منطق کی نقل، کاروباری منطق کے حجم میں اضافہ؛
  • اکثر نئی کاروباری منطق میں فوری منتقلی کی ضرورت ہوتی ہے (انضمام کے کاموں کے لحاظ سے - تقریبا ہمیشہ)؛
  • ڈویلپر کو نہیں معلوم کہ پرانے ماڈلز کو کس مقام پر حذف کیا جا سکتا ہے۔

عملی طور پر ہم دونوں طریقوں کا استعمال کرتے ہیں، لیکن اپنی زندگیوں کو آسان بنانے کے لیے متعدد فیصلے کیے ہیں:

  • ڈیٹا بیس میں، کاروباری عمل کی مستقل حالت کو آسانی سے پڑھنے کے قابل اور آسانی سے پروسیس شدہ شکل میں محفوظ کیا جاتا ہے: JSON فارمیٹ سٹرنگ میں۔ یہ ہجرت کو ایپلیکیشن کے اندر اور باہر دونوں طرح سے انجام دینے کی اجازت دیتا ہے۔ آخری حربے کے طور پر، آپ اسے دستی طور پر درست کر سکتے ہیں (خاص طور پر ڈیبگنگ کے دوران ترقی میں مفید)؛
  • انضمام کاروباری منطق کاروباری عمل کے ناموں کا استعمال نہیں کرتی ہے، تاکہ کسی بھی وقت حصہ لینے والے عمل میں سے کسی ایک کے نفاذ کو ایک نئے نام سے تبدیل کرنا ممکن ہو (مثال کے طور پر، "InitialPlayerV2")۔ بائنڈنگ پیغام اور سگنل کے ناموں کے ذریعے ہوتی ہے۔
  • پروسیس ماڈل کا ایک ورژن نمبر ہوتا ہے، جسے ہم بڑھاتے ہیں اگر ہم اس ماڈل میں غیر موافق تبدیلیاں کرتے ہیں، اور یہ نمبر عمل کی مثال کے ساتھ محفوظ ہو جاتا ہے۔
  • عمل کی مستقل حالت کو پہلے ڈیٹا بیس سے ایک آسان آبجیکٹ ماڈل میں پڑھا جاتا ہے، جس کے ساتھ ماڈل ورژن نمبر تبدیل ہونے کی صورت میں منتقلی کا طریقہ کار کام کر سکتا ہے۔
  • منتقلی کے طریقہ کار کو کاروباری منطق کے ساتھ رکھا جاتا ہے اور ڈیٹا بیس سے بحالی کے وقت کاروباری عمل کی ہر مثال کے لیے اسے "سست" کہا جاتا ہے۔
  • اگر آپ کو تمام پروسیس انسٹینسز کی حالت کو تیزی سے اور ہم وقت سازی سے منتقل کرنے کی ضرورت ہے، تو مزید کلاسک ڈیٹا بیس کی منتقلی کے حل استعمال کیے جاتے ہیں، لیکن آپ کو JSON کے ساتھ کام کرنا ہوگا۔

کیا آپ کو کاروباری عمل کے لیے کسی اور فریم ورک کی ضرورت ہے؟

مضمون میں بیان کردہ حلوں نے ہمیں اپنی زندگی کو نمایاں طور پر آسان بنانے، ایپلیکیشن ڈویلپمنٹ کی سطح پر حل کیے گئے مسائل کی حد کو بڑھانے، اور کاروباری منطق کو مائیکرو سروسز میں الگ کرنے کے خیال کو مزید پرکشش بنانے کی اجازت دی۔ اس کو حاصل کرنے کے لیے، بہت کام کیا گیا، کاروباری عمل کے لیے ایک بہت ہی "ہلکا پھلکا" فریم ورک بنایا گیا، اور ساتھ ہی ایپلی کیشن کے مسائل کی ایک وسیع رینج کے تناظر میں شناخت شدہ مسائل کو حل کرنے کے لیے سروس کے اجزاء۔ ہماری خواہش ہے کہ ہم ان نتائج کا اشتراک کریں اور مشترکہ اجزاء کی ترقی کو مفت لائسنس کے تحت کھلی رسائی فراہم کریں۔ اس کے لیے کچھ محنت اور وقت درکار ہوگا۔ اس طرح کے حل کی مانگ کو سمجھنا ہمارے لیے ایک اضافی ترغیب ہو سکتا ہے۔ مجوزہ مضمون میں، خود فریم ورک کی صلاحیتوں پر بہت کم توجہ دی گئی ہے، لیکن ان میں سے کچھ پیش کردہ مثالوں سے نظر آتی ہیں۔ اگر ہم اپنا فریم ورک شائع کرتے ہیں تو اس کے لیے ایک الگ مضمون وقف کیا جائے گا۔ اس دوران، اگر آپ سوال کا جواب دے کر تھوڑی سی رائے دیں تو ہم مشکور ہوں گے:

سروے میں صرف رجسٹرڈ صارفین ہی حصہ لے سکتے ہیں۔ سائن ان، برائے مہربانی.

کیا آپ کو کاروباری عمل کے لیے کسی اور فریم ورک کی ضرورت ہے؟

  • 18,8٪ہاں، میں کافی عرصے سے اس طرح کی چیز تلاش کر رہا ہوں۔

  • 12,5٪میں آپ کے نفاذ کے بارے میں مزید جاننے میں دلچسپی رکھتا ہوں، یہ مفید ہو سکتا ہے2

  • 6,2٪ہم موجودہ فریم ورک میں سے ایک کا استعمال کرتے ہیں، لیکن 1 کو تبدیل کرنے کے بارے میں سوچ رہے ہیں۔

  • 18,8٪ہم موجودہ فریم ورک میں سے ایک استعمال کرتے ہیں، سب کچھ ٹھیک ہے۔

  • 18,8٪ہم فریم ورک کے بغیر انتظام کرتے ہیں۔

  • 25,0٪اپنا لکھیں 4

16 صارفین نے ووٹ دیا۔ 7 صارفین غیر حاضر رہے۔

ماخذ: www.habr.com

نیا تبصرہ شامل کریں