BPM uslubi integratsiyasi

BPM uslubi integratsiyasi

Salom, Habr!

Bizning kompaniyamiz ERP toifasidagi dasturiy echimlarni ishlab chiqishga ixtisoslashgan bo'lib, unda asosiy ulush katta hajmdagi biznes mantig'i va ish jarayoni a la EDMS bilan tranzaktsion tizimlarga to'g'ri keladi. Mahsulotlarimizning zamonaviy versiyalari JavaEE texnologiyalariga asoslangan, ammo biz mikroservislar bilan ham faol tajriba o'tkazmoqdamiz. Bunday echimlarning eng muammoli sohalaridan biri qo'shni domenlar bilan bog'liq bo'lgan turli quyi tizimlarning integratsiyasidir. Biz foydalanadigan arxitektura uslublari, texnologiya steklari va ramkalaridan qat'i nazar, integratsiya vazifalari doimo bizga katta bosh og'rig'ini keltirdi, ammo yaqinda bunday muammolarni hal qilishda muvaffaqiyatlar kuzatilmoqda.

Sizning e'tiboringizga taqdim etilgan maqolada men NPO Kristaning belgilangan hududdagi tajribasi va arxitektura tadqiqotlari haqida gapiraman. Biz, shuningdek, dastur ishlab chiquvchisi nuqtai nazaridan integratsiya muammosini hal qilishning oddiy misolini ko'rib chiqamiz va bu soddalik ortida nima yashiringanligini bilib olamiz.

Rad etish

Maqolada tasvirlangan me'moriy va texnik echimlar men tomonidan muayyan vazifalar kontekstida shaxsiy tajribaga asoslangan holda taklif etiladi. Ushbu echimlar universal deb da'vo qilmaydi va boshqa foydalanish shartlarida optimal bo'lmasligi mumkin.

BPM ning bunga qanday aloqasi bor?

Bu savolga javob berish uchun biz yechimlarimizning qo'llaniladigan muammolarining o'ziga xos xususiyatlarini biroz o'rganishimiz kerak. Bizning odatiy tranzaktsion tizimimizda biznes mantiqining asosiy qismi foydalanuvchi interfeyslari orqali ma'lumotlar bazasiga ma'lumotlarni kiritish, ushbu ma'lumotlarni qo'lda va avtomatik ravishda tekshirish, uni qandaydir ish jarayoni orqali o'tkazish, boshqa tizimga / tahliliy ma'lumotlar bazasiga / arxivga nashr qilish, hisobotlarni yaratishdir. Shunday qilib, mijozlar uchun tizimning asosiy vazifasi ularning ichki biznes jarayonlarini avtomatlashtirishdir.

Qulaylik uchun biz aloqada "hujjat" atamasidan ma'lum bir ish jarayoni "biriktirilishi" mumkin bo'lgan umumiy kalit bilan birlashtirilgan ma'lumotlar to'plamining ba'zi bir mavhumligi sifatida foydalanamiz.
Ammo integratsiya mantig'i haqida nima deyish mumkin? Axir, integratsiya vazifasi mijozning iltimosiga binoan EMAS, balki butunlay boshqa omillar ta'siri ostida qismlarga "arralangan" tizim arxitekturasi tomonidan yaratiladi:

  • Konvey qonunining ta'siri ostida;
  • boshqa mahsulotlar uchun ilgari ishlab chiqilgan quyi tizimlardan qayta foydalanish natijasida;
  • me'mor tomonidan qaror qabul qilinganidek, funktsional bo'lmagan talablar asosida.

Biznes mantiqini integratsiya artefaktlari bilan ifloslantirmaslik va dastur ishlab chiquvchisini tizimning me'moriy landshaftining o'ziga xos xususiyatlarini o'rganishdan qutqarish uchun integratsiya mantig'ini asosiy ish jarayonining biznes mantig'idan ajratish katta vasvasa mavjud. Ushbu yondashuv bir qator afzalliklarga ega, ammo amaliyot uning samarasizligini ko'rsatadi:

  • integratsiya muammolarini hal qilish, odatda, asosiy ish oqimini amalga oshirishda cheklangan kengaytma nuqtalari tufayli sinxron qo'ng'iroqlar ko'rinishidagi eng oddiy variantlarga siljiydi (quyida sinxron integratsiyaning kamchiliklari haqida batafsilroq);
  • integratsiya artefaktlari boshqa quyi tizimdan fikr-mulohazalar zarur bo'lganda ham asosiy biznes mantig'iga kirib boradi;
  • dastur ishlab chiqaruvchisi integratsiyani e'tiborsiz qoldiradi va ish jarayonini o'zgartirib, uni osongina buzishi mumkin;
  • tizim foydalanuvchi nuqtai nazaridan bir butun bo'lishni to'xtatadi, quyi tizimlar orasidagi "choklar" sezilarli bo'ladi, ma'lumotlarni bir quyi tizimdan ikkinchisiga o'tkazishni boshlaydigan ortiqcha foydalanuvchi operatsiyalari paydo bo'ladi.

Yana bir yondashuv - integratsiyalashgan o'zaro ta'sirlarni asosiy biznes mantig'i va ish jarayonining ajralmas qismi sifatida ko'rib chiqish. Ilovalarni ishlab chiquvchilarning mahorat talablari keskin oshib ketmasligi uchun yangi integratsiya shovqinlarini yaratish oson va tabiiy ravishda, yechim tanlashning minimal variantlari bilan amalga oshirilishi kerak. Bu ko'rinadiganidan ko'ra qiyinroq: asbob foydalanuvchiga undan foydalanish uchun zarur bo'lgan turli xil variantlarni taqdim etishi va shu bilan birga oyog'iga o'q otilishiga yo'l qo'ymaslik uchun etarlicha kuchli bo'lishi kerak. Muhandis integratsiya vazifalari kontekstida javob berishi kerak bo'lgan ko'plab savollar mavjud, ammo dastur ishlab chiquvchisi kundalik ishlarida o'ylamasligi kerak: tranzaktsiyalar chegaralari, izchillik, atomiklik, xavfsizlik, masshtablash, yuk va resurslarni taqsimlash, marshrutlash, marshallash, targ'ibot va kontekstlarni o'zgartirish va hokazo. Ilovalarni ishlab chiquvchilarga juda oddiy qarorlar shablonlarini taklif qilish kerak, ularda bunday savollarga javoblar allaqachon yashiringan. Ushbu naqshlar etarlicha xavfsiz bo'lishi kerak: biznes mantig'i juda tez-tez o'zgarib turadi, bu xatolarni kiritish xavfini oshiradi, xatolar narxi ancha past darajada qolishi kerak.

Ammo shunga qaramay, BPM ning bunga nima aloqasi bor? Ish jarayonini amalga oshirishning ko'plab variantlari mavjud ...
Haqiqatan ham, biznes-jarayonlarning yana bir amalga oshirilishi bizning echimlarimizda juda mashhur - davlat o'tish diagrammasining deklarativ sozlamalari va o'tishlarga biznes mantig'i bilan ishlov beruvchilarni ulash orqali. Shu bilan birga, ish jarayonida "hujjat" ning hozirgi holatini belgilovchi davlat "hujjat" ning o'ziga xos atributidir.

BPM uslubi integratsiyasi
Loyihaning boshida jarayon shunday ko'rinadi

Bunday amalga oshirishning mashhurligi chiziqli biznes jarayonlarini yaratishning nisbatan soddaligi va tezligi bilan bog'liq. Biroq, dasturiy ta'minot tizimlarining murakkablashishi bilan biznes jarayonining avtomatlashtirilgan qismi o'sib boradi va murakkablashadi. Har bir filial parallel ravishda bajarilishi uchun parchalanish, jarayonlarning qismlarini qayta ishlatish, shuningdek, vilkalar jarayonlariga ehtiyoj bor. Bunday sharoitda asbob noqulay bo'lib qoladi va holatga o'tish diagrammasi axborot mazmunini yo'qotadi (integratsiya o'zaro ta'siri diagrammada umuman aks ettirilmaydi).

BPM uslubi integratsiyasi
Talablarni aniqlashtirishning bir necha marta takrorlanishidan keyin jarayon shunday ko'rinadi

Ushbu vaziyatdan chiqish yo'li dvigatelning integratsiyasi edi jBPM eng murakkab biznes jarayonlariga ega bo'lgan ba'zi mahsulotlarga. Qisqa muddatda ushbu yechim biroz muvaffaqiyatga erishdi: yozuvda juda ma'lumotli va dolzarb diagrammani saqlab, murakkab biznes jarayonlarini amalga oshirish mumkin bo'ldi. BPMN2.

BPM uslubi integratsiyasi
Murakkab biznes jarayonining kichik qismi

Uzoq muddatda yechim umidlarni oqlamadi: vizual vositalar yordamida biznes-jarayonlarni yaratishda yuqori mehnat zichligi maqbul samaradorlik ko'rsatkichlariga erishishga imkon bermadi va asbobning o'zi ishlab chiquvchilar orasida eng yoqmaydigan narsalardan biriga aylandi. Dvigatelning ichki tuzilishi haqida ham shikoyatlar bor edi, bu ko'plab "yamoqlar" va "tayoqchalar" paydo bo'lishiga olib keldi.

JBPM-dan foydalanishning asosiy ijobiy tomoni biznes-jarayon namunasi uchun o'zining doimiy holatiga ega bo'lishning foydalari va zararlarini anglash edi. Shuningdek, biz signallar va xabarlar orqali asinxron shovqinlardan foydalangan holda turli ilovalar o'rtasida murakkab integratsiya protokollarini amalga oshirish uchun jarayon yondashuvidan foydalanish imkoniyatini ko'rdik. Bunda doimiy davlatning mavjudligi hal qiluvchi rol o'ynaydi.

Yuqoridagilarga asoslanib, biz xulosa qilishimiz mumkin: BPM uslubidagi jarayon yondashuvi bizga tobora murakkab bo'lgan biznes jarayonlarini avtomatlashtirish bo'yicha keng ko'lamli vazifalarni hal qilishga, integratsiya faoliyatini ushbu jarayonlarga uyg'unlashtirishga va amalga oshirilgan jarayonni mos yozuvda vizual ravishda aks ettirish qobiliyatini saqlab qolishga imkon beradi.

Integratsiya sxemasi sifatida sinxron qo'ng'iroqlarning kamchiliklari

Sinxron integratsiya eng oddiy blokirovka qiluvchi qo'ng'iroqni anglatadi. Bitta quyi tizim server tomoni sifatida ishlaydi va APIni kerakli usul bilan ochadi. Boshqa quyi tizim mijoz tomoni sifatida ishlaydi va kerakli vaqtda natijani kutgan holda qo'ng'iroq qiladi. Tizimning arxitekturasiga qarab, mijoz va server tomonlari bir xil dastur va jarayonda yoki turli xillarida joylashtirilishi mumkin. Ikkinchi holda, siz RPC-ning ba'zi bir amalga oshirilishini qo'llashingiz va qo'ng'iroqning parametrlari va natijasini marshalllashni ta'minlashingiz kerak.

BPM uslubi integratsiyasi

Bunday integratsiya sxemasi juda katta kamchiliklarga ega, ammo u soddaligi tufayli amalda juda keng qo'llaniladi. Amalga oshirish tezligi sizni o'ziga jalb qiladi va uni "yonib ketish" muddati sharoitida qayta-qayta qo'llashga majbur qiladi, yechimni texnik qarzga yozadi. Ammo tajribasiz ishlab chiquvchilar uni ongsiz ravishda ishlatishadi, shunchaki salbiy oqibatlarni sezmaydilar.

Quyi tizimlar ulanishining eng aniq o'sishiga qo'shimcha ravishda, tranzaktsiyalarni "tarqatish" va "cho'zish" bilan bog'liq kamroq aniq muammolar mavjud. Haqiqatan ham, agar biznes mantig'i har qanday o'zgarishlarni amalga oshirsa, tranzaktsiyalar ajralmas hisoblanadi va tranzaktsiyalar, o'z navbatida, ushbu o'zgarishlardan ta'sirlangan ma'lum dastur resurslarini bloklaydi. Ya'ni, bitta quyi tizim boshqasidan javob kutmaguncha, u tranzaktsiyani yakunlay olmaydi va qulflarni bo'shata olmaydi. Bu turli xil ta'sirlar xavfini sezilarli darajada oshiradi:

  • tizimning sezgirligi yo'qoladi, foydalanuvchilar so'rovlarga javob berish uchun uzoq vaqt kutishadi;
  • server, odatda, to'lib-toshgan iplar hovuzi tufayli foydalanuvchi so'rovlariga javob berishni to'xtatadi: ko'pgina iplar tranzaksiya bilan band bo'lgan resurs qulfida "turib turadi";
  • tiqilib qolishlar paydo bo'la boshlaydi: ularning paydo bo'lish ehtimoli tranzaktsiyalar davomiyligiga, biznes mantig'i va tranzaktsiyaga jalb qilingan qulflar miqdoriga juda bog'liq;
  • tranzaksiya muddati tugashi bilan bog'liq xatolar paydo bo'ladi;
  • agar vazifa katta hajmdagi ma'lumotlarni qayta ishlash va o'zgartirishni talab qilsa, server OutOfMemory-ga "tushadi" va sinxron integratsiyalarning mavjudligi ishlov berishni "engilroq" tranzaktsiyalarga ajratishni juda qiyinlashtiradi.

Arxitektura nuqtai nazaridan, integratsiya jarayonida blokirovka qiluvchi qo'ng'iroqlardan foydalanish alohida quyi tizimlar sifatini nazorat qilishni yo'qotishiga olib keladi: bitta quyi tizimning sifat ko'rsatkichlarini boshqa quyi tizimning sifat maqsadlaridan ajratilgan holda ta'minlash mumkin emas. Agar quyi tizimlar turli jamoalar tomonidan ishlab chiqilgan bo'lsa, bu katta muammo.

Agar integratsiya qilinayotgan quyi tizimlar turli ilovalarda bo'lsa va ikkala tomondan sinxron o'zgarishlar qilish kerak bo'lsa, ishlar yanada qiziqarli bo'ladi. Ushbu o'zgarishlarni qanday qilib tranzaksiya qilish mumkin?

Agar alohida tranzaktsiyalarda o'zgarishlar amalga oshirilsa, unda mustahkam istisnolarni qayta ishlash va kompensatsiyani ta'minlash kerak bo'ladi va bu sinxron integratsiyaning asosiy afzalligi - soddalikni butunlay yo'q qiladi.

Taqsimlangan tranzaktsiyalar ham esga tushadi, ammo biz ularni o'z yechimlarimizda ishlatmaymiz: ishonchlilikni ta'minlash qiyin.

"Saga" bitimlar muammosiga yechim sifatida

Mikroservislarning ommaviyligi ortib borayotgani sababli, ularga talab ortib bormoqda Saga naqsh.

Ushbu naqsh uzoq muddatli tranzaktsiyalarning yuqoridagi muammolarini mukammal hal qiladi, shuningdek, tizimning holatini biznes mantig'i nuqtai nazaridan boshqarish imkoniyatlarini kengaytiradi: muvaffaqiyatsiz bitimdan keyin kompensatsiya tizimni asl holatiga qaytarmasligi mumkin, balki muqobil variantni taqdim etadi. ma'lumotlarni qayta ishlash yo'nalishi. Bundan tashqari, jarayonni "yaxshi" yakunlashga harakat qilganingizda, muvaffaqiyatli bajarilgan ma'lumotlarni qayta ishlash bosqichlarini takrorlamaslikka imkon beradi.

Qizig'i shundaki, monolit tizimlarda bu naqsh bo'shashmasdan bog'langan quyi tizimlarning integratsiyasi haqida gap ketganda ham dolzarb bo'lib, uzoq muddatli tranzaktsiyalar va tegishli resurs blokirovkalari natijasida yuzaga keladigan salbiy ta'sirlar mavjud.

BPM uslubidagi biznes jarayonlarimizga kelsak, Sagasni amalga oshirish juda oson: Sagasning individual bosqichlari biznes-jarayon doirasidagi faoliyat sifatida belgilanishi mumkin va biznes-jarayonning barqaror holati, shu jumladan, boshqa narsalarni belgilaydi. , dostonlarning ichki holati. Ya'ni, bizga qo'shimcha muvofiqlashtirish mexanizmi kerak emas. Sizga kerak bo'lgan yagona narsa - transport sifatida "kamida bir marta" kafolatlarni qo'llab-quvvatlaydigan xabar brokeri.

Ammo bunday yechimning ham o'ziga xos "narxi" bor:

  • biznes mantig'i yanada murakkablashadi: siz kompensatsiyani ishlab chiqishingiz kerak;
  • monolitik tizimlar uchun ayniqsa sezgir bo'lishi mumkin bo'lgan to'liq mustahkamlikdan voz kechish kerak bo'ladi;
  • arxitektura biroz murakkablashadi, xabar brokeriga qo'shimcha ehtiyoj paydo bo'ladi;
  • qo'shimcha monitoring va boshqaruv vositalari talab qilinadi (garchi umuman olganda bu hatto yaxshi: tizim xizmatining sifati oshadi).

Monolit tizimlar uchun "Sags" dan foydalanishning asosi unchalik aniq emas. Mikroservislar va boshqa SOAlar uchun, ehtimol, broker allaqachon mavjud bo'lgan va loyiha boshida to'liq izchillik qurbon qilingan bo'lsa, ushbu naqshdan foydalanishning afzalliklari kamchiliklardan sezilarli darajada ustun bo'lishi mumkin, ayniqsa, agar qulay API mavjud bo'lsa. biznes mantiq darajasi.

Mikroservislarda biznes mantiqining inkapsulyatsiyasi

Mikroservislar bilan tajriba o'tkazishni boshlaganimizda, oqilona savol tug'ildi: domen ma'lumotlarining barqarorligini ta'minlaydigan xizmatga nisbatan domen biznes mantig'ini qaerga qo'yish kerak?

Turli xil BPMS arxitekturasini ko'rib chiqayotganda, biznes mantig'ini qat'iylikdan ajratish maqsadga muvofiq bo'lib tuyulishi mumkin: domen biznes mantig'ini bajarish uchun muhit va konteynerni tashkil etuvchi platforma va domenga bog'liq bo'lmagan mikroservislar qatlamini yarating va domen ma'lumotlarining barqarorligini alohida tartibga soling. juda oddiy va engil mikroservislar qatlami. Bu holda biznes jarayonlari qat'iylik qatlami xizmatlarini tartibga soladi.

BPM uslubi integratsiyasi

Ushbu yondashuv juda katta plyusga ega: siz platformaning funksionalligini xohlaganingizcha oshirishingiz mumkin va faqat platforma mikroservislarining tegishli qatlami bundan "yog'lanadi". Har qanday domendagi biznes jarayonlar yangilangandan so'ng darhol platformaning yangi funksiyalaridan foydalanish imkoniyatiga ega bo'ladi.

Batafsilroq o'rganish ushbu yondashuvning muhim kamchiliklarini aniqladi:

  • bir vaqtning o'zida ko'plab domenlarning biznes mantig'ini bajaradigan platforma xizmati bitta muvaffaqiyatsizlik nuqtasi sifatida katta xavflarni o'z ichiga oladi. Biznes mantiqining tez-tez o'zgarishi tizim miqyosida nosozliklarga olib keladigan xatolar xavfini oshiradi;
  • ishlash muammolari: biznes mantig'i o'z ma'lumotlari bilan tor va sekin interfeys orqali ishlaydi:
    • ma'lumotlar yana bir marta yig'iladi va tarmoq stek orqali pompalanadi;
    • domen xizmati ko'pincha xizmatning tashqi API darajasida so'rovlarni parametrlash imkoniyatlari etarli emasligi sababli qayta ishlash uchun biznes mantig'idan ko'ra ko'proq ma'lumotlarni qaytaradi;
    • biznes mantig'ining bir nechta mustaqil qismlari bir xil ma'lumotlarni qayta ishlash uchun qayta-qayta so'rashi mumkin (siz ma'lumotlarni keshlash uchun seans loviyalarini qo'shish orqali bu muammoni yumshata olasiz, ammo bu arxitekturani yanada murakkablashtiradi va ma'lumotlarning yangiligi va keshni bekor qilish muammolarini keltirib chiqaradi);
  • tranzaksiya masalalari:
    • platforma xizmati tomonidan saqlanadigan doimiy holatga ega biznes jarayonlari domen ma'lumotlariga mos kelmaydi va bu muammoni hal qilishning oson yo'llari yo'q;
    • domen ma'lumotlarining qulfini tranzaktsiyadan tashqariga ko'chirish: agar domen biznes mantig'iga o'zgartirishlar kiritish kerak bo'lsa, birinchi navbatda haqiqiy ma'lumotlarning to'g'riligini tekshirgandan so'ng, qayta ishlangan ma'lumotlarning raqobatbardosh o'zgarishi ehtimolini istisno qilish kerak. Ma'lumotlarning tashqi blokirovkasi muammoni hal qilishga yordam beradi, ammo bunday yechim qo'shimcha xavflarni keltirib chiqaradi va tizimning umumiy ishonchliligini pasaytiradi;
  • yangilashda qo'shimcha asoratlar: ba'zi hollarda siz qat'iylik xizmati va biznes mantiqini sinxron yoki qat'iy ketma-ketlikda yangilashingiz kerak.

Oxir-oqibat, men asoslarga qaytishim kerak edi: domen ma'lumotlari va domen biznes mantig'ini bitta mikroservisga qamrab olish. Ushbu yondashuv mikroservisni tizimning ajralmas komponenti sifatida qabul qilishni soddalashtiradi va yuqoridagi muammolarni keltirib chiqarmaydi. Bu ham bepul emas:

  • API standartlashtirish biznes mantig'i (xususan, biznes jarayonlarining bir qismi sifatida foydalanuvchi faoliyatini ta'minlash) va API platformasi xizmatlari bilan o'zaro ta'sir qilish uchun talab qilinadi; API o'zgarishlariga ko'proq e'tibor berish, oldinga va orqaga muvofiqlik talab qilinadi;
  • har bir bunday mikroservisning bir qismi sifatida biznes mantig'ining ishlashini ta'minlash uchun qo'shimcha ish vaqti kutubxonalarini qo'shish talab qilinadi va bu bunday kutubxonalar uchun yangi talablarni keltirib chiqaradi: engillik va minimal o'tish davriga bog'liqlik;
  • biznes mantiqini ishlab chiquvchilar kutubxona versiyalarini kuzatib borishlari kerak: agar mikroservis uzoq vaqt davomida yakunlanmagan bo'lsa, unda kutubxonalarning eskirgan versiyasi bo'lishi mumkin. Bu yangi xususiyatni qo'shish uchun kutilmagan to'siq bo'lishi mumkin va versiyalar o'rtasida mos kelmaydigan o'zgarishlar bo'lsa, bunday xizmatning eski biznes mantig'ini kutubxonalarning yangi versiyalariga ko'chirishni talab qilishi mumkin.

BPM uslubi integratsiyasi

Bunday arxitekturada platforma xizmatlarining qatlami ham mavjud, ammo bu qatlam endi domen biznes mantig'ini bajarish uchun konteyner emas, balki yordamchi "platforma" funktsiyalarini ta'minlovchi faqat uning muhitini tashkil qiladi. Bunday qatlam nafaqat domen mikroservislarining yengilligini saqlab qolish, balki boshqaruvni markazlashtirish uchun ham kerak.

Masalan, biznes-jarayonlardagi foydalanuvchi faoliyati vazifalarni yaratadi. Biroq, vazifalar bilan ishlashda foydalanuvchi umumiy ro'yxatdagi barcha domenlardagi vazifalarni ko'rishi kerak, ya'ni domen biznes mantig'idan tozalangan tegishli vazifani ro'yxatga olish platformasi xizmati bo'lishi kerak. Ushbu kontekstda biznes mantig'ining inkapsulyatsiyasini saqlash juda muammoli va bu ushbu arxitekturaning yana bir murosasi.

Ilovalarni ishlab chiquvchining ko'zi bilan biznes jarayonlarini integratsiyalash

Yuqorida aytib o'tilganidek, dastur ishlab chiquvchisi yaxshi rivojlanish unumdorligiga ishonish uchun bir nechta ilovalarning o'zaro ta'sirini amalga oshirishning texnik va muhandislik xususiyatlaridan mavhum bo'lishi kerak.

Keling, maqola uchun maxsus ixtiro qilingan juda qiyin integratsiya muammosini hal qilishga harakat qilaylik. Bu uchta dasturni o'z ichiga olgan "o'yin" vazifasi bo'ladi, ularning har biri ba'zi domen nomini belgilaydi: "app1", "app2", "app3".

Har bir ilova ichida integratsiya avtobusi orqali "to'p o'ynashni" boshlaydigan biznes jarayonlari ishga tushiriladi. "Ball" nomli xabarlar to'p rolini o'ynaydi.

O'yin qoidalari:

  • birinchi o'yinchi tashabbuskor hisoblanadi. U boshqa o'yinchilarni o'yinga taklif qiladi, o'yinni boshlaydi va istalgan vaqtda tugatishi mumkin;
  • boshqa o'yinchilar o'yinda ishtirok etishlarini e'lon qiladilar, bir-birlari va birinchi o'yinchi bilan "tanishadilar";
  • to'pni qabul qilgandan so'ng, o'yinchi boshqa ishtirokchi o'yinchini tanlaydi va to'pni unga uzatadi. O'tishlarning umumiy soni hisobga olinadi;
  • har bir o'yinchida "energiya" bor, bu o'sha o'yinchi tomonidan to'pning har bir o'tishi bilan kamayadi. Energiya tugagach, o'yinchi o'yindan chetlatiladi va nafaqaga chiqqanini e'lon qiladi;
  • agar o'yinchi yolg'iz qolsa, u darhol ketishini e'lon qiladi;
  • barcha o'yinchilar chiqarib yuborilganda, birinchi o'yinchi o'yin tugaganligini e'lon qiladi. Agar u o'yinni oldinroq tark etgan bo'lsa, uni yakunlash uchun o'yinni kuzatish qoladi.

Ushbu muammoni hal qilish uchun men DSL-ni biznes jarayonlari uchun ishlataman, bu sizga Kotlin-dagi mantiqni ixcham, minimal boilerplate bilan tasvirlash imkonini beradi.

App1 ilovasida birinchi o'yinchining biznes jarayoni (u ham o'yin tashabbuskori) ishlaydi:

InitialPlayer sinfi

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}")
}

Biznes mantiqini bajarishdan tashqari, yuqoridagi kod diagramma sifatida ko'rsatilishi mumkin bo'lgan biznes jarayonining ob'ekt modelini ishlab chiqishi mumkin. Biz hali vizualizatorni amalga oshirmadik, shuning uchun rasm chizishga biroz vaqt sarflashimiz kerak edi (bu erda diagrammaning yuqoridagi kodga mos kelishini yaxshilash uchun shlyuzlardan foydalanish bo'yicha BPMN yozuvini biroz soddalashtirdim):

BPM uslubi integratsiyasi

app2 boshqa o'yinchining biznes jarayonini o'z ichiga oladi:

RandomPlayer sinfi

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}")
}

Diagramma:

BPM uslubi integratsiyasi

App3 ilovasida biz o'yinchini biroz boshqacha xatti-harakatga aylantiramiz: keyingi o'yinchini tasodifiy tanlash o'rniga, u aylana algoritmiga muvofiq harakat qiladi:

sinf 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}")
}

Aks holda, o'yinchining xatti-harakati avvalgisidan farq qilmaydi, shuning uchun diagramma o'zgarmaydi.

Endi hammasini ishga tushirish uchun test kerak. Maqola bilan bog'lanib qolmaslik uchun men faqat test kodini beraman (aslida men boshqa biznes-jarayonlarning integratsiyasini sinab ko'rish uchun avval yaratilgan test muhitidan foydalanganman):

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;
}

Sinovni bajaring, jurnalga qarang:

konsol chiqishi

Взята Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ° ΠΊΠ»ΡŽΡ‡Π° 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!

Bularning barchasidan bir nechta muhim xulosalar chiqarish mumkin:

  • agar zarur vositalar mavjud bo'lsa, dastur ishlab chiquvchilari biznes mantig'idan uzoqlashmagan holda ilovalar o'rtasida integratsiya aloqalarini yaratishi mumkin;
  • muhandislik malakalarini talab qiladigan integratsiya vazifasining murakkabligi (murakkabligi), agar u dastlab ramka arxitekturasida belgilangan bo'lsa, ramka ichida yashirin bo'lishi mumkin. Vazifaning qiyinligini (qiyinchilikni) yashirib bo'lmaydi, shuning uchun koddagi qiyin vazifaning echimi shunga mos ravishda ko'rinadi;
  • integratsiya mantig'ini ishlab chiqishda, oxir-oqibat, barcha integratsiya ishtirokchilarining holatini o'zgartirishning izchilligi va chiziqli bo'lmasligini hisobga olish kerak. Bu bizni mantiqni tashqi hodisalar sodir bo'lish tartibiga befarq qilish uchun murakkablashtirishga majbur qiladi. Bizning misolimizda, o'yinchi o'yindan chiqqanini e'lon qilganidan keyin o'yinda qatnashishga majbur bo'ladi: uning chiqishi haqidagi ma'lumot yetib borguncha va barcha ishtirokchilar tomonidan qayta ishlanmaguncha boshqa o'yinchilar to'pni unga uzatishda davom etadilar. Bu mantiq o'yin qoidalaridan kelib chiqmaydi va tanlangan arxitektura doirasidagi murosali yechimdir.

Keyinchalik, yechimimizning turli nozikliklari, murosalar va boshqa fikrlar haqida gapiraylik.

Barcha xabarlar bitta navbatda

Barcha integratsiyalangan ilovalar tashqi broker sifatida taqdim etilgan bitta integratsiya avtobusi, xabarlar uchun bitta BPMQueue va signallar (hodisalar) uchun bitta BPMTopic mavzusi bilan ishlaydi. Barcha xabarlarni bitta navbat orqali o'tkazish o'z-o'zidan murosadir. Biznes mantig'i darajasida endi siz tizim tuzilishiga o'zgartirishlar kiritmasdan xohlagancha yangi turdagi xabarlarni kiritishingiz mumkin. Bu sezilarli soddalashtirish, lekin u bizning odatiy vazifalarimiz kontekstida bizga unchalik ahamiyatsiz bo'lib tuyulgan ma'lum xavflarni o'z ichiga oladi.

BPM uslubi integratsiyasi

Biroq, bu erda bitta noziklik bor: har bir dastur kirishdagi navbatdan "o'z" xabarlarini o'z domeni nomi bilan filtrlaydi. Bundan tashqari, signalning "ko'lamini" bitta dastur bilan cheklash kerak bo'lsa, domen signallarda ko'rsatilishi mumkin. Bu avtobusning tarmoqli kengligini oshirishi kerak, ammo biznes mantig'i endi domen nomlari bilan ishlashi kerak: xabarlarni manzillash uchun majburiy, signallar uchun kerakli.

Integratsiya avtobusining ishonchliligini ta'minlash

Ishonchlilik bir necha omillardan iborat:

  • Tanlangan xabar brokeri arxitekturaning muhim komponenti va bitta nosozlik nuqtasidir: u xatoga etarlicha chidamli bo'lishi kerak. Siz yaxshi qo'llab-quvvatlash va katta hamjamiyat bilan faqat vaqt sinovidan o'tgan ilovalardan foydalanishingiz kerak;
  • xabar brokerining yuqori darajada mavjudligini ta'minlash kerak, buning uchun u jismonan integratsiyalangan ilovalardan ajratilishi kerak (qo'llaniladigan biznes mantig'iga ega ilovalarning yuqori mavjudligini ta'minlash ancha qiyin va qimmatroq);
  • broker "kamida bir marta" yetkazib berish kafolatlarini taqdim etishga majburdir. Bu integratsiya avtobusining ishonchli ishlashi uchun majburiy talabdir. "Aniq bir marta" darajadagi kafolatlarga ehtiyoj yo'q: biznes jarayonlari odatda xabarlar yoki hodisalarning takroriy kelishiga sezgir emas va bu muhim bo'lgan maxsus vazifalarda doimiy foydalanishdan ko'ra biznes mantig'iga qo'shimcha tekshiruvlar qo'shish osonroqdir. ancha "qimmat" "kafolatlar;
  • xabarlar va signallarni yuborish biznes jarayonlari va domen ma'lumotlarining holatini o'zgartirish bilan umumiy bitimda ishtirok etishi kerak. Eng maqbul variant naqshdan foydalanish bo'ladi Tranzaksiya chiqish qutisi, lekin u ma'lumotlar bazasida qo'shimcha jadval va o'rni talab qiladi. JEE ilovalarida buni mahalliy JTA menejeri yordamida soddalashtirish mumkin, ammo tanlangan brokerga ulanish rejimda ishlashi kerak. XA;
  • kiruvchi xabarlar va hodisalarni qayta ishlovchilar biznes-jarayon holatini o'zgartirish tranzaksiyasi bilan ham ishlashi kerak: agar bunday tranzaksiya orqaga qaytarilsa, xabarni qabul qilish ham bekor qilinishi kerak;
  • xatolar tufayli yetkazilmagan xabarlar alohida do'konda saqlanishi kerak D.L.Q. (O'lik xat navbati). Buning uchun biz bunday xabarlarni o'z xotirasida saqlaydigan, ularni atributlar bo'yicha indekslaydigan (tez guruhlash va qidirish uchun) va APIni ko'rish, manzil manziliga qayta yuborish va xabarlarni o'chirish uchun ochadigan alohida platforma mikroservis yaratdik. Tizim ma'murlari ushbu xizmat bilan o'zlarining veb-interfeysi orqali ishlashi mumkin;
  • broker sozlamalarida xabarlarning DLQ-ga tushish ehtimolini kamaytirish uchun etkazib berish urinishlari va etkazib berishlar orasidagi kechikishlar sonini sozlashingiz kerak (optimal parametrlarni hisoblash deyarli mumkin emas, lekin siz empirik tarzda harakat qilishingiz va ularni o'zgartirishingiz mumkin. operatsiya);
  • DLQ do'konini doimiy ravishda kuzatib borish kerak va monitoring tizimi tizim ma'murlarini xabardor qilishi kerak, shunda ular etkazib berilmagan xabarlar yuzaga kelganda imkon qadar tezroq javob berishlari mumkin. Bu nosozlik yoki biznes mantiqiy xatosining "zarar zonasini" kamaytiradi;
  • integratsiya avtobusi ilovalarning vaqtinchalik yo'qligiga befarq bo'lishi kerak: mavzu obunalari bardoshli bo'lishi kerak va ilovaning domen nomi noyob bo'lishi kerak, shunda boshqa birov ilova yo'qligida navbatdan uning xabarini qayta ishlashga harakat qilmaydi.

Biznes mantig'ining ip xavfsizligini ta'minlash

Biznes jarayonining bir xil nusxasi bir vaqtning o'zida bir nechta xabarlar va hodisalarni qabul qilishi mumkin, ularni qayta ishlash parallel ravishda boshlanadi. Shu bilan birga, dastur ishlab chiquvchisi uchun hamma narsa oddiy va iplar uchun xavfsiz bo'lishi kerak.

Jarayon biznes mantig'i ushbu biznes jarayoniga alohida ta'sir ko'rsatadigan har bir tashqi hodisani qayta ishlaydi. Bunday hodisalar bo'lishi mumkin:

  • biznes-jarayon namunasini ishga tushirish;
  • biznes jarayonidagi faoliyat bilan bog'liq foydalanuvchi harakati;
  • biznes-jarayon namunasi obuna bo'lgan xabar yoki signalni olish;
  • biznes jarayoni instantsiyasi tomonidan belgilangan taymerning tugashi;
  • API orqali harakatni boshqarish (masalan, jarayonni to'xtatish).

Har bir bunday hodisa biznes jarayonining holatini o'zgartirishi mumkin: ba'zi harakatlar tugashi va boshqalar boshlanishi mumkin, doimiy xususiyatlarning qiymatlari o'zgarishi mumkin. Har qanday faoliyatni yopish quyidagi faoliyatlardan biri yoki bir nechtasini faollashtirishga olib kelishi mumkin. Ular, o'z navbatida, boshqa hodisalarni kutishni to'xtatishlari mumkin yoki agar ularga qo'shimcha ma'lumotlar kerak bo'lmasa, ular xuddi shu tranzaksiyani bajarishlari mumkin. Tranzaktsiyani yopishdan oldin, biznes jarayonining yangi holati ma'lumotlar bazasida saqlanadi, u erda keyingi tashqi hodisani kutadi.

Relyatsion ma'lumotlar bazasida saqlanadigan doimiy biznes-jarayon ma'lumotlari SELECT FOR UPDATE-dan foydalanganda juda qulay ishlov berish sinxronizatsiya nuqtasidir. Agar bitta tranzaksiya ma'lumotlar bazasidan biznes-jarayon holatini uni o'zgartirish uchun olishga muvaffaq bo'lsa, parallel ravishda boshqa hech qanday operatsiya boshqa o'zgarish uchun xuddi shunday holatni ololmaydi va birinchi operatsiya tugagandan so'ng, ikkinchisi. allaqachon o'zgartirilgan holatni olish kafolatlangan.

DBMS tomonidagi pessimistik qulflardan foydalanib, biz barcha kerakli talablarni bajaramiz ACID, shuningdek, ishlaydigan misollar sonini ko'paytirish orqali dasturni biznes mantig'i bilan o'lchash qobiliyatini saqlab qoling.

Biroq, pessimistik qulflar bizni boshi berk ko'chaga olib kelishi bilan tahdid qiladi, ya'ni SELECT FOR UPDATE biznes mantig'idagi ba'zi dahshatli holatlarda boshi berk ko'chaga tushib qolgan taqdirda hamon ba'zi oqilona kutish vaqti bilan cheklanishi kerak.

Yana bir muammo - biznes jarayonining boshlanishini sinxronlashtirish. Biznes-jarayon namunasi bo'lmasa-da, ma'lumotlar bazasida ham holat yo'q, shuning uchun tasvirlangan usul ishlamaydi. Agar siz ma'lum bir sohada biznes-jarayon namunasining o'ziga xosligini ta'minlashni istasangiz, u holda sizga jarayon sinfi va mos keladigan doira bilan bog'liq bo'lgan sinxronizatsiya ob'ektining bir turi kerak bo'ladi. Ushbu muammoni hal qilish uchun biz tashqi xizmat orqali URI formatidagi kalit tomonidan ko'rsatilgan o'zboshimchalik manbasini blokirovka qilish imkonini beruvchi boshqa qulflash mexanizmidan foydalanamiz.

Bizning misollarimizda InitialPlayer biznes jarayoni deklaratsiyani o'z ichiga oladi

uniqueConstraint = UniqueConstraints.singleton

Shuning uchun jurnalda tegishli kalitning qulfini olish va bo'shatish haqidagi xabarlar mavjud. Boshqa biznes jarayonlar uchun bunday xabarlar yo'q: uniqueConstraint o'rnatilmagan.

Doimiy holat bilan biznes jarayoni muammolari

Ba'zida doimiy holatga ega bo'lish nafaqat yordam beradi, balki haqiqatan ham rivojlanishga to'sqinlik qiladi.
Muammolar biznes mantig'iga va/yoki biznes jarayoni modeliga o'zgartirish kiritishingiz kerak bo'lganda boshlanadi. Bunday o'zgarishlar biznes jarayonlarining eski holatiga mos kelmaydi. Agar ma'lumotlar bazasida juda ko'p "jonli" misollar mavjud bo'lsa, unda mos kelmaydigan o'zgarishlar jBPM-dan foydalanishda biz tez-tez duch keladigan ko'plab muammolarni keltirib chiqarishi mumkin.

O'zgarishlarning chuqurligiga qarab, siz ikkita usulda harakat qilishingiz mumkin:

  1. eskisiga mos kelmaydigan o'zgartirishlar kiritmaslik uchun yangi biznes-jarayon turini yarating va yangi misollarni boshlashda uni eskisi o'rniga ishlating. Eski misollar "eski usulda" ishlashda davom etadi;
  2. biznes mantig'ini yangilashda biznes jarayonlarining doimiy holatini ko'chiring.

Birinchi usul sodda, ammo uning cheklovlari va kamchiliklari bor, masalan:

  • biznes-jarayonlarning ko'plab modellarida biznes mantiqining takrorlanishi, biznes mantiqi hajmining oshishi;
  • tez-tez yangi biznes mantig'iga bir zumda o'tish talab qilinadi (deyarli har doim integratsiya vazifalari nuqtai nazaridan);
  • ishlab chiquvchi qaysi nuqtada eskirgan modellarni o'chirish mumkinligini bilmaydi.

Amalda biz ikkala yondashuvdan ham foydalanamiz, lekin hayotimizni soddalashtirish uchun bir qator qarorlar qabul qildik:

  • ma'lumotlar bazasida biznes jarayonining doimiy holati osongina o'qiladigan va oson qayta ishlanadigan shaklda saqlanadi: JSON formatidagi satrda. Bu sizga dastur ichida ham, tashqarisida ham migratsiyalarni amalga oshirish imkonini beradi. Haddan tashqari holatlarda siz uni tutqichlar bilan ham sozlashingiz mumkin (ayniqsa, disk raskadrovka paytida ishlab chiqishda foydali);
  • integratsiya biznes mantig'i biznes jarayonlarining nomlaridan foydalanmaydi, shuning uchun istalgan vaqtda ishtirok etuvchi jarayonlardan birini amalga oshirishni yangi nom bilan, yangi nom bilan almashtirish mumkin (masalan, "InitialPlayerV2"). Bog'lanish xabarlar va signallarning nomlari orqali sodir bo'ladi;
  • jarayon modeli versiya raqamiga ega, agar biz ushbu modelga mos kelmaydigan o'zgarishlar kiritsak, biz uni oshiramiz va bu raqam jarayon misolining holati bilan birga saqlanadi;
  • jarayonning doimiy holati birinchi navbatda bazadan qulay ob'ekt modeliga o'qiladi, agar modelning versiya raqami o'zgargan bo'lsa, migratsiya protsedurasi ishlashi mumkin;
  • migratsiya protsedurasi biznes mantig'i yonida joylashtiriladi va ma'lumotlar bazasidan qayta tiklanganda biznes jarayonining har bir misoli uchun "dangasa" deb nomlanadi;
  • agar siz barcha jarayon misollarining holatini tez va sinxron tarzda ko'chirishingiz kerak bo'lsa, ko'proq klassik ma'lumotlar bazasini ko'chirish echimlaridan foydalaniladi, lekin u erda JSON bilan ishlashingiz kerak.

Menga biznes jarayonlari uchun boshqa ramka kerakmi?

Maqolada tasvirlangan echimlar hayotimizni sezilarli darajada soddalashtirishga, ilovalarni ishlab chiqish darajasida hal qilinadigan muammolar doirasini kengaytirishga va biznes mantig'ini mikroservislarga ajratish g'oyasini yanada jozibador qilishga imkon berdi. Buning uchun juda ko'p ishlar amalga oshirildi, biznes jarayonlari uchun juda "engil" ramka, shuningdek, keng ko'lamli amaliy vazifalar kontekstida aniqlangan muammolarni hal qilish uchun xizmat ko'rsatish komponentlari yaratildi. Biz ushbu natijalarni baham ko'rish, umumiy komponentlarning rivojlanishini bepul litsenziya ostida ochiq kirishga olib kelish istagimiz bor. Bu biroz kuch va vaqt talab qiladi. Bunday yechimlarga bo'lgan talabni tushunish biz uchun qo'shimcha rag'bat bo'lishi mumkin. Taklif etilayotgan maqolada ramkaning imkoniyatlariga juda kam e'tibor beriladi, ammo ularning ba'zilari keltirilgan misollardan ko'rinadi. Agar biz shunga qaramay o'z ramkamizni nashr qilsak, unga alohida maqola bag'ishlanadi. Ayni paytda, savolga javob berib, ozgina fikr-mulohaza qoldirsangiz, minnatdor bo'lamiz:

So'rovda faqat ro'yxatdan o'tgan foydalanuvchilar ishtirok etishlari mumkin. tizimga kirishiltimos.

Menga biznes jarayonlari uchun boshqa ramka kerakmi?

  • 18,8%Ha, men uzoq vaqtdan beri shunga o'xshash narsani qidiryapman.

  • 12,5%amalga oshirish haqida ko'proq ma'lumot olish qiziq, bu foydali bo'lishi mumkin2

  • 6,2%biz mavjud ramkalardan birini ishlatamiz, lekin biz uni almashtirish haqida o'ylaymiz1

  • 18,8%biz mavjud ramkalardan birini ishlatamiz, hamma narsa mos keladi3

  • 18,8%ramkasiz engish3

  • 25,0%o'zingizni yozing 4

16 foydalanuvchi ovoz berdi. 7 nafar foydalanuvchi betaraf qolgan.

Manba: www.habr.com

a Izoh qo'shish