شرکت ما در توسعه راه حل های نرم افزاری کلاس ERP تخصص دارد، که در آن سهم شیر توسط سیستم های تراکنش با حجم عظیمی از منطق تجاری و گردش کار a la EDMS اشغال شده است. نسخههای مدرن محصولات ما مبتنی بر فناوریهای JavaEE هستند، اما ما نیز به طور فعال در حال آزمایش میکروسرویسها هستیم. یکی از مشکلسازترین حوزههای چنین راهحلهایی، ادغام زیرسیستمهای مختلف مرتبط با حوزههای مجاور است. وظایف یکپارچه سازی همیشه بدون توجه به سبک های معماری، پشته های فناوری و چارچوب هایی که استفاده می کنیم، دردسر بزرگی برای ما ایجاد کرده است، اما اخیراً پیشرفت هایی در حل چنین مشکلاتی حاصل شده است.
در مقاله ای که مورد توجه شما قرار گرفت، در مورد تجربه و تحقیقات معماری NPO Krista در منطقه تعیین شده صحبت خواهم کرد. ما همچنین نمونه ای از یک راه حل ساده برای یک مشکل یکپارچه سازی را از دیدگاه یک توسعه دهنده برنامه در نظر خواهیم گرفت و خواهیم فهمید که در پس این سادگی چه چیزی پنهان شده است.
سلب مسئولیت
راه حل های معماری و فنی شرح داده شده در مقاله توسط من بر اساس تجربه شخصی در زمینه وظایف خاص ارائه شده است. این راه حل ها ادعای جهانی بودن ندارند و ممکن است تحت شرایط استفاده دیگر بهینه نباشند.
BPM چه ربطی به آن دارد؟
برای پاسخ به این سوال، باید کمی به جزئیات مشکلات کاربردی راه حل های خود بپردازیم. بخش اصلی منطق تجاری در سیستم تراکنشی معمولی ما، وارد کردن داده ها به پایگاه داده از طریق رابط های کاربر، بررسی دستی و خودکار این داده ها، عبور آن از طریق برخی گردش کار، انتشار آن به سیستم دیگر / پایگاه داده تحلیلی / بایگانی، و تولید گزارش است. بنابراین، عملکرد کلیدی سیستم برای مشتریان، اتوماسیون فرآیندهای تجاری داخلی آنهاست.
برای راحتی، ما از اصطلاح "سند" در ارتباطات به عنوان انتزاعی از یک مجموعه داده استفاده می کنیم، که توسط یک کلید مشترک متحد شده است، که یک گردش کار خاص را می توان "ضمیمه" کرد.
اما در مورد منطق یکپارچه سازی چطور؟ از این گذشته ، وظیفه ادغام توسط معماری سیستم ایجاد می شود ، که نه به درخواست مشتری بلکه تحت تأثیر عوامل کاملاً متفاوت به قطعات "اره" می شود:
تحت تأثیر قانون کانوی؛
در نتیجه استفاده مجدد از زیرسیستم هایی که قبلاً برای محصولات دیگر توسعه یافته بودند.
همانطور که معمار بر اساس الزامات غیر عملکردی تصمیم گرفته است.
وسوسه بزرگی برای جدا کردن منطق یکپارچه سازی از منطق تجاری گردش کار اصلی وجود دارد تا منطق تجاری را با مصنوعات یکپارچه سازی آلوده نکنیم و توسعه دهنده برنامه را از کاوش در ویژگی های چشم انداز معماری سیستم نجات دهیم. این رویکرد دارای چندین مزیت است، اما تمرین ناکارآمدی آن را نشان می دهد:
حل مشکلات ادغام معمولاً به سادهترین گزینهها در قالب تماسهای همزمان به دلیل محدودیت نقاط گسترش در اجرای گردش کار اصلی کاهش مییابد (در مورد کاستیهای یکپارچهسازی همزمان در زیر)
در صورت نیاز به بازخورد از سیستم فرعی دیگر، مصنوعات یکپارچه سازی همچنان در منطق اصلی تجارت نفوذ می کنند.
توسعهدهنده برنامه یکپارچهسازی را نادیده میگیرد و به راحتی میتواند با تغییر گردش کار آن را بشکند.
سیستم از نظر کاربر یک کل واحد نیست، "درز" بین زیرسیستم ها قابل توجه می شود، عملیات اضافی کاربر ظاهر می شود که انتقال داده ها را از یک زیر سیستم به زیرسیستم دیگر آغاز می کند.
رویکرد دیگر این است که تعاملات یکپارچه سازی را به عنوان بخشی جدایی ناپذیر از منطق اصلی تجارت و گردش کار در نظر بگیریم. برای جلوگیری از افزایش سرسام آور نیازهای مهارتی توسعه دهندگان برنامه، ایجاد تعاملات یکپارچه سازی جدید باید به راحتی و به طور طبیعی و با حداقل گزینه ها برای انتخاب راه حل انجام شود. این دشوارتر از آن چیزی است که به نظر می رسد: ابزار باید به اندازه کافی قدرتمند باشد تا گزینه های لازم را برای استفاده از آن در اختیار کاربر قرار دهد و در عین حال به خود اجازه اصابت گلوله به پا را ندهد. سوالات زیادی وجود دارد که یک مهندس در زمینه وظایف یکپارچه سازی باید به آنها پاسخ دهد، اما یک توسعه دهنده برنامه نباید در کار روزانه خود به آنها فکر کند: مرزهای تراکنش، ثبات، اتمی، امنیت، مقیاس بندی، توزیع بار و منابع، مسیریابی، مارشال کردن، انتشار و تغییر زمینه ها، و غیره. لازم است که به توسعه دهندگان برنامه های کاربردی الگوهای تصمیم گیری نسبتاً ساده ای ارائه شود، که در آن پاسخ به همه این سؤالات از قبل پنهان شده باشد. این الگوها باید به اندازه کافی ایمن باشند: منطق تجاری اغلب تغییر می کند، که خطر ایجاد خطا را افزایش می دهد، هزینه خطاها باید در سطح نسبتاً پایینی باقی بماند.
اما با این حال، BPM چه ربطی به آن دارد؟ گزینه های زیادی برای پیاده سازی گردش کار وجود دارد ...
در واقع، اجرای دیگری از فرآیندهای کسب و کار در راه حل های ما بسیار محبوب است - از طریق تنظیم اعلامی نمودار انتقال حالت و اتصال کنترل کننده ها با منطق تجاری به انتقال. در عین حال، حالتی که موقعیت فعلی "سند" را در فرآیند تجاری تعیین می کند، ویژگی خود "سند" است.
این روند در شروع پروژه به این صورت است
محبوبیت چنین پیاده سازی به دلیل سادگی و سرعت نسبی ایجاد فرآیندهای تجاری خطی است. با این حال، با پیچیدهتر شدن سیستمهای نرمافزاری، بخش خودکار فرآیند کسبوکار رشد میکند و پیچیدهتر میشود. نیاز به تجزیه، استفاده مجدد از بخش هایی از فرآیندها و همچنین فرآیندهای انشعاب وجود دارد تا هر شاخه به صورت موازی اجرا شود. در چنین شرایطی، ابزار ناخوشایند می شود و نمودار انتقال وضعیت محتوای اطلاعاتی خود را از دست می دهد (تعاملات یکپارچه سازی به هیچ وجه در نمودار منعکس نمی شود).
این همان چیزی است که این فرآیند پس از چندین بار شفاف سازی الزامات به نظر می رسد
راه برون رفت از این وضعیت یکپارچه سازی موتور بود jBPM به برخی از محصولات با پیچیده ترین فرآیندهای تجاری. در کوتاهمدت، این راهحل موفقیتهایی داشت: پیادهسازی فرآیندهای تجاری پیچیده با حفظ یک نمودار نسبتاً آموزنده و بهروز در نماد ممکن شد. BPMN2.
بخش کوچکی از یک فرآیند پیچیده تجاری
در درازمدت، راهحل انتظارات را برآورده نکرد: شدت کار بالا در ایجاد فرآیندهای تجاری از طریق ابزارهای بصری امکان دستیابی به شاخصهای بهرهوری قابل قبول را فراهم نمیکرد و خود این ابزار به یکی از محبوبترین ابزارها در میان توسعهدهندگان تبدیل شد. همچنین شکایاتی در مورد ساختار داخلی موتور وجود داشت که منجر به ظهور بسیاری از "لکه ها" و "عصاها" شد.
جنبه مثبت اصلی استفاده از jBPM، درک مزایا و مضرات داشتن حالت پایدار خود برای یک نمونه فرآیند تجاری بود. ما همچنین امکان استفاده از یک رویکرد فرآیندی را برای پیاده سازی پروتکل های یکپارچه سازی پیچیده بین برنامه های مختلف با استفاده از تعاملات ناهمزمان از طریق سیگنال ها و پیام ها مشاهده کردیم. حضور یک دولت پایدار در این امر نقش اساسی دارد.
با توجه به مطالب فوق می توان نتیجه گرفت: رویکرد فرآیند در سبک BPM به ما اجازه میدهد تا طیف گستردهای از وظایف را برای خودکارسازی فرآیندهای تجاری پیچیدهتر حل کنیم، فعالیتهای یکپارچهسازی را به طور هماهنگ در این فرآیندها قرار دهیم و توانایی نمایش بصری فرآیند اجرا شده را در یک نماد مناسب حفظ کنیم.
معایب تماس های همزمان به عنوان یک الگوی یکپارچه سازی
ادغام همزمان به ساده ترین تماس مسدود کننده اشاره دارد. یک زیر سیستم به عنوان سمت سرور عمل می کند و API را با روش مورد نظر در معرض نمایش قرار می دهد. زیرسیستم دیگری به عنوان سمت مشتری عمل می کند و در زمان مناسب با انتظار نتیجه تماس برقرار می کند. بسته به معماری سیستم، سمت سرویس گیرنده و سرور می تواند در یک برنامه و فرآیند یکسان یا در برنامه های مختلف میزبانی شود. در حالت دوم، باید مقداری پیاده سازی RPC را اعمال کنید و پارامترها و نتیجه فراخوانی را مارشال کنید.
چنین الگوی ادغامی دارای مجموعه نسبتاً بزرگی از اشکالات است، اما به دلیل سادگی در عمل بسیار مورد استفاده قرار می گیرد. سرعت اجرا را مجذوب خود می کند و باعث می شود که در شرایط "سوزاندن" ضرب الاجل، بارها و بارها آن را اعمال کنید و راه حل را در بدهی فنی بنویسید. اما همچنین اتفاق می افتد که توسعه دهندگان بی تجربه به طور ناخودآگاه از آن استفاده می کنند و به سادگی متوجه عواقب منفی آن نیستند.
علاوه بر آشکارترین افزایش در اتصال زیرسیستم ها، مشکلات کمتر آشکاری در مورد "گسترش" و "کشش" تراکنش ها وجود دارد. در واقع، اگر منطق کسبوکار تغییراتی ایجاد کند، تراکنشها ضروری هستند و تراکنشها به نوبه خود، منابع برنامه خاصی را که تحت تأثیر این تغییرات قرار گرفتهاند قفل میکنند. به این معنا که تا زمانی که یک زیرسیستم منتظر پاسخ از سوی زیرسیستم دیگری نباشد، نمیتواند تراکنش را کامل کند و قفلها را آزاد کند. این به طور قابل توجهی خطر ابتلا به انواع اثرات را افزایش می دهد:
پاسخگویی سیستم از بین می رود، کاربران مدت زیادی منتظر پاسخ به درخواست ها هستند.
معمولاً سرور به دلیل وجود یک مخزن موضوعی سرریز از پاسخگویی به درخواستهای کاربر متوقف میشود: بیشتر رشتهها روی قفل منبع اشغال شده توسط تراکنش قرار دارند.
بن بست ها ظاهر می شوند: احتمال وقوع آنها به شدت به مدت تراکنش ها، میزان منطق تجاری و قفل های موجود در معامله بستگی دارد.
خطاهای انقضای مهلت معامله ظاهر می شوند.
سرور بر روی OutOfMemory می افتد اگر کار به پردازش و تغییر مقادیر زیادی داده نیاز داشته باشد و وجود ادغام های همزمان تقسیم پردازش به تراکنش های "سبک تر" را بسیار دشوار می کند.
از نقطه نظر معماری، استفاده از مسدود کردن تماس ها در طول یکپارچه سازی منجر به از دست دادن کنترل کیفیت زیرسیستم های فردی می شود: دستیابی به اهداف کیفیت برای یک زیرسیستم جدا از شاخص های کیفیت برای زیرسیستم دیگر غیرممکن است. اگر زیرسیستم ها توسط تیم های مختلف توسعه داده شوند، این یک مشکل بزرگ است.
اگر زیرسیستم های در حال ادغام در برنامه های مختلف باشند و نیاز به تغییرات همزمان در هر دو طرف باشد، همه چیز جالب تر می شود. چگونه می توان این تغییرات را تراکنشی کرد؟
اگر تغییراتی در تراکنشهای جداگانه انجام شود، باید مدیریت استثنایی و جبران خسارت قوی ارائه شود و این مزیت اصلی ادغامهای همزمان - سادگی را کاملاً از بین میبرد.
تراکنش های توزیع شده نیز به ذهن می رسند، اما ما از آنها در راه حل های خود استفاده نمی کنیم: اطمینان از قابلیت اطمینان دشوار است.
"حماسه" به عنوان راه حلی برای مشکل معاملات
با افزایش محبوبیت میکروسرویس ها، تقاضای فزاینده ای برای آنها وجود دارد الگوی حماسه.
این الگو به خوبی مشکلات فوق در مورد تراکنش های طولانی را حل می کند و همچنین امکانات مدیریت وضعیت سیستم را از ناحیه منطق تجاری گسترش می دهد: جبران خسارت پس از یک تراکنش ناموفق ممکن است سیستم را به حالت اولیه خود برنگرداند، اما یک جایگزین ارائه می دهد. مسیر پردازش داده ها همچنین به شما این امکان را میدهد که مراحل پردازش دادهها را که با موفقیت انجام شدهاند، تکرار نکنید، زمانی که میخواهید فرآیند را به پایان «خوب» برسانید.
جالب اینجاست که در سیستمهای یکپارچه، این الگو در مورد ادغام زیرسیستمهای جفت شده آزاد نیز مرتبط است و اثرات منفی ناشی از تراکنشهای طولانی و قفلهای منابع مربوطه وجود دارد.
با توجه به فرآیندهای کسب و کار ما در سبک BPM، به نظر می رسد که اجرای Sagas بسیار آسان است: مراحل فردی Sagas را می توان به عنوان فعالیت هایی در فرآیند کسب و کار تنظیم کرد، و وضعیت پایدار فرآیند کسب و کار را تعیین می کند. چیزهای دیگر، وضعیت داخلی حماسه. یعنی ما به هیچ مکانیزم هماهنگی اضافی نیاز نداریم. تنها چیزی که نیاز دارید یک کارگزار پیام با پشتیبانی از "حداقل یک بار" تضمین به عنوان حمل و نقل است.
اما چنین راه حلی نیز "قیمت" خود را دارد:
منطق کسب و کار پیچیده تر می شود: شما باید جبران خسارت کنید.
لازم است سازگاری کامل را کنار بگذاریم که می تواند به ویژه برای سیستم های یکپارچه حساس باشد.
معماری کمی پیچیده تر می شود، نیاز اضافی به یک کارگزار پیام وجود دارد.
ابزار نظارت و مدیریت اضافی مورد نیاز خواهد بود (اگرچه به طور کلی این حتی خوب است: کیفیت خدمات سیستم افزایش خواهد یافت).
برای سیستم های یکپارچه، توجیه استفاده از "Sags" چندان واضح نیست. برای میکروسرویس ها و سایر SOA ها، که به احتمال زیاد، در حال حاضر یک کارگزار وجود دارد، و سازگاری کامل در شروع پروژه قربانی شده است، مزایای استفاده از این الگو می تواند به میزان قابل توجهی از معایب آن بیشتر باشد، به خصوص اگر یک API مناسب در آن وجود داشته باشد. سطح منطق کسب و کار
کپسوله سازی منطق کسب و کار در میکروسرویس ها
زمانی که ما شروع به آزمایش با میکروسرویس ها کردیم، یک سوال منطقی مطرح شد: منطق تجاری دامنه را در رابطه با سرویسی که تداوم داده دامنه را فراهم می کند، کجا قرار دهیم؟
هنگامی که به معماری BPMS های مختلف نگاه می کنیم، ممکن است منطقی به نظر برسد که منطق کسب و کار را از پایداری جدا کنیم: ایجاد یک لایه از پلت فرم و میکروسرویس های مستقل از دامنه که محیط و محفظه ای را برای اجرای منطق تجاری دامنه تشکیل می دهد، و تداوم داده های دامنه را به عنوان یک مجزا مرتب می کند. لایه ای از میکروسرویس های بسیار ساده و سبک. فرآیندهای تجاری در این مورد خدمات لایه پایداری را هماهنگ میکنند.
این رویکرد یک مزیت بسیار بزرگ دارد: شما می توانید عملکرد پلت فرم را تا جایی که دوست دارید افزایش دهید و فقط لایه مربوطه از میکروسرویس های پلت فرم از این طریق "چاق می شود". فرآیندهای تجاری از هر دامنه بلافاصله فرصت استفاده از قابلیت های جدید پلت فرم را به محض به روز رسانی آن به دست می آورند.
مطالعه دقیق تر کاستی های قابل توجهی را در این رویکرد نشان داد:
یک سرویس پلت فرم که منطق تجاری بسیاری از دامنه ها را به طور همزمان اجرا می کند، به عنوان یک نقطه شکست، خطرات زیادی را به همراه دارد. تغییرات مکرر در منطق کسب و کار، خطر اشکالات منجر به شکست در سراسر سیستم را افزایش می دهد.
مسائل مربوط به عملکرد: منطق کسب و کار با داده های خود از طریق یک رابط باریک و کند کار می کند:
داده ها یک بار دیگر مارشال شده و از طریق پشته شبکه پمپ می شوند.
به دلیل ناکافی بودن قابلیت پارامترسازی پرس و جو در سطح API خارجی سرویس، سرویس دامنه اغلب داده های بیشتری را نسبت به منطق تجاری که برای پردازش نیاز دارد، برمی گرداند.
چندین بخش مستقل از منطق تجاری می توانند بارها و بارها همان داده ها را برای پردازش درخواست کنند (شما می توانید این مشکل را با اضافه کردن Session beans که داده ها را در حافظه پنهان می کند، کاهش دهید، اما این امر معماری را پیچیده تر می کند و مشکلاتی در تازگی داده ها و عدم اعتبار کش ایجاد می کند).
مسائل معاملاتی:
فرآیندهای تجاری با وضعیت پایدار ذخیره شده توسط سرویس پلت فرم با داده های دامنه ناسازگار هستند و هیچ راه آسانی برای حل این مشکل وجود ندارد.
انتقال قفل داده های دامنه از تراکنش: اگر منطق کسب و کار دامنه نیاز به ایجاد تغییرات داشته باشد، پس از بررسی صحت داده های واقعی، لازم است امکان تغییر رقابتی در داده های پردازش شده حذف شود. مسدود کردن خارجی داده ها می تواند به حل مشکل کمک کند، اما چنین راه حلی خطرات بیشتری را به همراه دارد و قابلیت اطمینان کلی سیستم را کاهش می دهد.
عوارض اضافی هنگام به روز رسانی: در برخی موارد، شما باید سرویس پایداری و منطق تجاری را به طور همزمان یا به ترتیب دقیق به روز کنید.
در پایان، مجبور شدم به اصول اولیه بازگردم: داده های دامنه و منطق تجاری دامنه را در یک میکروسرویس محصور کنید. این رویکرد درک میکروسرویس را به عنوان یک جزء جدایی ناپذیر در سیستم ساده می کند و مشکلات فوق را ایجاد نمی کند. این نیز رایگان نیست:
استانداردسازی API برای تعامل با منطق تجاری (به ویژه برای ارائه فعالیت های کاربر به عنوان بخشی از فرآیندهای تجاری) و خدمات پلت فرم API مورد نیاز است. توجه دقیق تر به تغییرات API، سازگاری رو به جلو و عقب مورد نیاز است.
برای اطمینان از عملکرد منطق تجاری به عنوان بخشی از هر میکروسرویس، لازم است کتابخانههای زمان اجرا اضافی اضافه شود، و این باعث ایجاد الزامات جدیدی برای چنین کتابخانههایی میشود: سبکی و حداقل وابستگیهای گذرا.
توسعه دهندگان منطق کسب و کار باید نسخه های کتابخانه را پیگیری کنند: اگر یک میکروسرویس برای مدت طولانی نهایی نشده باشد، به احتمال زیاد دارای نسخه قدیمی کتابخانه ها خواهد بود. این می تواند یک مانع غیرمنتظره برای افزودن یک ویژگی جدید باشد و ممکن است نیاز داشته باشد که منطق تجاری قدیمی چنین سرویسی به نسخه های جدید کتابخانه ها منتقل شود، اگر تغییرات ناسازگاری بین نسخه ها وجود داشته باشد.
لایهای از خدمات پلتفرم نیز در چنین معماری وجود دارد، اما این لایه دیگر محفظهای برای اجرای منطق کسبوکار دامنه تشکیل نمیدهد، بلکه فقط محیط آن را تشکیل میدهد که توابع «پلتفرم» کمکی را ارائه میکند. چنین لایه ای نه تنها برای حفظ سبکی ریزسرویس های دامنه، بلکه برای متمرکز کردن مدیریت نیز مورد نیاز است.
به عنوان مثال، فعالیت های کاربر در فرآیندهای تجاری، وظایفی را ایجاد می کند. با این حال، هنگام کار با وظایف، کاربر باید وظایف همه دامنهها را در فهرست کلی ببیند، به این معنی که باید یک سرویس پلت فرم ثبت وظیفه مناسب، پاک از منطق تجاری دامنه وجود داشته باشد. حفظ کپسوله کردن منطق تجاری در این زمینه کاملاً مشکل ساز است و این یکی دیگر از مصالحه این معماری است.
یکپارچه سازی فرآیندهای کسب و کار از دید یک توسعه دهنده برنامه
همانطور که در بالا ذکر شد، توسعهدهنده برنامه باید از ویژگیهای فنی و مهندسی اجرای تعامل چندین برنامه انتزاعی باشد تا بتواند روی بهرهوری توسعه خوب حساب کند.
بیایید سعی کنیم یک مشکل ادغام نسبتاً دشوار را که مخصوصاً برای مقاله اختراع شده است حل کنیم. این یک کار "بازی" خواهد بود که شامل سه برنامه است، که در آن هر یک از آنها نام دامنه ای را تعریف می کند: "app1"، "app2"، "app3".
در داخل هر برنامه، فرآیندهای تجاری راه اندازی می شوند که از طریق گذرگاه یکپارچه سازی شروع به "توپ بازی" می کنند. پیام هایی با نام "Ball" به عنوان توپ عمل می کنند.
قوانین بازی:
اولین بازیکن آغازگر است. او بازیکنان دیگر را به بازی دعوت می کند، بازی را شروع می کند و می تواند آن را در هر زمان به پایان برساند.
سایر بازیکنان شرکت خود را در بازی اعلام می کنند، با یکدیگر و اولین بازیکن "آشنایی" می یابند.
پس از دریافت توپ، بازیکن بازیکن دیگر شرکت کننده را انتخاب کرده و توپ را به او پاس می دهد. تعداد کل پاس ها شمرده می شود.
هر بازیکن دارای "انرژی" است که با هر پاس توپ توسط آن بازیکن کاهش می یابد. وقتی انرژی تمام می شود، بازیکن از بازی حذف می شود و بازنشستگی خود را اعلام می کند.
اگر بازیکن تنها بماند، فوراً جدایی خود را اعلام می کند.
وقتی همه بازیکنان حذف شوند، اولین بازیکن پایان بازی را اعلام می کند. اگر او بازی را زودتر ترک کرد، پس باید بازی را دنبال کرد تا آن را کامل کند.
برای حل این مشکل، من از DSL خود برای فرآیندهای تجاری استفاده می کنم، که به شما امکان می دهد منطق را در Kotlin به طور فشرده و با حداقل یک دیگ بخار توصیف کنید.
در برنامه app1، فرآیند کسب و کار اولین بازیکن (او همچنین آغازگر بازی است) کار خواهد کرد:
کلاس InitialPlayer
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 شامل فرآیند کسب و کار بازیکن دیگر می شود:
کلاس RandomPlayer
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}")
}
نمودار:
در اپلیکیشن app3، بازیکن را با رفتار کمی متفاوت می سازیم: به جای انتخاب تصادفی بازیکن بعدی، طبق الگوریتم دورگرد عمل می کند:
کلاس RoundRobinPlayer
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}")
}
در غیر این صورت، رفتار بازیکن با رفتار قبلی تفاوتی ندارد، بنابراین نمودار تغییر نمی کند.
اکنون برای اجرای همه آن به یک آزمایش نیاز داریم. من فقط کد خود آزمون را میدهم تا مقاله را با دیگ بخار شلوغ نکنم (در واقع از محیط آزمایشی که قبلاً ایجاد شده بود برای آزمایش ادغام سایر فرآیندهای تجاری استفاده کردم):
testGame()
@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;
}
اگر ابزارهای لازم در دسترس باشد، توسعه دهندگان برنامه می توانند تعاملات یکپارچه سازی بین برنامه ها را بدون جدا شدن از منطق تجاری ایجاد کنند.
پیچیدگی (پیچیدگی) یک کار یکپارچهسازی که به شایستگیهای مهندسی نیاز دارد، میتواند در داخل چارچوب پنهان شود، اگر در ابتدا در معماری چارچوب مشخص شده باشد. دشواری کار (سختی) را نمی توان پنهان کرد، بنابراین راه حل یک کار دشوار در کد مطابق با آن به نظر می رسد.
هنگام توسعه منطق ادغام، لازم است در نهایت سازگاری و عدم خطی شدن تغییر حالت همه شرکت کنندگان در ادغام را در نظر گرفت. این ما را مجبور می کند که منطق را پیچیده کنیم تا آن را نسبت به ترتیب رخ دادن رویدادهای خارجی بی احساس کنیم. در مثال ما، بازیکن پس از اعلام خروج خود از بازی مجبور به شرکت در بازی می شود: بازیکنان دیگر همچنان توپ را به او پاس می دهند تا زمانی که اطلاعات مربوط به خروج او به دست او برسد و توسط همه شرکت کنندگان پردازش شود. این منطق از قواعد بازی پیروی نمی کند و یک راه حل سازش در چارچوب معماری انتخاب شده است.
در مرحله بعد، اجازه دهید در مورد ظرافت های مختلف راه حل، مصالحه و سایر نکات خود صحبت کنیم.
همه پیام ها در یک صف
همه برنامه های یکپارچه با یک گذرگاه یکپارچه سازی کار می کنند که به عنوان یک واسطه خارجی، یک BPMQueue برای پیام ها و یک موضوع BPMTopic برای سیگنال ها (رویدادها) نشان داده می شود. عبور همه پیام ها از طریق یک صف به خودی خود یک مصالحه است. در سطح منطق کسب و کار، اکنون می توانید بدون ایجاد تغییرات در ساختار سیستم، هر تعداد پیام جدید را که می خواهید معرفی کنید. این یک ساده سازی قابل توجه است، اما خطرات خاصی را به همراه دارد، که در چارچوب وظایف معمول ما، به نظر ما چندان مهم نبود.
با این حال، در اینجا یک نکته ظریف وجود دارد: هر برنامه پیامهای "خود" را از صف ورودی، با نام دامنه خود فیلتر میکند. همچنین، در صورت نیاز به محدود کردن "حوزه" سیگنال به یک برنامه واحد، دامنه را می توان در سیگنال ها مشخص کرد. این باید پهنای باند گذرگاه را افزایش دهد، اما منطق تجاری اکنون باید با نام های دامنه کار کند: اجباری برای آدرس دهی پیام ها، مطلوب برای سیگنال ها.
اطمینان از قابلیت اطمینان گذرگاه یکپارچه سازی
قابلیت اطمینان از چند چیز تشکیل شده است:
کارگزار پیام انتخاب شده یک جزء حیاتی از معماری و یک نقطه شکست واحد است: باید به اندازه کافی تحمل خطا داشته باشد. شما باید فقط از پیاده سازی های تست شده با پشتیبانی خوب و یک جامعه بزرگ استفاده کنید.
لازم است از دسترسی بالای کارگزار پیام اطمینان حاصل شود، که برای آن باید از نظر فیزیکی از برنامه های یکپارچه جدا شود (در دسترس بودن برنامه های کاربردی با منطق تجاری کاربردی بسیار دشوارتر و گران تر است).
کارگزار موظف است "حداقل یک بار" ضمانت تحویل را ارائه دهد. این یک الزام اجباری برای عملکرد قابل اعتماد گذرگاه یکپارچه سازی است. نیازی به تضمین سطح "دقیقا یک بار" نیست: فرآیندهای تجاری معمولاً به دریافت مکرر پیام ها یا رویدادها حساس نیستند و در کارهای ویژه که این مهم است، اضافه کردن چک های اضافی به منطق تجاری آسان تر از استفاده مداوم است. تضمین "گران" ";
ارسال پیام ها و سیگنال ها باید در یک تراکنش مشترک با تغییر در وضعیت فرآیندهای تجاری و داده های دامنه دخیل باشد. گزینه ترجیحی استفاده از الگو خواهد بود صندوق خروجی تراکنش، اما به یک جدول اضافی در پایگاه داده و یک رله نیاز دارد. در برنامه های JEE، این کار را می توان با استفاده از یک مدیر JTA محلی ساده کرد، اما اتصال به کارگزار انتخاب شده باید بتواند در حالت کار کند. XA;
گردانندگان پیامها و رویدادهای دریافتی نیز باید با تراکنش تغییر وضعیت فرآیند کسبوکار کار کنند: اگر چنین معاملهای برگشت داده شود، دریافت پیام نیز باید لغو شود.
پیام هایی که به دلیل خطا نمی توانند تحویل داده شوند باید در یک فروشگاه جداگانه ذخیره شوند D.L.Q. (صف نامه مرده). برای انجام این کار، ما یک میکروسرویس پلتفرم جداگانه ایجاد کردیم که چنین پیامهایی را در فضای ذخیرهسازی خود ذخیره میکند، آنها را با ویژگیها فهرستبندی میکند (برای گروهبندی و جستجوی سریع)، و API را برای مشاهده، ارسال مجدد به آدرس مقصد و حذف پیامها در معرض دید قرار میدهد. مدیران سیستم می توانند از طریق رابط وب خود با این سرویس کار کنند.
در تنظیمات کارگزار، برای کاهش احتمال ورود پیامها به DLQ، باید تعداد دفعات تحویل مجدد و تأخیر بین تحویلها را تنظیم کنید (محاسبه پارامترهای بهینه تقریبا غیرممکن است، اما میتوانید به صورت تجربی عمل کنید و آنها را در طول زمان تنظیم کنید. عمل)؛
فروشگاه DLQ باید به طور مداوم نظارت شود و سیستم نظارت باید مدیران سیستم را مطلع کند تا در صورت بروز پیامهای تحویلنشده، بتوانند در سریعترین زمان ممکن پاسخ دهند. این "منطقه آسیب" شکست یا خطای منطق تجاری را کاهش می دهد.
گذرگاه یکپارچه سازی باید نسبت به عدم حضور موقت برنامه ها حساس نباشد: اشتراک موضوع باید بادوام باشد و نام دامنه برنامه باید منحصر به فرد باشد تا شخص دیگری در طول غیبت برنامه سعی نکند پیام آن را از صف پردازش کند.
اطمینان از ایمنی رشته منطق تجاری
یک نمونه از یک فرآیند تجاری می تواند چندین پیام و رویداد را به طور همزمان دریافت کند که پردازش آنها به صورت موازی آغاز می شود. در عین حال، برای یک توسعه دهنده برنامه، همه چیز باید ساده و ایمن باشد.
منطق کسب و کار فرآیند هر رویداد خارجی را که بر این فرآیند تجاری تأثیر می گذارد به صورت جداگانه پردازش می کند. این رویدادها می توانند عبارتند از:
راه اندازی یک نمونه فرآیند کسب و کار؛
یک اقدام کاربر مربوط به یک فعالیت در یک فرآیند تجاری؛
دریافت پیام یا سیگنالی که یک نمونه فرآیند تجاری در آن ثبت شده است.
انقضای تایمر تنظیم شده توسط نمونه فرآیند کسب و کار؛
کنترل کنش از طریق API (مثلاً توقف فرآیند).
هر یک از این رویدادها میتواند وضعیت یک نمونه فرآیند تجاری را تغییر دهد: برخی از فعالیتها میتوانند پایان یابند و برخی دیگر شروع میشوند، مقادیر ویژگیهای پایدار میتوانند تغییر کنند. بستن هر فعالیت ممکن است منجر به فعال شدن یک یا چند مورد از فعالیت های زیر شود. آنها به نوبه خود میتوانند منتظر رویدادهای دیگر نباشند، یا اگر به دادههای اضافی نیاز ندارند، میتوانند در همان تراکنش کامل شوند. قبل از بستن تراکنش، وضعیت جدید فرآیند تجاری در پایگاه داده ذخیره می شود، جایی که منتظر رویداد خارجی بعدی می ماند.
داده های دائمی فرآیند کسب و کار ذخیره شده در یک پایگاه داده رابطه ای یک نقطه همگام سازی پردازش بسیار راحت هنگام استفاده از SELECT FOR UPDATE است. اگر یک تراکنش بتواند وضعیت فرآیند کسب و کار را از پایگاه داده دریافت کند تا آن را تغییر دهد، هیچ تراکنش دیگری به طور موازی قادر به تغییر وضعیت مشابه برای تغییر دیگری نخواهد بود و پس از اتمام اولین تراکنش، تراکنش دوم تضمین شده است که وضعیت قبلاً تغییر یافته را دریافت می کند.
با استفاده از قفل های بدبینانه در سمت DBMS، ما تمام الزامات لازم را برآورده می کنیم ACID، و همچنین توانایی مقیاس سازی برنامه با منطق تجاری را با افزایش تعداد نمونه های در حال اجرا حفظ می کند.
با این حال، قفلهای بدبینانه ما را با بنبست تهدید میکنند، به این معنی که SELECT FOR UPDATE همچنان باید به مدت زمان معقولی در صورت بنبست در برخی موارد فاحش در منطق تجاری محدود شود.
مشکل دیگر همگام سازی شروع فرآیند کسب و کار است. در حالی که هیچ نمونه فرآیند تجاری وجود ندارد، هیچ حالتی در پایگاه داده نیز وجود ندارد، بنابراین روش توصیف شده کار نخواهد کرد. اگر میخواهید از منحصربهفرد بودن یک نمونه فرآیند تجاری در یک محدوده خاص اطمینان حاصل کنید، به نوعی شی همگامسازی مرتبط با کلاس فرآیند و محدوده مربوطه نیاز دارید. برای حل این مشکل، از مکانیزم قفل متفاوتی استفاده می کنیم که به ما امکان می دهد از طریق یک سرویس خارجی، یک منبع دلخواه را که توسط یک کلید در قالب URI مشخص شده است، قفل کنیم.
در مثال های ما، فرآیند کسب و کار InitialPlayer حاوی یک اعلان است
uniqueConstraint = UniqueConstraints.singleton
بنابراین، گزارش حاوی پیام هایی در مورد گرفتن و رها کردن قفل کلید مربوطه است. چنین پیام هایی برای سایر فرآیندهای تجاری وجود ندارد: منحصر به فرد تنظیم نشده است.
مشکلات فرآیند کسب و کار با وضعیت پایدار
گاهی اوقات داشتن حالت پایدار نه تنها کمک می کند، بلکه واقعاً مانع از پیشرفت می شود.
مشکلات زمانی شروع می شوند که شما نیاز به ایجاد تغییراتی در منطق کسب و کار و/یا مدل فرآیند کسب و کار دارید. هیچ چنین تغییری با وضعیت قدیمی فرآیندهای تجاری سازگار نیست. اگر نمونههای "زنده" زیادی در پایگاه داده وجود داشته باشد، ایجاد تغییرات ناسازگار میتواند باعث ایجاد مشکلات زیادی شود که اغلب هنگام استفاده از jBPM با آن مواجه میشویم.
بسته به عمق تغییر، می توانید به دو روش عمل کنید:
یک نوع فرآیند کسب و کار جدید ایجاد کنید تا تغییرات ناسازگاری با نوع قبلی ایجاد نکنید و هنگام شروع نمونه های جدید از آن به جای نوع قبلی استفاده کنید. نمونه های قدیمی "روش قدیمی" به کار خود ادامه خواهند داد.
هنگام به روز رسانی منطق کسب و کار، وضعیت پایدار فرآیندهای تجاری را تغییر دهید.
راه اول ساده تر است، اما محدودیت ها و معایب خود را دارد، به عنوان مثال:
تکرار منطق کسب و کار در بسیاری از مدل های فرآیند کسب و کار، افزایش حجم منطق کسب و کار؛
اغلب یک انتقال فوری به یک منطق تجاری جدید مورد نیاز است (تقریبا همیشه از نظر وظایف یکپارچه سازی).
توسعه دهنده نمی داند در چه مرحله ای می توان مدل های منسوخ را حذف کرد.
در عمل، ما از هر دو رویکرد استفاده می کنیم، اما تعدادی تصمیم برای ساده کردن زندگی خود گرفته ایم:
در پایگاه داده، وضعیت پایدار فرآیند کسب و کار به شکلی به راحتی قابل خواندن و پردازش آسان ذخیره می شود: در یک رشته با فرمت JSON. این به شما این امکان را می دهد که هم در داخل برنامه و هم در خارج از آن مهاجرت انجام دهید. در موارد شدید، شما همچنین می توانید آن را با دسته ها تغییر دهید (به ویژه در توسعه در هنگام اشکال زدایی مفید است).
منطق کسبوکار یکپارچهسازی از نامهای فرآیندهای تجاری استفاده نمیکند، به طوری که در هر زمان میتوان اجرای یکی از فرآیندهای شرکتکننده را با یک نام جدید جایگزین کرد (به عنوان مثال، "InitialPlayerV2"). اتصال از طریق نام پیام ها و سیگنال ها اتفاق می افتد.
مدل فرآیند یک شماره نسخه دارد که در صورت ایجاد تغییرات ناسازگار در این مدل، آن را افزایش میدهیم و این عدد همراه با وضعیت نمونه فرآیند ذخیره میشود.
حالت پایدار فرآیند ابتدا از پایه به یک مدل شی مناسب خوانده می شود که اگر شماره نسخه مدل تغییر کرده باشد، رویه مهاجرت می تواند با آن کار کند.
رویه مهاجرت در کنار منطق تجاری قرار می گیرد و برای هر نمونه از فرآیند تجاری در زمان بازیابی آن از پایگاه داده "تنبل" نامیده می شود.
اگر نیاز دارید که وضعیت همه نمونه های فرآیند را به سرعت و همزمان منتقل کنید، راه حل های کلاسیک تری برای انتقال پایگاه داده استفاده می شود، اما باید در آنجا با JSON کار کنید.
آیا به چارچوب دیگری برای فرآیندهای تجاری نیاز دارم؟
راه حل های شرح داده شده در مقاله به ما این امکان را می دهد که زندگی خود را به طور قابل توجهی ساده کنیم، دامنه مسائل حل شده در سطح توسعه برنامه را گسترش دهیم و ایده جداسازی منطق تجاری به میکروسرویس ها را جذاب تر کنیم. برای این کار، کارهای زیادی انجام شده است، یک چارچوب بسیار سبک وزن برای فرآیندهای تجاری و همچنین اجزای خدماتی برای حل مشکلات شناسایی شده در زمینه طیف گسترده ای از وظایف کاربردی ایجاد شده است. ما تمایل داریم این نتایج را به اشتراک بگذاریم تا توسعه اجزای مشترک را تحت یک مجوز رایگان به دسترسی آزاد تبدیل کنیم. این امر مستلزم کمی تلاش و زمان است. درک تقاضا برای چنین راه حل هایی می تواند یک انگیزه اضافی برای ما باشد. در مقاله پیشنهادی به قابلیت های خود فریم ورک بسیار کم توجه شده است، اما برخی از آنها از نمونه های ارائه شده قابل مشاهده است. اگر با این وجود چارچوب خود را منتشر کنیم، مقاله جداگانه ای به آن اختصاص داده خواهد شد. در ضمن، اگر با پاسخ دادن به این سوال، بازخورد کوچکی را در اختیار ما بگذارید، سپاسگزار خواهیم بود:
فقط کاربران ثبت نام شده می توانند در نظرسنجی شرکت کنند. ورود، لطفا.
آیا به چارچوب دیگری برای فرآیندهای تجاری نیاز دارم؟
٪۱۰۰بله من خیلی وقته دنبال همچین چیزی بودم.
٪۱۰۰جالب است که در مورد پیاده سازی خود بیشتر بدانید، ممکن است مفید باشد2
٪۱۰۰ما از یکی از فریمورک های موجود استفاده می کنیم، اما به فکر جایگزینی آن هستیم
٪۱۰۰ما از یکی از چارچوب های موجود استفاده می کنیم، همه چیز مناسب است