BPM ոճի ինտեգրում

BPM ոճի ինտեգրում

բարեւ, Հաբր!

Մեր ընկերությունը մասնագիտացած է ERP դասի ծրագրային լուծումների մշակման մեջ, որոնցում առյուծի բաժինը զբաղեցնում են գործարքային համակարգերը հսկայական քանակությամբ բիզնես տրամաբանությամբ և աշխատանքային հոսքով a la EDMS: Մեր արտադրանքի ժամանակակից տարբերակները հիմնված են JavaEE տեխնոլոգիաների վրա, սակայն մենք նաև ակտիվորեն փորձարկում ենք միկրոսերվիսները: Նման լուծումների ամենախնդրահարույց ոլորտներից մեկը հարակից տիրույթների հետ կապված տարբեր ենթահամակարգերի ինտեգրումն է։ Ինտեգրման առաջադրանքները միշտ մեծ գլխացավանք են պատճառել մեզ՝ անկախ մեր օգտագործած ճարտարապետական ​​ոճերից, տեխնոլոգիական կույտերից և շրջանակներից, սակայն վերջերս առաջընթաց է գրանցվել նման խնդիրների լուծման գործում:

Ձեր ուշադրությանն արժանացած հոդվածում կխոսեմ NPO Krista-ի փորձի և ճարտարապետական ​​հետազոտությունների մասին նշված տարածքում: Մենք նաև կդիտարկենք ինտեգրման խնդրի պարզ լուծման օրինակ հավելված մշակողի տեսանկյունից և պարզելու, թե ինչ է թաքնված այս պարզության հետևում:

Հրաժարում պատասխանատվությունից

Հոդվածում նկարագրված ճարտարապետական ​​և տեխնիկական լուծումներն առաջարկվում են իմ կողմից՝ հիմնվելով անձնական փորձի վրա՝ կոնկրետ առաջադրանքների համատեքստում: Այս լուծումները չեն պնդում, որ ունիվերսալ են և կարող են օպտիմալ չլինել օգտագործման այլ պայմաններում:

Ի՞նչ կապ ունի BPM-ն դրա հետ:

Այս հարցին պատասխանելու համար պետք է մի փոքր խորանալ մեր լուծումների կիրառական խնդիրների առանձնահատկությունների մեջ։ Մեր սովորական գործարքային համակարգում բիզնես տրամաբանության հիմնական մասը տվյալների բազա մուտքագրելն է օգտատերերի միջերեսների միջոցով, ձեռքով և ավտոմատ կերպով ստուգել այդ տվյալները, դրանք փոխանցել որոշակի աշխատանքային հոսքով, հրապարակել դրանք այլ համակարգ / վերլուծական տվյալների բազա / արխիվ, ստեղծել հաշվետվություններ: Այսպիսով, հաճախորդների համար համակարգի հիմնական գործառույթը նրանց ներքին բիզնես գործընթացների ավտոմատացումն է:

Հարմարության համար մենք օգտագործում ենք «փաստաթուղթ» տերմինը հաղորդակցության մեջ որպես տվյալների հավաքածուի որոշակի վերացականություն, որը միավորված է ընդհանուր բանալիով, որին կարող է «կցվել» որոշակի աշխատանքային հոսք:
Բայց ինչ վերաբերում է ինտեգրացիոն տրամաբանությանը: Ի վերջո, ինտեգրման խնդիրը ստեղծվում է համակարգի ճարտարապետությամբ, որը «սղոցված» է մասերի ՈՉ հաճախորդի խնդրանքով, այլ բոլորովին այլ գործոնների ազդեցության տակ.

  • Կոնուեյի օրենքի ազդեցության տակ;
  • այլ ապրանքների համար նախկինում մշակված ենթահամակարգերի վերաօգտագործման արդյունքում.
  • ինչպես որոշել է ճարտարապետը՝ ելնելով ոչ ֆունկցիոնալ պահանջներից։

Մեծ գայթակղություն կա առանձնացնել ինտեգրացիոն տրամաբանությունը հիմնական աշխատանքային հոսքի բիզնես տրամաբանությունից, որպեսզի բիզնես տրամաբանությունը չաղտոտվի ինտեգրացիոն արտեֆակտներով և չփրկի հավելվածի մշակողին համակարգի ճարտարապետական ​​լանդշաֆտի առանձնահատկությունների մեջ խորամուխ լինելուց: Այս մոտեցումն ունի մի շարք առավելություններ, սակայն պրակտիկան ցույց է տալիս դրա անարդյունավետությունը.

  • Ինտեգրման խնդիրների լուծումը սովորաբար սահում է դեպի ամենապարզ տարբերակները համաժամանակյա զանգերի տեսքով՝ հիմնական աշխատանքային հոսքի իրականացման սահմանափակ ընդլայնման կետերի պատճառով (ավելին ՝ ստորև համաժամանակյա ինտեգրման թերությունների մասին);
  • ինտեգրման արտեֆակտները դեռևս ներթափանցում են հիմնական բիզնես տրամաբանությունը, երբ անհրաժեշտ է հետադարձ կապ այլ ենթահամակարգից.
  • հավելվածի մշակողն անտեսում է ինտեգրումը և կարող է հեշտությամբ կոտրել այն՝ փոխելով աշխատանքային հոսքը.
  • Համակարգը դադարում է լինել մեկ ամբողջություն օգտվողի տեսանկյունից, ենթահամակարգերի միջև «կարերը» նկատելի են դառնում, օգտատիրոջ ավելորդ գործողություններ են առաջանում, որոնք նախաձեռնում են տվյալների փոխանցումը մի ենթահամակարգից մյուսը:

Մեկ այլ մոտեցում է ինտեգրացիոն փոխազդեցությունները դիտարկել որպես հիմնական բիզնեսի տրամաբանության և աշխատանքային հոսքի անբաժանելի մաս: Որպեսզի հավելվածների մշակողների հմտությունների պահանջները չբարձրանան, նոր ինտեգրացիոն փոխազդեցությունների ստեղծումը պետք է կատարվի հեշտությամբ և բնականաբար՝ լուծում ընտրելու նվազագույն տարբերակներով: Սա ավելի դժվար է, քան թվում է. գործիքը պետք է բավականաչափ հզոր լինի, որպեսզի օգտագործողին տրամադրի իր օգտագործման համար անհրաժեշտ տարբերակների բազմազանությունը և միևնույն ժամանակ թույլ չտա, որ իրեն կրակեն ոտքի վրա: Կան բազմաթիվ հարցեր, որոնց ինժեները պետք է պատասխանի ինտեգրացիոն առաջադրանքների համատեքստում, բայց որոնց մասին հավելված մշակողը չպետք է մտածի իր ամենօրյա աշխատանքում. գործարքների սահմաններ, հետևողականություն, ատոմականություն, անվտանգություն, մասշտաբում, բեռ և ռեսուրսների բաշխում, երթուղիացում, մարշալավորում, Համատեքստերի տարածում և փոխարկում և այլն: Անհրաժեշտ է հավելվածների մշակողներին առաջարկել բավականին պարզ որոշումների ձևանմուշներ, որոնցում արդեն թաքնված են բոլոր նման հարցերի պատասխանները: Այս օրինաչափությունները պետք է բավականաչափ ապահով լինեն. բիզնեսի տրամաբանությունը շատ հաճախ փոխվում է, ինչը մեծացնում է սխալների ներմուծման ռիսկը, սխալների արժեքը պետք է մնա բավականին ցածր մակարդակի վրա:

Բայց այնուամենայնիվ, ի՞նչ կապ ունի BPM-ն դրա հետ։ Աշխատանքային հոսքի իրականացման բազմաթիվ տարբերակներ կան...
Իրոք, բիզնես գործընթացների մեկ այլ իրականացում շատ տարածված է մեր լուծումներում՝ անցումային վիճակի գծապատկերի դեկլարատիվ կարգավորմամբ և բիզնես տրամաբանությամբ կառավարիչներին անցումներին միացնելու միջոցով: Միևնույն ժամանակ, այն պետությունը, որը որոշում է «փաստաթղթի» ներկայիս դիրքը բիզնես գործընթացում, հենց «փաստաթղթի» հատկանիշն է։

BPM ոճի ինտեգրում
Այսպիսին է գործընթացը նախագծի սկզբում

Նման իրականացման հանրաճանաչությունը պայմանավորված է գծային բիզնես գործընթացների ստեղծման հարաբերական պարզությամբ և արագությամբ: Այնուամենայնիվ, քանի որ ծրագրային համակարգերը դառնում են ավելի բարդ, բիզնես գործընթացի ավտոմատացված մասը մեծանում և բարդանում է: Կարիք կա տարրալուծման, պրոցեսների մասերի վերաօգտագործման, ինչպես նաև ֆորքինգ գործընթացների, որպեսզի յուրաքանչյուր ճյուղ կատարվի զուգահեռ։ Նման պայմաններում գործիքը դառնում է անհարմար, և վիճակի անցման դիագրամը կորցնում է իր տեղեկատվական բովանդակությունը (ինտեգրացիոն փոխազդեցությունները ընդհանրապես չեն արտացոլվում դիագրամում):

BPM ոճի ինտեգրում
Ահա թե ինչ տեսք ունի գործընթացը պահանջների հստակեցման մի քանի կրկնություններից հետո

Այս իրավիճակից ելքը շարժիչի ինտեգրումն էր jBPM ամենաբարդ բիզնես գործընթացներով որոշ ապրանքների մեջ: Կարճաժամկետ հեռանկարում այս լուծումը որոշակի հաջողություն ունեցավ. հնարավոր եղավ իրականացնել բարդ բիզնես գործընթացներ՝ միաժամանակ պահպանելով բավականին տեղեկատվական և արդի դիագրամը նշումներում: BPMN2.

BPM ոճի ինտեգրում
Բարդ բիզնես գործընթացի մի փոքր մաս

Երկարաժամկետ հեռանկարում լուծումը չարդարացրեց ակնկալիքները. տեսողական գործիքների միջոցով բիզնես գործընթացների ստեղծման բարձր աշխատանքային ինտենսիվությունը թույլ չտվեց հասնել արտադրողականության ընդունելի ցուցանիշների, և գործիքն ինքնին դարձավ ծրագրավորողների շրջանում ամենաչսիրվածներից մեկը: Բողոքներ կային նաև շարժիչի ներքին կառուցվածքի վերաբերյալ, ինչը հանգեցրեց բազմաթիվ «կարկատանների» և «հենակների» ի հայտ գալուն։

jBPM-ի օգտագործման հիմնական դրական կողմը բիզնես գործընթացի օրինակով սեփական կայուն վիճակ ունենալու օգուտների և վնասների գիտակցումն էր: Մենք նաև տեսանք գործընթացային մոտեցման օգտագործման հնարավորությունը տարբեր հավելվածների միջև բարդ ինտեգրման արձանագրություններ իրականացնելու համար՝ օգտագործելով ասինխրոն փոխազդեցություններ ազդանշանների և հաղորդագրությունների միջոցով: Դրանում վճռորոշ դեր է խաղում համառ պետության առկայությունը։

Ելնելով վերը նշվածից՝ կարող ենք եզրակացնել. Գործընթացային մոտեցումը BPM ոճով մեզ թույլ է տալիս լուծել ավելի բարդ բիզնես գործընթացների ավտոմատացման առաջադրանքների լայն շրջանակ, ներդաշնակորեն տեղավորել ինտեգրացիոն գործողությունները այս գործընթացներում և պահպանել իրականացված գործընթացը համապատասխան նշումով տեսողականորեն ցուցադրելու ունակությունը:

Սինխրոն զանգերի թերությունները որպես ինտեգրման օրինակ

Սինխրոն ինտեգրումը վերաբերում է ամենապարզ արգելափակող զանգին: Մեկ ենթահամակարգը գործում է որպես սերվերի կողմ և ցուցադրում է API-ն ցանկալի մեթոդով: Մեկ այլ ենթահամակարգ հանդես է գալիս որպես հաճախորդի կողմ և ճիշտ ժամանակին զանգ է կատարում արդյունքի ակնկալիքով: Կախված համակարգի ճարտարապետությունից, հաճախորդի և սերվերի կողմերը կարող են տեղակայվել կամ նույն հավելվածում և գործընթացում, կամ տարբեր ծրագրերում: Երկրորդ դեպքում, դուք պետք է կիրառեք RPC-ի որոշակի ներդրում և տրամադրեք պարամետրերի և զանգի արդյունքի մարշալավորում:

BPM ոճի ինտեգրում

Նման ինտեգրման օրինաչափությունն ունի բավականին մեծ թերություններ, բայց այն շատ լայնորեն կիրառվում է գործնականում իր պարզության պատճառով: Իրականացման արագությունը գերում և ստիպում է կրկին ու կրկին կիրառել այն «վառվող» ժամկետների պայմաններում՝ լուծումը գրելով տեխնիկական պարտքի մեջ։ Բայց պատահում է նաև, որ անփորձ ծրագրավորողները դա օգտագործում են անգիտակցաբար՝ պարզապես չհասկանալով բացասական հետևանքները։

Ի հավելումն ենթահամակարգերի միացման առավել ակնհայտ աճին, ավելի քիչ ակնհայտ խնդիրներ կան գործարքների «տարածման» և «ձգման» հետ կապված։ Իսկապես, եթե բիզնեսի տրամաբանությունը որևէ փոփոխություն է կատարում, ապա գործարքներն անփոխարինելի են, և գործարքներն իրենց հերթին արգելափակում են կիրառական որոշակի ռեսուրսներ, որոնց վրա ազդում են այդ փոփոխությունները: Այսինքն, քանի դեռ մի ենթահամակարգը չի սպասում մյուսի պատասխանին, այն չի կարողանա ավարտել գործարքը և բաց թողնել կողպեքները: Սա զգալիորեն մեծացնում է մի շարք հետևանքների վտանգը.

  • համակարգի արձագանքումը կորել է, օգտվողները երկար են սպասում հարցումների պատասխաններին.
  • սերվերը սովորաբար դադարում է արձագանքել օգտատերերի խնդրանքներին` հորդառատ թելերի լողավազանի պատճառով. թելերի մեծ մասը «կանգնում է» գործարքի կողմից զբաղեցրած ռեսուրսի կողպեքի վրա.
  • փակուղիները սկսում են ի հայտ գալ. դրանց առաջացման հավանականությունը մեծապես կախված է գործարքների տևողությունից, բիզնես տրամաբանության քանակից և գործարքի մեջ ներգրավված կողպեքներից.
  • գործարքի ժամկետի ավարտի ժամկետի ավարտի սխալներ են հայտնվում.
  • սերվերը «ընկնում է» OutOfMemory-ի վրա, եթե առաջադրանքը պահանջում է մեծ քանակությամբ տվյալների մշակում և փոփոխություն, իսկ համաժամանակյա ինտեգրացիաների առկայությունը շատ դժվարացնում է մշակումը «ավելի թեթև» գործարքների բաժանելը:

Ճարտարապետական ​​տեսանկյունից ինտեգրման ընթացքում արգելափակող զանգերի օգտագործումը հանգեցնում է առանձին ենթահամակարգերի որակի վերահսկման կորստի. անհնար է ապահովել մեկ ենթահամակարգի որակի թիրախները մեկ այլ ենթահամակարգի որակի թիրախներից մեկուսացված: Եթե ​​ենթահամակարգերը մշակվում են տարբեր թիմերի կողմից, սա մեծ խնդիր է։

Իրավիճակն ավելի հետաքրքիր է դառնում, եթե ինտեգրվող ենթահամակարգերը տարբեր ծրագրերում են, և պետք է համաժամանակյա փոփոխություններ կատարվեն երկու կողմից: Ինչպե՞ս դարձնել այս փոփոխությունները գործարքային:

Եթե ​​փոփոխությունները կատարվում են առանձին գործարքներում, ապա պետք է ապահովվի բացառությունների կայուն մշակում և փոխհատուցում, և դա լիովին վերացնում է համաժամանակյա ինտեգրման հիմնական առավելությունը՝ պարզությունը:

Մտքի մեջ են նաև բաշխված գործարքները, բայց մենք դրանք չենք օգտագործում մեր լուծումներում. հուսալիությունը դժվար է ապահովել:

«Սագա»-ն՝ որպես գործարքների խնդրի լուծում

Միկրոծառայությունների աճող ժողովրդականության հետ մեկտեղ աճում է պահանջարկը Սագայի օրինաչափություն.

Այս օրինաչափությունը հիանալի լուծում է երկարատև գործարքների վերը նշված խնդիրները, ինչպես նաև ընդլայնում է համակարգի վիճակը բիզնես տրամաբանության կողմից կառավարելու հնարավորությունները. անհաջող գործարքից հետո փոխհատուցումը չի կարող համակարգը հետ բերել իր սկզբնական վիճակին, այլ ապահովել այլընտրանք տվյալների մշակման երթուղին. Այն նաև թույլ է տալիս չկրկնել տվյալների մշակման հաջողությամբ ավարտված քայլերը, երբ փորձում եք գործընթացը հասցնել «լավ» ավարտի:

Հետաքրքիր է, որ մոնոլիտ համակարգերում այս օրինաչափությունը նույնպես տեղին է, երբ խոսքը վերաբերում է թույլ զուգակցված ենթահամակարգերի ինտեգրմանը, և կան բացասական ազդեցություններ, որոնք առաջանում են երկարատև գործարքների և համապատասխան ռեսուրսների կողպեքների պատճառով:

Ինչ վերաբերում է BPM ոճով մեր բիզնես գործընթացներին, պարզվում է, որ սագաների իրագործումը շատ հեշտ է. Սագաների առանձին քայլերը կարող են սահմանվել որպես բիզնես գործընթացի գործունեությունը, և բիզնես գործընթացի կայուն վիճակը որոշում է. այլ բաներ, սագաների ներքին վիճակը: Այսինքն՝ մեզ որեւէ լրացուցիչ համակարգման մեխանիզմ պետք չէ։ Ձեզ անհրաժեշտ է ընդամենը հաղորդագրության բրոքեր՝ որպես փոխադրամիջոց «առնվազն մեկ անգամ» երաշխիքներով:

Բայց նման լուծումն ունի նաև իր «գինը».

  • բիզնեսի տրամաբանությունն ավելի բարդ է դառնում. պետք է փոխհատուցում մշակել.
  • անհրաժեշտ կլինի հրաժարվել ամբողջական հետևողականությունից, որը կարող է հատկապես զգայուն լինել մոնոլիտ համակարգերի համար.
  • ճարտարապետությունը մի փոքր ավելի բարդ է դառնում, հաղորդագրության միջնորդի լրացուցիչ կարիք կա.
  • կպահանջվեն լրացուցիչ մոնիտորինգի և կառավարման գործիքներ (չնայած ընդհանուր առմամբ դա նույնիսկ լավ է. համակարգի սպասարկման որակը կբարձրանա):

Մոնոլիտ համակարգերի համար «Sags»-ի օգտագործման հիմնավորումն այնքան էլ ակնհայտ չէ։ Միկրոծառայությունների և այլ 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;
  • Մուտքային հաղորդագրությունների և իրադարձությունների մշակողները պետք է աշխատեն նաև բիզնես գործընթացի վիճակը փոխելու գործարքի հետ.
  • հաղորդագրությունները, որոնք չեն կարողացել առաքվել սխալների պատճառով, պետք է պահվեն առանձին խանութում Դ.Լ.Ք. (Մեռած նամակների հերթ): Դա անելու համար մենք ստեղծեցինք առանձին հարթակ միկրոսերվիս, որը պահում է նման հաղորդագրությունները իր պահեստում, ինդեքսավորում դրանք ըստ ատրիբուտների (արագ խմբավորման և որոնման համար) և ցուցադրում է API-ը՝ դիտելու, նպատակակետ հասցեին վերաուղարկելու և հաղորդագրությունները ջնջելու համար: Համակարգի ադմինիստրատորները կարող են աշխատել այս ծառայության հետ իրենց վեբ ինտերֆեյսի միջոցով.
  • բրոքերի կարգավորումներում դուք պետք է կարգավորեք առաքման կրկնությունների և առաքումների միջև ուշացումների քանակը, որպեսզի նվազեցնեք հաղորդագրությունների DLQ մուտքի հավանականությունը (օպտիմալ պարամետրերը գրեթե անհնար է հաշվարկել, բայց դուք կարող եք գործել էմպիրիկորեն և կարգավորել դրանք ընթացքում: շահագործում);
  • DLQ խանութը պետք է շարունակական մոնիտորինգի ենթարկվի, և մոնիտորինգի համակարգը պետք է տեղեկացնի համակարգի ադմինիստրատորներին, որպեսզի նրանք կարողանան հնարավորինս արագ արձագանքել չհանձնված հաղորդագրությունների դեպքում: Սա կնվազեցնի ձախողման կամ բիզնես տրամաբանական սխալի «վնասի գոտին».
  • Ինտեգրման ավտոբուսը պետք է անտարբեր լինի հավելվածների ժամանակավոր բացակայության նկատմամբ. թեմայի բաժանորդագրությունները պետք է լինեն երկարակյաց, և հավելվածի տիրույթի անունը պետք է լինի եզակի, որպեսզի որևէ մեկը չփորձի մշակել իր հաղորդագրությունը հերթից հավելվածի բացակայության ժամանակ:

Բիզնես տրամաբանության թելի անվտանգության ապահովում

Բիզնես գործընթացի նույն օրինակը կարող է միանգամից մի քանի հաղորդագրություն և իրադարձություն ստանալ, որոնց մշակումը կսկսվի զուգահեռ։ Միևնույն ժամանակ, հավելված մշակողի համար ամեն ինչ պետք է լինի պարզ և անվտանգ:

Գործընթացի բիզնես տրամաբանությունը մշակում է յուրաքանչյուր արտաքին իրադարձություն, որն ազդում է այս բիզնես գործընթացի վրա առանձին: Այս իրադարձությունները կարող են լինել.

  • բիզնես գործընթացի օրինակի գործարկում;
  • օգտատիրոջ գործողություն՝ կապված բիզնես գործընթացի գործունեության հետ.
  • հաղորդագրության կամ ազդանշանի ստացում, որին բաժանորդագրված է բիզնես գործընթացի օրինակ.
  • բիզնես գործընթացի օրինակով սահմանված ժամաչափի ժամկետի ավարտը.
  • վերահսկել գործողությունը API-ի միջոցով (օրինակ՝ գործընթացի ընդհատում):

Յուրաքանչյուր նման իրադարձություն կարող է փոխել բիզնես գործընթացի օրինակի վիճակը. որոշ գործողություններ կարող են ավարտվել, իսկ մյուսները սկսել, կայուն հատկությունների արժեքները կարող են փոխվել: Ցանկացած գործունեության փակումը կարող է հանգեցնել հետևյալ գործողություններից մեկի կամ մի քանիսի ակտիվացմանը: Նրանք, իրենց հերթին, կարող են դադարել սպասել այլ իրադարձությունների կամ, եթե որևէ լրացուցիչ տվյալների կարիք չունեն, կարող են կատարել նույն գործարքը: Գործարքի փակումից առաջ բիզնես գործընթացի նոր վիճակը պահվում է տվյալների բազայում, որտեղ այն կսպասի հաջորդ արտաքին իրադարձությանը:

Հարաբերական տվյալների բազայում պահվող մշտական ​​բիզնես գործընթացների տվյալները շատ հարմար մշակման համաժամացման կետ են SELECT FOR UPDATE-ի օգտագործման ժամանակ: Եթե ​​մեկ գործարքի հաջողվել է տվյալների բազայից ստանալ բիզնես գործընթացի վիճակը՝ այն փոխելու համար, ապա ոչ մի այլ գործարք զուգահեռաբար չի կարող նույն վիճակը ստանալ մեկ այլ փոփոխության դեպքում, իսկ առաջին գործարքի ավարտից հետո երկրորդը. երաշխավորված ստանալ արդեն փոխված վիճակը։

Օգտագործելով հոռետեսական կողպեքներ DBMS-ի կողմից, մենք կատարում ենք բոլոր անհրաժեշտ պահանջները ACID, ինչպես նաև պահպանել հավելվածը բիզնես տրամաբանությամբ մեծացնելու հնարավորությունը՝ ավելացնելով գործարկվող օրինակների քանակը:

Այնուամենայնիվ, հոռետեսական կողպեքները մեզ սպառնում են փակուղիներով, ինչը նշանակում է, որ 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

Добавить комментарий