BPM стилинин интеграциясы

BPM стилинин интеграциясы

Hello, Хабр!

Биздин компания ERP классындагы программалык чечимдерди иштеп чыгууга адистешкен, анын негизги үлүшүн бизнес логикасы жана EDMS документ агымы менен транзакциялык системалар ээлейт. Биздин өнүмдөрдүн учурдагы версиялары JavaEE технологияларына негизделген, бирок биз микросервистерди активдүү түрдө сынап жатабыз. Мындай чечимдердин эң көйгөйлүү багыттарынын бири чектеш домендерге тиешелүү ар кандай подсистемаларды интеграциялоо болуп саналат. Биз колдонгон архитектуралык стилдерге, технологиялык стектерге жана алкактарга карабастан, интеграциялык көйгөйлөр бизге ар дайым чоң баш ооруну берип келген, бирок акыркы убакта мындай көйгөйлөрдү чечүүдө жылыштар байкалууда.

Мен сиздин көңүлүңүзгө алып келе жаткан макалада мен Кристанын дайындалган аймактагы тажрыйбасы жана архитектуралык изилдөөлөрү жөнүндө сөз кылам. Биз ошондой эле тиркемени иштеп чыгуучунун көз карашынан интеграциялык көйгөйдү чечүүнүн жөнөкөй мисалын карап чыгабыз жана бул жөнөкөйлүктүн артында эмне катылганын билебиз.

Жоопкерчиликтен баш тартуу

Макалада баяндалган архитектуралык жана техникалык чечимдер конкреттүү милдеттердин контекстинде жеке тажрыйбанын негизинде мен тарабынан сунушталган. Бул чечимдер универсалдуу деп ырастабайт жана колдонуунун башка шарттарында оптималдуу болбой калышы мүмкүн.

BPM муну менен кандай байланышы бар?

Бул суроого жооп берүү үчүн, биз чечимдерибиздин колдонулуучу көйгөйлөрүнүн өзгөчөлүктөрүнө бир аз тереңирээк киришибиз керек. Биздин типтүү транзакциялык системабыздагы бизнес логикасынын негизги бөлүгү – бул маалыматтарды колдонуучу интерфейстери аркылуу маалымат базасына киргизүү, бул маалыматтарды кол менен жана автоматташтырылган текшерүү, аны кандайдыр бир иш процесси аркылуу өткөрүү, аны башка системага/аналитикалык маалымат базасына/архивге жарыялоо жана отчетторду түзүү. . Ошентип, кардарлар үчүн системанын негизги милдети алардын ички бизнес-процесстерин автоматташтыруу болуп саналат.

Ыңгайлуу болушу үчүн, биз байланышта "документ" терминин белгилүү бир иш процессин "байланыштыра турган" жалпы ачкыч менен бириктирилген маалыматтар жыйындысынын кандайдыр бир абстракциясы катары колдонобуз.
Бирок интеграциялык логика жөнүндө эмне айтууга болот? Анткени, интеграциялык тапшырма системанын архитектурасы тарабынан түзүлөт, ал кардардын талабы боюнча ЭМЕС, бирок такыр башка факторлордун таасири астында бөлүктөргө "кесилген":

  • Конвей мыйзамына ылайык;
  • башка продукциялар үчүн мурда иштелип чыккан подсистемаларды кайра колдонуунун натыйжасында;
  • функционалдык эмес талаптардын негизинде архитектордун кароосу боюнча.

Бизнес логикасын интеграциялык артефакттар менен булгап албаш үчүн жана тиркемени иштеп чыгуучуну системанын архитектуралык ландшафтынын спецификасын изилдөө зарылчылыгынан бошотуу үчүн интеграциялык логиканы негизги иш процессинин бизнес-логикасынан бөлүүгө чоң азгырык бар. Бул ыкма бир катар артыкчылыктарга ээ, бирок практика анын натыйжасыздыгын көрсөтүп турат:

  • интеграциялык көйгөйлөрдү чечүү, адатта, негизги иш процессин ишке ашырууда чектелген кеңейтүү чекиттеринен улам синхрондук чалуулар түрүндөгү эң жөнөкөй варианттарга кайра түшөт (синхрондук интеграциянын кемчиликтери төмөндө каралат);
  • башка подсистемадан пикир талап кылынганда интеграциялык артефакттар дагы эле негизги бизнес логикасына кирет;
  • тиркемени иштеп чыгуучу интеграцияны этибарга албайт жана жумуш процессин өзгөртүү менен аны оңой эле бузуп алат;
  • система колдонуучунун көз карашы боюнча бир бүтүн болуу болбой калат, подсистемалардын ортосундагы "тигүүлөр" байкалып калат жана ашыкча колдонуучунун операциялары пайда болуп, маалыматтарды бир подсистемадан экинчисине өткөрүп берүүнү демилгелейт.

Дагы бир ыкма - интеграциялык өз ара аракеттенүүнү негизги бизнес логикасынын жана иш процессинин ажырагыс бөлүгү катары кароо. Тиркемени иштеп чыгуучунун квалификациясынын көтөрүлүшүнө жол бербөө үчүн, жаңы интеграциялык өз ара аракеттенүүнү түзүү чечимди тандоо үчүн минималдуу мүмкүнчүлүк менен жеңил жана оңой болушу керек. Бул көрүнгөндөн да кыйыныраак: курал колдонуучуга аны "бутка атууга" жол бербестен, аны колдонуу үчүн керектүү ар түрдүү варианттарды камсыз кылуу үчүн жетиштүү күчтүү болушу керек. Инженер интеграциялык тапшырмалардын контекстинде жооп бериши керек болгон көптөгөн суроолор бар, бирок тиркемени иштеп чыгуучу өзүнүн күнүмдүк ишинде ойлонбошу керек: транзакциянын чек аралары, ырааттуулугу, атомдуулугу, коопсуздук, масштабдоо, жүктөө жана ресурстарды бөлүштүрүү, маршруттоо, маршалдаштыруу, жайылтуу жана которуштуруу контексттери ж.б.. Тиркемелерди иштеп чыгуучуларга бардык ушул сыяктуу суроолорго жооптор катылган жетиштүү жөнөкөй чечим шаблондорун сунуштоо зарыл. Бул шаблондор абдан коопсуз болушу керек: бизнес логикасы бат-баттан өзгөрүп турат, бул каталарды киргизүү тобокелдигин жогорулатат, каталардын баасы кыйла төмөн деңгээлде калышы керек.

Бирок BPMдин буга кандай тиешеси бар? Жумуш процессин ишке ашыруу үчүн көптөгөн варианттар бар...
Чынында эле, бизнес-процесстердин дагы бир ишке ашырылышы биздин чечимдерибизде абдан популярдуу - мамлекеттик өткөөл диаграмманын декларативдик аныктамасы жана өткөөлдөрдүн бизнес логикасы менен иштетүүчүлөрдүн байланышы аркылуу. Бул учурда бизнес процессиндеги "документтин" учурдагы абалын аныктаган мамлекет "документтин" өзүнүн атрибуту болуп саналат.

BPM стилинин интеграциясы
Долбоордун башталышында процесс ушундай көрүнөт

Бул ишке ашыруунун популярдуулугу сызыктуу бизнес процесстерин түзүүнүн салыштырмалуу жөнөкөйлүгү жана ылдамдыгы менен шартталган. Бирок, программалык камсыздоо тутумдары дайыма татаалдашкан сайын, бизнес процессинин автоматташтырылган бөлүгү чоңоюп, татаалдашып баратат. Ар бир бутак параллелдүү аткарылышы үчүн процесстердин бөлүктөрүн декомпозицияга, кайра колдонууга, ошондой эле тармакташуу процесстерине муктаждык бар. Мындай шарттарда инструмент ыңгайсыз болуп, абалдын өтүү диаграммасы өзүнүн маалыматтык мазмунун жоготот (интегралдык өз ара аракеттенүү диаграммада такыр чагылдырылбайт).

BPM стилинин интеграциясы
Бир нече жолу талаптарды тактоодон кийин процесс ушундай көрүнөт.

Бул абалдан чыгуунун жолу кыймылдаткычтын интеграциясы болгон jBPM эң татаал бизнес процесстери менен кээ бир продуктыларга. Кыска мөөнөттүн ичинде бул чечим кандайдыр бир ийгиликке ээ болду: белгилердеги кыйла маалыматтык жана актуалдуу диаграмманы сактоо менен татаал бизнес процесстерин ишке ашырууга мүмкүн болду. BPMN2.

BPM стилинин интеграциясы
Татаал бизнес процессинин кичинекей бөлүгү

Узак мөөнөттүү келечекте бул чечим үмүттөрдү актаган жок: визуалдык инструменттер аркылуу бизнес-процесстерди түзүүнүн жогорку эмгек сыйымдуулугу алгылыктуу өндүрүмдүүлүк көрсөткүчтөрүнө жетишүүгө мүмкүндүк берген жок, ал эми курал өзү иштеп чыгуучулардын эң жактырбагандарынын бири болуп калды. Кыймылдаткычтын ички түзүлүшү боюнча да даттануулар болгон, бул көптөгөн "жамалардын" жана "балдактардын" пайда болушуна алып келген.

JBPMди колдонуунун негизги позитивдүү жагы бизнес процессинин инстанциясынын өзүнүн туруктуу абалынын пайдалары жана зыяндары жөнүндө кабардар болгон. Биз ошондой эле сигналдар жана билдирүүлөр аркылуу асинхрондук өз ара аракеттенүүнү колдонуу менен ар кандай тиркемелер арасында татаал интеграциялык протоколдорду ишке ашыруу үчүн процесстик ыкманы колдонуу мүмкүнчүлүгүн көрдүк. Мында туруктуу мамлекеттин болушу чечүүчү роль ойнойт.

Жогоруда айтылгандардын негизинде, биз жыйынтык чыгарсак болот: BPM стилиндеги процесстик мамиле барган сайын татаалдашып бара жаткан бизнес-процесстерди автоматташтыруу боюнча милдеттердин кеңири спектрин чечүүгө, интеграциялык иш-аракеттерди бул процесстерге шайкеш келтирүү жана ишке ашырылган процессти ылайыктуу белгилер менен визуалдык көрсөтүү мүмкүнчүлүгүн сактоого мүмкүндүк берет.

Интеграция үлгүсү катары синхрондук чалуулардын кемчиликтери

Синхрондуу интеграция эң жөнөкөй бөгөттөө чакырыгын билдирет. Бир подсистема сервер тарабынын ролун аткарат жана APIди талап кылынган ыкма менен көрсөтөт. Башка подсистема кардар тараптын ролун аткарат жана керектүү убакта чалуу жасап, натыйжаны күтөт. Системанын архитектурасына жараша кардар жана сервер тараптары бир эле тиркемеде жана процессте, же башкаларында жайгашышы мүмкүн. Экинчи учурда, сиз RPC ишке ашырууну колдонуп, параметрлерди жана чалуунун жыйынтыгын маршалдаштырууну камсыз кылышыңыз керек.

BPM стилинин интеграциясы

Бул интеграция үлгүсүнүн жетишерлик чоң кемчиликтери бар, бирок ал жөнөкөйлүгүнөн улам иш жүзүндө абдан кеңири колдонулат. Ишке ашыруу ылдамдыгы өзүнө тартып турат жана сизди аны кайра-кайра колдонууга мажбурлайт, бул чечимди техникалык карыз катары жазуу. Бирок, ошондой эле тажрыйбасыз иштеп чыгуучулар жөн гана терс кесепеттерин түшүнбөй, аны аң-сезимсиз колдонушат.

Подсистеманын туташуулугунун эң айкын өсүшүнөн тышкары, транзакциялардын "өсүү" жана "созулушу" менен азыраак айкын көйгөйлөр бар. Чынында эле, эгерде бизнес логикасы кандайдыр бир өзгөртүүлөрдү киргизсе, анда транзакциялардан качуу мүмкүн эмес, ал эми транзакциялар, өз кезегинде, бул өзгөртүүлөр таасир эткен айрым колдонмо ресурстарын бөгөттөйт. Башкача айтканда, бир подсистема экинчисинен жооп күтмөйүнчө, транзакцияны бүтүрүп, кулпуларды алып сала албайт. Бул ар кандай таасирлердин пайда болуу коркунучун олуттуу жогорулатат:

  • Системанын жооп берүү жөндөмдүүлүгү жоголуп, колдонуучулар суроолорго жооп алуу үчүн көпкө күтүшөт;
  • сервер толуп кеткен жип бассейнинен улам колдонуучунун суроо-талаптарына жооп бербейт: жиптердин көпчүлүгү транзакция ээлеген ресурста кулпуланган;
  • Туюктар пайда боло баштайт: алардын пайда болуу ыктымалдыгы транзакциялардын узактыгына, бизнес логикасынын көлөмүнө жана бүтүмгө тартылган кулпулардан көз каранды;
  • транзакциянын күтүүсүз каталары пайда болот;
  • эгер тапшырма чоң көлөмдөгү маалыматтарды иштеп чыгууну жана өзгөртүүнү талап кылса, сервер OutOfMemory менен "иштей албай калат" жана синхрондуу интеграциянын болушу кайра иштетүүнү "жеңилирээк" транзакцияларга бөлүүнү абдан кыйындатат.

Архитектуралык көз караштан алганда, интеграциялоо учурунда бөгөттөөчү чалууларды колдонуу айрым подсистемалардын сапатын көзөмөлдөөнү жоготууга алып келет: бир подсистеманын максаттуу сапат индикаторлорун башка подсистеманын сапаттык көрсөткүчтөрүнөн бөлүп камсыз кылуу мүмкүн эмес. Эгерде подсистемаларды ар кандай командалар иштеп чыгышса, бул чоң көйгөй.

Интеграциялануучу подсистемалар ар кандай тиркемелерде болсо жана эки тарапка тең синхрондуу өзгөртүүлөрдү киргизүү керек болсо, нерселер ого бетер кызыктуу болот. Бул өзгөртүүлөрдүн транзакциялуулугун кантип камсыз кылуу керек?

Эгерде өзгөртүүлөр өзүнчө транзакцияларда жасалса, анда сиз ишенимдүү өзгөчөлүктү башкарууну жана компенсацияны камсыз кылышыңыз керек болот жана бул синхрондук интеграциянын негизги пайдасын - жөнөкөйлүгүн толугу менен жок кылат.

Бөлүштүрүлгөн транзакциялар да эске түшөт, бирок биз аларды чечимдерибизде колдонбойбуз: ишенимдүүлүктү камсыз кылуу кыйын.

транзакция маселесин чечүү катары "Сага"

Микросервистердин популярдуулугу менен, суроо-талап Saga Pattern.

Бул үлгү жогоруда айтылган узак транзакциялардын көйгөйлөрүн эң сонун чечет, ошондой эле системанын абалын бизнес логикасы жагынан башкаруу мүмкүнчүлүктөрүн кеңейтет: ишке ашпай калган транзакциядан кийин компенсация системаны баштапкы абалына кайтарып бербейт, бирок альтернативалык маалымат иштетүү жолу. Бул ошондой эле процессти "жакшы" аяктоо үчүн аракет кылып жатканда ийгиликтүү аяктаган маалыматтарды иштетүү кадамдарын кайталоодон качууга мүмкүндүк берет.

Кызыктуусу, монолиттүү системаларда бул схема эркин туташкан подсистемалардын интеграциясына келгенде да актуалдуу жана узакка созулган транзакциялардан келип чыккан терс таасирлер жана тиешелүү ресурстук кулпулар байкалат.

BPM стилиндеги биздин бизнес-процесстерибизге карата, "Сагаларды" ишке ашыруу абдан оңой болуп чыкты: "Сага" жеке кадамдары бизнес-процесстин ичиндеги иш-аракеттер катары көрсөтүлүшү мүмкүн, ошондой эле бизнес-процесстин туруктуу абалы да «Саганын» ички абалын аныктайт. Башкача айтканда, биз эч кандай кошумча координациялоо механизмин талап кылбайбыз. Сизге керек болгон нерсе - транспорт катары "жок дегенде бир жолу" кепилдиктерди колдогон билдирүү брокери.

Бирок бул чечимдин да өзүнүн "баасы" бар:

  • бизнес логикасы татаалдашат: компенсацияны иштеп чыгуу керек;
  • монолиттүү системалар үчүн өзгөчө сезгич болушу мүмкүн болгон толук ырааттуулуктан баш тартуу керек болот;
  • Архитектура бир аз татаалдашып, билдирүү брокерине кошумча муктаждык пайда болот;
  • кошумча мониторинг жана башкаруу инструменттери талап кылынат (бирок бул жалпысынан жакшы: системалык тейлөөнүн сапаты жогорулайт).

Монолиттик системалар үчүн "Sag" колдонуунун негизи анчалык деле ачык эмес. Микросервистер жана башка SOA үчүн, болжол менен брокер бар жана долбоордун башталышында толук ырааттуулук курмандыкка чалынат, бул үлгүнү колдонуунун пайдасы кемчиликтерден бир топ жогору болушу мүмкүн, айрыкча бизнес логикасында ыңгайлуу API бар болсо. деңгээл.

Микросервистерде бизнес логикасын инкапсуляциялоо

Микросервистер менен эксперимент жүргүзө баштаганда, акылга сыярлык суроо пайда болду: домендик маалыматтардын туруктуулугун камсыз кылган кызматка карата домендик бизнес логикасын кайда жайгаштыруу керек?

Ар кандай BPMS архитектурасын карап жатканда, бизнес логикасын туруктуулуктан бөлүү акылга сыярлык көрүнүшү мүмкүн: домендик бизнес логикасын аткаруу үчүн чөйрөнү жана контейнерди түзгөн платформа жана доменден көз карандысыз микросервистердин катмарын түзүү жана домен маалыматтарынын туруктуулугун иштеп чыгуу. абдан жөнөкөй жана жеңил микросервистердин өзүнчө катмары. Бул учурда бизнес-процесстер туруктуулук катмарынын кызматтарын башкарууну жүзөгө ашырат.

BPM стилинин интеграциясы

Бул ыкманын абдан чоң артыкчылыгы бар: сиз каалагандай платформанын функционалдуулугун жогорулата аласыз, андан платформа микросервистеринин тиешелүү катмары гана "май" болуп калат. Каалаган домендеги бизнес процесстер платформанын жаңы функцияларын жаңырары менен дароо колдоно алышат.

Кененирээк изилдөө бул ыкманын олуттуу кемчиликтерин аныктады:

  • бир эле учурда көптөгөн домендердин бизнес логикасын ишке ашырган платформа кызматы бир эле ката катары чоң тобокелдиктерди алып келет. Бизнес логикасын бат-баттан өзгөртүү системанын бүтүндөй бузулушуна алып келген каталардын коркунучун жогорулатат;
  • аткаруу маселелери: бизнес логикасы тар жана жай интерфейс аркылуу анын маалыматтары менен иштейт:
    • маалыматтар дагы бир жолу ирээтке келтирилет жана тармактык стек аркылуу айдалат;
    • домен кызматы көбүнчө кызматтын тышкы API деңгээлинде суроо-талаптарды параметрлештирүү мүмкүнчүлүгүнүн жетишсиздигинен улам бизнес логикасын иштетүү үчүн талап кылынгандан көбүрөөк маалыматтарды берет;
    • бизнес логикасынын бир нече көз карандысыз бөлүктөрүн кайра иштетүү үчүн бир эле маалыматты кайра талап кыла алат (бул көйгөйдү маалыматтарды кэш кылган сеанс компоненттерин кошуу менен жеңилдетүүгө болот, бирок бул архитектураны ого бетер татаалдантат жана маалыматтардын актуалдуулугун жана кэшти жараксыз деп табуу көйгөйлөрүн жаратат);
  • транзакция көйгөйлөрү:
    • платформа кызматы тарабынан сакталган туруктуу абалы менен бизнес-процесстер домен маалыматтарына туура келбейт жана бул маселени чечүүнүн оңой жолдору жок;
    • бүтүмдөн тышкары домен маалыматтарын бөгөт коюу: эгерде домендик бизнес логикасы учурдагы маалыматтардын тууралыгын биринчи жолу текшергенден кийин өзгөртүүлөрдү киргизүү керек болсо, анда иштетилген маалыматтардын атаандаштык өзгөрүү мүмкүнчүлүгүн жокко чыгаруу керек. Тышкы маалыматтарды бөгөттөө көйгөйдү чечүүгө жардам берет, бирок мындай чечим кошумча тобокелдиктерди алып келет жана системанын жалпы ишенимдүүлүгүн төмөндөтөт;
  • жаңыртууда кошумча кыйынчылыктар: кээ бир учурларда туруктуулук кызматы жана бизнес логикасы синхрондуу же катуу ырааттуулукта жаңыртылууга муктаж.

Акыр-аягы, биз негиздерине кайтууга туура келди: домен маалыматтарын жана домен бизнесинин логикасын бир микросервиске инкапсуляциялоо. Бул ыкма микросервисти системанын ажырагыс бөлүгү катары кабыл алууну жөнөкөйлөтөт жана жогоруда айтылган көйгөйлөрдү жаратпайт. Бул да бекер берилбейт:

  • API стандартташтыруу бизнес-логика (атап айтканда, бизнес-процесстердин бир бөлүгү катары колдонуучу иш-аракеттерди камсыз кылуу үчүн) жана API платформа кызматтары менен өз ара аракеттенүү үчүн талап кылынат; API өзгөрүүлөрүнө, алдыга жана артка шайкештикке кылдат көңүл бурууну талап кылат;
  • ар бир ушундай микросервистин бир бөлүгү катары бизнес логиканын иштешин камсыз кылуу үчүн кошумча иштөө убактысынын китепканаларын кошуу зарыл, бул мындай китепканаларга жаңы талаптарды жаратат: жеңилдик жана өтмө көз карандылыктын минималдуу болушу;
  • бизнес логикасын иштеп чыгуучулар китепкананын версияларын көзөмөлдөшү керек: эгерде микросервис көптөн бери аягына чыга элек болсо, анда ал китепканалардын эскирген версиясын камтышы мүмкүн. Бул жаңы функцияны кошуу үчүн күтүлбөгөн тоскоолдук болушу мүмкүн жана версиялардын ортосунда туура келбеген өзгөрүүлөр болсо, мындай кызматтын эски бизнес логикасын китепканалардын жаңы версияларына көчүрүү талап кылынышы мүмкүн.

BPM стилинин интеграциясы

Мындай архитектурада платформа кызматтарынын катмары да бар, бирок бул катмар мындан ары домендик бизнес логикасын аткаруу үчүн контейнерди түзбөй, көмөкчү “платформа” функцияларын камсыз кылган анын чөйрөсүн гана түзөт. Мындай катмар домендик микросервистердин жеңил мүнөзүн сактоо үчүн гана эмес, башкарууну борборлоштуруу үчүн да керек.

Мисалы, бизнес процесстериндеги колдонуучу иш-аракеттери тапшырмаларды жаратат. Бирок, тапшырмалар менен иштөөдө колдонуучу жалпы тизмедеги бардык домендердин тапшырмаларын көрүшү керек, демек, домендик бизнес логикасынан тазаланган тиешелүү платформа тапшырманы каттоо кызматы болушу керек. Мындай контекстте бизнес логикасын инкапсуляциялоо абдан көйгөйлүү жана бул архитектуранын дагы бир компромисси.

Тиркеме иштеп чыгуучунун көзү менен бизнес процесстерин интеграциялоо

Жогоруда айтылгандай, тиркемени иштеп чыгуучу жакшы өнүгүү өндүрүмдүүлүгүнө ишенүү үчүн, бир нече тиркемелердин өз ара аракеттенүүсүн ишке ашыруунун техникалык жана инженердик өзгөчөлүктөрүнөн абстракцияланышы керек.

Келгиле, макала үчүн атайын ойлоп табылган бир кыйла татаал интеграциялык маселени чечүүгө аракет кылалы. Бул үч тиркемени камтыган "оюн" тапшырмасы болот, мында алардын ар бири белгилүү бир домен атын аныктайт: "app1", "app2", "app3".

Ар бир тиркеменин ичинде интеграциялык автобус аркылуу “топ ойной” баштаган бизнес процесстер ишке киргизилет. "Топ" деген аталыштагы билдирүүлөр шардын ролун аткарат.

Оюндун эрежеси:

  • биринчи оюнчу демилгечи болуп саналат. Ал оюнга башка оюнчуларды чакырат, оюнду баштап, каалаган убакта бүтүрө алат;
  • башка оюнчулар оюнга катышкандыктарын билдиришет, бири-бирин жана биринчи оюнчуну "таанышат";
  • топту алгандан кийин оюнчу башка катышуучу оюнчуну тандап, ага топту берет. Берүүлөрдүн жалпы саны эсептелет;
  • Ар бир оюнчуда "энергия" бар, ал ошол оюнчунун топтун ар бир пасы менен азаят. Энергиясы түгөнүп калганда, оюнчу отставкага кеткенин жарыялап оюндан чыгат;
  • эгерде оюнчу жалгыз калса, ал дароо кетип жатканын жарыялайт;
  • Бардык оюнчулар жок болгондо, биринчи оюнчу оюн бүттү деп жарыялайт. Эгер ал оюндан эрте чыгып кетсе, аны аягына чыгаруу үчүн оюнду ээрчип калат.

Бул көйгөйдү чечүү үчүн мен бизнес процесстерибиз үчүн DSLди колдоном, бул бизге Котлиндеги логиканы компакттык менен, минималдуу пластинка менен сүрөттөөгө мүмкүндүк берет.

Биринчи оюнчунун (оюндун демилгечиси) бизнес процесси 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 нотасын бир аз жөнөкөйлөштүрдүм):

BPM стилинин интеграциясы

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

Диаграмма:

BPM стилинин интеграциясы

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

Келгиле, тестти өткөрүп, журналды карап көрөлү:

консол чыгаруу

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

Мунун бардыгынан биз бир нече маанилүү тыянактарды чыгара алабыз:

  • керектүү куралдар менен тиркемени иштеп чыгуучулар бизнес логикасын үзгүлтүккө учуратпастан тиркемелердин ортосунда интеграциялык өз ара аракеттенүүнү түзө алышат;
  • инженердик компетенцияларды талап кылган интеграциялык тапшырманын татаалдыгы, эгерде бул алгач алкактын архитектурасына киргизилсе, ал алкагында жашырылышы мүмкүн. Проблеманын кыйынчылыгын жашырууга болбойт, андыктан коддогу татаал маселенин чечилиши ушундай болот;
  • Интеграциялоонун логикасын иштеп чыгууда интеграциянын бардык катышуучуларынын абалынын өзгөрүүсүнүн акыры ырааттуулугун жана сызыктуу эместигин эске алуу зарыл. Бул бизди логиканы тышкы окуялардын пайда болуу тартибине сезимсиз кылуу үчүн татаалдаштырууга мажбурлайт. Биздин мисалда, оюнчу оюндан чыкканын жарыялагандан кийин оюнга катышууга аргасыз болот: анын чыгуусу тууралуу маалымат жеткенге чейин жана бардык катышуучулар тарабынан иштелип чыкканга чейин башка оюнчулар ага топту берүүнү уланта беришет. Бул логика оюндун эрежелерине ылайык келбейт жана тандалган архитектуранын алкагында компромисстик чечим болуп саналат.

Андан кийин, биз чечимибиздин ар кандай татаалдыктары, компромисстер жана башка пункттар жөнүндө сүйлөшөбүз.

Бардык билдирүүлөр бир кезекте турат

Бардык интеграцияланган тиркемелер тышкы брокер, билдирүүлөр үчүн бир BPMQueue жана сигналдар (окуялар) үчүн бир BPMTopic темасы түрүндө берилген бир интеграциялык автобус менен иштейт. Бардык билдирүүлөрдү бир кезекке коюунун өзү компромисс. Бизнес-логика деңгээлинде сиз системанын структурасына өзгөртүүлөрдү киргизбестен, сиз каалагандай жаңы билдирүү түрлөрүн киргизе аласыз. Бул олуттуу жөнөкөйлөштүрүү, бирок бул биздин типтүү милдеттерибиздин контекстинде бизге анчалык деле маанилүү көрүнгөн эмес, белгилүү бир тобокелдиктерди камтыйт.

BPM стилинин интеграциясы

Бирок, бул жерде бир кылдаттык бар: ар бир тиркеме өзүнүн доменинин аталышы боюнча кире бериштеги кезекте турган "өз" билдирүүлөрүн чыпкалайт. Домен сигналдарда да көрсөтүлүшү мүмкүн, эгерде сиз сигналдын "көрүнүү чөйрөсүн" бир эле тиркеме менен чектешиңиз керек болсо. Бул автобустун өткөрүү жөндөмдүүлүгүн жогорулатуу керек, бирок бизнес логикасы азыр домендик аталыштар менен иштеши керек: билдирүүлөрдү дароо үчүн - милдеттүү, сигналдар үчүн - каалайт.

Интеграция автобусунун ишенимдүүлүгүн камсыз кылуу

Ишенимдүүлүк бир нече пункттардан турат:

  • Тандалган билдирүү брокери архитектуранын критикалык компоненти жана бузулуунун бир чекити болуп саналат: ал жетишээрлик деңгээлде катага чыдамдуу болушу керек. Сиз жакшы колдоо жана чоң коомчулук менен, убакыт сынагынан өткөн ишке ашырууларды гана колдонушуңуз керек;
  • билдирүү брокеринин жогорку жеткиликтүүлүгүн камсыз кылуу зарыл, ал үчүн ал интегралдык тиркемелерден физикалык жактан бөлүнүшү керек (колдонулган бизнес логикасы бар тиркемелердин жогорку жеткиликтүүлүгүн камсыз кылуу алда канча татаал жана кымбат);
  • брокер "жок дегенде бир жолу" жеткирүү кепилдиктерин берүүгө милдеттүү. Бул интеграциялык автобустун ишенимдүү иштеши үчүн милдеттүү талап. "Так бир жолу" деңгээлдеги кепилдиктердин кереги жок: бизнес-процесстер, эреже катары, билдирүүлөрдүн же окуялардын кайталанып келишине сезгич эмес жана бул маанилүү болгон атайын тапшырмаларда бизнеске кошумча текшерүүлөрдү кошуу оңой. бир кыйла "кымбат" кепилдиктерди дайыма колдонууга караганда логика;
  • билдирүүлөрдү жана сигналдарды жөнөтүү бизнес процесстеринин абалынын жана домен маалыматтарынын өзгөрүшү менен жалпы бүтүмгө тартылышы керек. артыкчылыктуу параметр үлгүсүн колдонуу болмок Транзакциянын чыгыш кутусу, бирок ал маалымат базасында кошумча таблица жана кайталоочу талап кылынат. JEE тиркемелеринде бул жергиликтүү JTA менеджерин колдонуу менен жөнөкөйлөштүрүлүшү мүмкүн, бирок тандалган брокерге туташууда иштөөгө жөндөмдүү болушу керек. XA;
  • келген билдирүүлөрдү жана окуяларды иштетүүчүлөр бизнес-процесстин абалын өзгөрткөн транзакция менен да иштеши керек: эгерде мындай транзакция артка кайтарылса, анда билдирүүнү алуу жокко чыгарылууга тийиш;
  • каталардан улам жеткирилбей калган билдирүүлөр өзүнчө сактагычта сакталышы керек D.L.Q. (Өлгөн каттын кезеги). Бул үчүн биз өзүнчө платформа микросервисин түздүк, ал мындай билдирүүлөрдү өзүнүн сактагычында сактайт, аларды атрибуттары боюнча индекстейт (тез топтоо жана издөө үчүн) жана көрүү, көздөгөн дарекке кайра жөнөтүү жана билдирүүлөрдү жок кылуу үчүн API ачат. Системалык администраторлор бул кызмат менен веб интерфейси аркылуу иштей алышат;
  • брокердин жөндөөлөрүндө, билдирүүлөрдүн DLQга түшүү ыктымалдыгын азайтуу үчүн жеткирүүлөрдүн кайталанмаларынын санын жана жеткирүүлөрдүн ортосундагы кечигүүлөрдү тууралашыңыз керек (оптималдуу параметрлерди эсептөө дээрлик мүмкүн эмес, бирок сиз эмпирикалык түрдө иш алып барып, операция учурунда аларды тууралай аласыз. );
  • DLQ дүкөнү үзгүлтүксүз көзөмөлдөнүшү керек жана мониторинг системасы жеткирилбеген билдирүүлөр пайда болгондо, алар мүмкүн болушунча тезирээк жооп бериши үчүн системанын администраторлорун эскертиши керек. Бул ийгиликсиздиктин же бизнес логикалык катасынын "жабыр тарткан аймагын" азайтат;
  • интеграциялык автобус тиркемелердин убактылуу жоктугуна сезимтал болбошу керек: темага жазылуулар туруктуу болушу керек жана колдонмонун домендик аталышы уникалдуу болушу керек, ошондуктан тиркеме жок болгон учурда башка бирөө анын билдирүүлөрүн иштеп чыгууга аракет кылбашы керек. кезек.

Бизнес логиканын жип коопсуздугун камсыз кылуу

Бизнес процессинин бир эле инстанциясы бир эле учурда бир нече билдирүүлөрдү жана окуяларды кабыл алышы мүмкүн, аларды иштетүү параллелдүү башталат. Ошол эле учурда, тиркемени иштеп чыгуучу үчүн, баары жөнөкөй жана жип коопсуз болушу керек.

Процесстин бизнес логикасы ошол бизнес процессине өзүнчө таасир этүүчү ар бир тышкы окуяны иштетет. Мындай окуялар болушу мүмкүн:

  • бизнес процессинин инстанциясын ишке киргизүү;
  • бизнес процессинин ичиндеги активдүүлүккө байланыштуу колдонуучунун аракети;
  • бизнес процессинин инстанциясы жазылган билдирүүнү же сигналды алуу;
  • бизнес процессинин инстанциясы тарабынан коюлган таймерди ишке киргизүү;
  • API аркылуу аракетти көзөмөлдөө (мисалы, процессти үзгүлтүккө учуратуу).

Мындай ар бир окуя бизнес процессинин абалын өзгөртө алат: кээ бир иш-аракеттер аяктап, башкалары башталышы мүмкүн, ал эми туруктуу касиеттердин баалуулуктары өзгөрүшү мүмкүн. Ар кандай иш-аракеттерди жабуу төмөнкү аракеттердин биринин же бир нечесинин жанданышына алып келиши мүмкүн. Алар, өз кезегинде, башка окуяларды күтүүнү токтотушу мүмкүн же кошумча маалымат керек болбосо, ошол эле транзакцияда бүтүрө алышат. Транзакцияны жабуунун алдында бизнес-процесстин жаңы абалы маалымат базасында сакталат, анда ал кийинки тышкы окуянын болушун күтөт.

Реляциялык маалымат базасында сакталган туруктуу бизнес процессинин маалыматтары, эгерде сиз SELECT FOR UPDATE колдонсоңуз, кайра иштетүүнү синхрондоштуруу үчүн абдан ыңгайлуу чекит болуп саналат. Эгерде бир транзакция бизнес-процесстин абалын аны өзгөртүү үчүн базадан ала алган болсо, анда параллелдүү башка эч бир транзакция башка өзгөртүү үчүн ошол эле абалды ала албайт, ал эми биринчи транзакция аяктагандан кийин, экинчиси мурунтан эле өзгөргөн абалын алууга кепилдик берилет.

МБС жагындагы пессимисттик кулпуларды колдонуу менен биз бардык зарыл талаптарды аткарабыз КЫЧКЫЛ, ошондой эле иштеп жаткан инстанциялардын санын көбөйтүү менен колдонмону бизнес логикасы менен масштабдоо мүмкүнчүлүгүн сактап каласыз.

Бирок, пессимисттик кулпулар бизди туңгуюктар менен коркутат, демек, SELECT FOR UPDATE бизнес логикасында кандайдыр бир олуттуу учурларда туюкка учураган учурда кандайдыр бир акылга сыярлык күтүү менен чектелиши керек.

Дагы бир көйгөй - бизнес процессинин башталышын синхрондоштуруу. Бизнес процессинин инстанциясы жок болсо да, маалымат базасында абал жок, ошондуктан сүрөттөлгөн ыкма иштебейт. Эгер бизнес процессинин инстанциясынын белгилүү бир чөйрөдө уникалдуулугун камсыз кылуу керек болсо, анда сизге процесс классы жана тиешелүү чөйрө менен байланышкан синхрондоштуруу объектинин кандайдыр бир түрү керек болот. Бул көйгөйдү чечүү үчүн, биз тышкы кызмат аркылуу URI форматындагы ачкыч менен көрсөтүлгөн ыктыярдуу ресурста кулпу алууга мүмкүндүк берген башка кулпулоо механизмин колдонобуз.

Биздин мисалдарыбызда InitialPlayer бизнес процесси декларацияны камтыйт

uniqueConstraint = UniqueConstraints.singleton

Демек, журналда тиешелүү ачкычтын кулпусун алуу жана бошотуу жөнүндө билдирүүлөр бар. Башка бизнес процесстер үчүн мындай билдирүүлөр жок: uniqueConstraint коюлган эмес.

Туруктуу абал менен бизнес-процесстердин көйгөйлөрү

Кээде туруктуу абалга ээ болуу бир гана жардам бербестен, өнүгүүгө чындап эле тоскоол болот.
Көйгөйлөр бизнес логикасына жана/же бизнес процессинин моделине өзгөртүүлөрдү киргизүү керек болгондо башталат. Мындай өзгөрүүлөрдүн баары эле бизнес процесстеринин эски абалы менен шайкеш келе бербейт. Эгерде маалымат базасында көптөгөн жандуу инстанциялар бар болсо, анда бири-бирине дал келбеген өзгөртүүлөр jBPMди колдонууда биз көп кездешкен көптөгөн кыйынчылыктарды жаратышы мүмкүн.

Өзгөртүүлөрдүн тереңдигине жараша, сиз эки жол менен иш-аракет кыла аласыз:

  1. эскиге туура келбеген өзгөртүүлөрдү киргизбөө үчүн жаңы бизнес процессинин түрүн түзүп, жаңы инстанцияларды ишке киргизүүдө аны эскинин ордуна колдонуңуз. Эски нускалар "мурдагыдай" иштей берет;
  2. бизнес логикасын жаңыртууда бизнес процесстеринин туруктуу абалын көчүрүү.

Биринчи жол жөнөкөй, бирок анын чектөөлөрү жана кемчиликтери бар, мисалы:

  • бизнес-логиканын көптөгөн бизнес-процесс моделдеринде кайталанышы, бизнес логикасынын көлөмүн көбөйтүү;
  • Көбүнчө жаңы бизнес логикасына дароо өтүү талап кылынат (интеграция милдеттери боюнча - дээрлик дайыма);
  • иштеп чыгуучу эскирген моделдер жок кылынышы мүмкүн экенин билбейт.

Иш жүзүндө биз эки ыкманы тең колдонобуз, бирок жашообузду жеңилдетүү үчүн бир катар чечимдерди кабыл алдык:

  • Маалымат базасында бизнес процессинин туруктуу абалы оңой окула турган жана оңой иштетилген формада сакталат: JSON форматындагы сапта. Бул миграцияны колдонмонун ичинде да, сырттан да аткарууга мүмкүндүк берет. Акыркы чара катары, сиз аны кол менен оңдой аласыз (айрыкча мүчүлүштүктөрдү оңдоо учурунда иштеп чыгууда пайдалуу);
  • интеграциялык бизнес логикасы бизнес-процесстердин аталыштарын колдонбойт, ошондуктан каалаган убакта катышуучу процесстердин биринин ишке ашырылышын жаңы ат менен жаңысына алмаштырууга болот (мисалы, “InitialPlayerV2”). Байланыш билдирүү жана сигнал аттары аркылуу ишке ашат;
  • процесс моделинде версия номери бар, эгерде биз бул моделге туура келбеген өзгөртүүлөрдү киргизсек, аны көбөйтөбүз жана бул сан процесс инстанциясынын абалы менен бирге сакталат;
  • процесстин туруктуу абалы алгач маалымат базасынан ыңгайлуу объект моделине окулат, эгерде моделдин версиясынын номери өзгөргөн болсо, миграция процедурасы аны менен иштей алат;
  • миграциялык жол-жобо бизнес логиканын жанына жайгаштырылат жана аны маалымат базасынан калыбына келтирүү учурунда бизнес-процесстин ар бир инстанциясы үчүн “жалкоо” деп аталат;
  • бардык процесс инстанцияларынын абалын тез жана синхрондуу көчүрүү керек болсо, көбүрөөк классикалык маалымат базасын көчүрүү чечимдери колдонулат, бирок сиз JSON менен иштешиңиз керек.

Сизге бизнес процесстери үчүн башка негиз керекпи?

Макалада сүрөттөлгөн чечимдер жашообузду кыйла жөнөкөйлөтүүгө, тиркемени иштеп чыгуу деңгээлинде чечилген маселелердин чөйрөсүн кеңейтүүгө жана бизнес логикасын микросервистерге бөлүү идеясын жагымдуураак кылууга мүмкүндүк берди. Буга жетишүү үчүн көп иштер жасалды, бизнес-процесстер үчүн өтө “жеңил” негиз түзүлдү, ошондой эле колдонмо көйгөйлөрүнүн кеңири спектринин контекстинде аныкталган көйгөйлөрдү чечүү үчүн сервистик компоненттер түзүлдү. Биз бул натыйжаларды бөлүшүүнү каалайбыз жана жалпы компоненттерди иштеп чыгууну акысыз лицензиянын алкагында ачык жеткиликтүүлүктү камсыз кылууну каалайбыз. Бул бир аз күч-аракетти жана убакытты талап кылат. Мындай чечимдерге болгон суроо-талапты түшүнүү биз үчүн кошумча стимул болушу мүмкүн. Сунушталган макалада алкактын өзүнүн мүмкүнчүлүктөрүнө өтө аз көңүл бурулган, бирок алардын айрымдары келтирилген мисалдардан көрүнүп турат. Эгерде биз алкакты жарыяласак, ага өзүнчө макала арналат. Ал ортодо, суроого жооп берип, бир аз пикир калтырсаңыз, ыраазы болобуз:

Сурамжылоого катталган колдонуучулар гана катыша алышат. Кирүү, өтүнөмүн.

Сизге бизнес процесстери үчүн башка негиз керекпи?

  • 18,8%Ооба, мен көптөн бери ушундай нерсени издеп жүрөм

  • 12,5%Мен сиздин ишке ашырууңуз жөнүндө көбүрөөк билгим келет, бул пайдалуу болушу мүмкүн2

  • 6,2%Биз бар алкактардын бирин колдонобуз, бирок алмаштырууну ойлонуп жатабыз1

  • 18,8%Биз бар алкактардын бирин колдонобуз, баары жакшы3

  • 18,8%биз негизсиз башкарып жатабыз3

  • 25,0%өзүңүздү жазыңыз4

16 колдонуучу добуш берди. 7 колдонуучу добуш берүүдөн баш тартты.

Source: www.habr.com

Комментарий кошуу