BPM stíl samþætting

BPM stíl samþætting

Habr!

Fyrirtækið okkar sérhæfir sig í þróun hugbúnaðarlausna í ERP-flokki, þar sem stærstur hluti þeirra er upptekinn af viðskiptakerfum með gríðarmikilli viðskiptarökfræði og skjalaflæði a la EDMS. Núverandi útgáfur af vörum okkar eru byggðar á JavaEE tækni, en við erum líka að gera virkar tilraunir með örþjónustur. Eitt af erfiðustu sviðum slíkra lausna er samþætting ýmissa undirkerfa sem tilheyra aðliggjandi lénum. Samþættingarvandamál hafa alltaf valdið okkur miklum höfuðverk, burtséð frá byggingarstílum, tæknistöflum og umgjörðum sem við notum, en undanfarið hafa orðið framfarir í að leysa slík vandamál.

Í greininni sem ég vek athygli á mun ég tala um reynsluna og byggingarrannsóknir sem NPO Krista hefur á afmörkuðu svæði. Við munum einnig skoða dæmi um einfalda lausn á samþættingarvanda frá sjónarhóli forritara og finna út hvað leynist á bak við þennan einfaldleika.

Fyrirvari

Þær byggingarfræðilegu og tæknilegu lausnir sem lýst er í greininni eru lagðar til af mér byggðar á persónulegri reynslu í samhengi við ákveðin verkefni. Þessar lausnir segjast ekki vera alhliða og eru kannski ekki ákjósanlegar við aðrar notkunarskilyrði.

Hvað hefur BPM með það að gera?

Til að svara þessari spurningu þurfum við að kafa aðeins dýpra í sérstöðu beittra vandamála lausna okkar. Meginhluti viðskiptarökfræðinnar í dæmigerðu viðskiptakerfi okkar er að slá inn gögn í gagnagrunninn í gegnum notendaviðmót, handvirk og sjálfvirk sannprófun á þessum gögnum, framkvæma þau í gegnum eitthvað verkflæði, birta þau í annað kerfi / greiningargagnagrunn / skjalasafn, búa til skýrslur . Þannig er lykilhlutverk kerfisins fyrir viðskiptavini sjálfvirkni innri viðskiptaferla þeirra.

Til hægðarauka notum við hugtakið „skjal“ í samskiptum sem einhverja útdrætti úr safni gagna sameinað með sameiginlegum lykli sem hægt er að „tengja ákveðið verkflæði við“.
En hvað með samþættingarrökfræði? Þegar öllu er á botninn hvolft er samþættingarverkefnið myndað af arkitektúr kerfisins, sem er „skorið“ í hluta EKKI að beiðni viðskiptavinarins, heldur undir áhrifum gjörólíkra þátta:

  • háð lögum Conway;
  • sem afleiðing af endurnotkun undirkerfa sem áður voru þróuð fyrir aðrar vörur;
  • að mati arkitekts, byggt á óvirkum kröfum.

Það er mikil freisting að aðskilja samþættingarrökfræði frá viðskiptarökfræði aðalverkflæðisins, til að menga ekki viðskiptarökfræðina með samþættingargripum og losa forritara frá þörfinni á að kafa ofan í sérkenni byggingarlandslags kerfisins. Þessi aðferð hefur ýmsa kosti, en æfingin sýnir árangursleysi hennar:

  • að leysa samþættingarvandamál fellur venjulega aftur til einföldustu valmöguleikanna í formi samstilltra símtala vegna takmarkaðra framlengingarpunkta í útfærslu aðalverkflæðisins (ókostir samþættingar eru ræddir hér að neðan);
  • samþættingargripir komast enn í gegnum rökfræði kjarnaviðskipta þegar endurgjöf frá öðru undirkerfi er krafist;
  • forritarinn hunsar samþættinguna og getur auðveldlega brotið hana með því að breyta verkflæðinu;
  • kerfið hættir að vera ein heild frá sjónarhóli notandans, „saumar“ á milli undirkerfa verða áberandi og óþarfur notendaaðgerðir koma fram sem hefja flutning gagna frá einu undirkerfi til annars.

Önnur nálgun er að líta á samþættingarsamskipti sem óaðskiljanlegan hluta af rökfræði og verkflæði kjarnaviðskipta. Til að koma í veg fyrir að hæfni forritara til að vaxa upp úr öllu valdi ætti að búa til ný samþættingarsamskipti að vera auðveld og áreynslulaus, með lágmarks valkostum til að velja lausn. Þetta er erfiðara að gera en það virðist: tólið verður að vera nógu öflugt til að veita notandanum nauðsynlega fjölbreytni valkosta fyrir notkun þess, án þess að leyfa honum að „skota sig í fótinn“. Það eru margar spurningar sem verkfræðingur verður að svara í samhengi við samþættingarverkefni, en sem forritari ætti ekki að hugsa um í daglegu starfi sínu: viðskiptamörk, samræmi, atómvirkni, öryggi, mælikvarði, álag og dreifingu auðlinda, leið, raðgreining, dreifingu og skiptasamhengi o.s.frv. Nauðsynlegt er að bjóða forriturum frekar einföld lausnasniðmát þar sem svörin við öllum slíkum spurningum eru þegar falin. Þessi sniðmát verða að vera nokkuð örugg: viðskiptarökfræði breytist mjög oft, sem eykur hættuna á að innleiða villur, kostnaður við villur verður að vera á frekar lágu stigi.

En hvað hefur BPM með það að gera? Það eru margir möguleikar til að innleiða verkflæði...
Reyndar er önnur útfærsla á viðskiptaferlum mjög vinsæl í lausnum okkar - í gegnum yfirlýsandi skilgreiningu á ástandsbreytingarmynd og tengingu meðhöndlunarmanna við viðskiptarökfræði fyrir umskipti. Í þessu tilviki er ríkið sem ákvarðar núverandi stöðu „skjalsins“ í viðskiptaferlinu eiginleiki „skjalsins“ sjálfs.

BPM stíl samþætting
Svona lítur ferlið út við upphaf verkefnis

Vinsældir þessarar útfærslu eru vegna tiltölulega einfaldleikans og hraðans við að búa til línuleg viðskiptaferli. Hins vegar, eftir því sem hugbúnaðarkerfi verða stöðugt flóknari, vex sjálfvirki hluti viðskiptaferlisins og verður flóknari. Þörf er á niðurbroti, endurnýtingu hluta ferla, auk greiningarferla þannig að hver grein sé framkvæmd samhliða. Við slíkar aðstæður verður tólið óþægilegt og ástandsbreytingarmyndin missir upplýsingainnihald sitt (samþættingarsamskipti endurspeglast alls ekki í skýringarmyndinni).

BPM stíl samþætting
Svona lítur ferlið út eftir nokkrar endurtekningar af kröfuskýringu.

Leiðin út úr þessum aðstæðum var samþætting vélarinnar jBPM inn í sumar vörur með flóknustu viðskiptaferlum. Til skamms tíma náði þessi lausn nokkurn árangur: það varð mögulegt að innleiða flókna viðskiptaferla á meðan viðhaldið var nokkuð upplýsandi og viðeigandi skýringarmynd í nótnaskriftinni BPMN2.

BPM stíl samþætting
Lítill hluti af flóknu viðskiptaferli

Til lengri tíma litið stóð lausnin ekki undir væntingum: mikil vinnuafl við að búa til viðskiptaferla með sjónrænum verkfærum leyfði ekki að ná ásættanlegum framleiðnivísum og tólið sjálft varð eitt það óþokkaðasta meðal þróunaraðila. Einnig var kvartað yfir innri uppbyggingu vélarinnar, sem leiddi til þess að margir „plástrar“ og „hækjur“ komu fram.

Helsti jákvæði þátturinn við að nota jBPM var meðvitundin um ávinninginn og skaðann af því að hafa eigin viðvarandi ástand viðskiptaferlisins. Við sáum einnig möguleika á að nota ferlinálgun til að innleiða flóknar samþættingarreglur milli mismunandi forrita með því að nota ósamstillt samskipti í gegnum merki og skilaboð. Tilvist viðvarandi ástands gegnir mikilvægu hlutverki í þessu.

Byggt á ofangreindu getum við ályktað: Ferlisnálgunin í BPM stílnum gerir okkur kleift að leysa margs konar verkefni til að gera sífellt flóknari viðskiptaferla sjálfvirkan, samræma samþættingaraðgerðir inn í þessa ferla og viðhalda getu til að sýna útfært ferli sjónrænt í viðeigandi nótnaskrift.

Ókostir við samstillt símtöl sem samþættingarmynstur

Samstillt samþætting vísar til einfaldasta lokunarsímtalsins. Eitt undirkerfi virkar sem miðlarahlið og afhjúpar API með nauðsynlegri aðferð. Annað undirkerfi virkar sem viðskiptavinur og hringir á réttum tíma og bíður eftir niðurstöðunni. Það fer eftir kerfisarkitektúrnum, biðlara- og miðlarahliðin geta verið staðsett annað hvort í sama forritinu og ferlinu, eða í mismunandi. Í öðru tilvikinu þarftu að beita RPC útfærslu og útvega röðun á breytum og niðurstöðu símtalsins.

BPM stíl samþætting

Þetta samþættingarmynstur hefur nokkuð mikið af ókostum, en það er mjög mikið notað í reynd vegna einfaldleika þess. Hraði innleiðingar heillar og neyðir þig til að nota það aftur og aftur í ljósi bráða fresti, skrá lausnina sem tæknilega skuld. En það gerist líka að óreyndir verktaki nota það ómeðvitað, einfaldlega gera sér ekki grein fyrir neikvæðu afleiðingunum.

Til viðbótar við augljósustu aukningu á tengingu undirkerfis, eru líka minna augljós vandamál með "vaxandi" og "teygja" viðskipti. Reyndar, ef viðskiptarökfræðin gerir einhverjar breytingar, þá er ekki hægt að forðast viðskipti og viðskipti, aftur á móti, loka fyrir ákveðnar umsóknarauðlindir sem þessar breytingar hafa áhrif á. Það er, þar til eitt undirkerfi bíður eftir svari frá hinu, mun það ekki geta klárað viðskiptin og fjarlægt læsinguna. Þetta eykur verulega hættuna á ýmsum áhrifum:

  • Svörun kerfisins tapast, notendur bíða lengi eftir svörum við beiðnum;
  • þjónninn hættir almennt að svara beiðnum notenda vegna yfirfulls þráðasafns: Meirihluti þráðanna er læstur á auðlind sem er upptekin af færslu;
  • Deadlocks byrja að birtast: líkurnar á viðburðum þeirra veltur mjög á lengd viðskipta, magn viðskiptarökfræði og læsingar sem taka þátt í viðskiptunum;
  • villur í tímamörkum við færslu birtast;
  • þjónninn „bilar“ með OutOfMemory ef verkefnið krefst vinnslu og breytinga á miklu magni af gögnum og tilvist samstilltra samþættinga gerir það mjög erfitt að skipta vinnslu í „léttari“ færslur.

Frá sjónarhóli byggingarlistar leiðir notkun á símtölum til að loka meðan á samþættingu stendur til taps á stjórn á gæðum einstakra undirkerfa: það er ómögulegt að tryggja markgæðavísa eins undirkerfis í einangrun frá gæðavísum annars undirkerfis. Ef undirkerfi eru þróuð af mismunandi teymum er þetta mikið vandamál.

Hlutirnir verða enn áhugaverðari ef undirkerfin sem verið er að samþætta eru í mismunandi forritum og þú þarft að gera samstilltar breytingar á báðum hliðum. Hvernig á að tryggja viðskiptahæfni þessara breytinga?

Ef breytingar eru gerðar í aðskildum viðskiptum, þá þarftu að veita áreiðanlega meðhöndlun undantekninga og bætur, og þetta útilokar algjörlega helsta ávinninginn af samstilltum samþættingum - einfaldleika.

Dreifð viðskipti koma líka upp í hugann, en við notum þau ekki í lausnum okkar: það er erfitt að tryggja áreiðanleika.

„Saga“ sem lausn á viðskiptavandanum

Með vaxandi vinsældum örþjónustu er eftirspurn eftir Saga mynstur.

Þetta mynstur leysir fullkomlega ofangreind vandamál langra viðskipta og eykur einnig getu til að stjórna ástandi kerfisins frá hlið viðskiptarökfræðinnar: bætur eftir misheppnaðar viðskipti mega ekki snúa kerfinu aftur í upprunalegt ástand, en veita önnur gagnavinnsluleið. Þetta gerir þér einnig kleift að forðast að endurtaka gagnavinnsluþrep með góðum árangri þegar reynt er að koma ferlinu á „góðan“ endi.

Athyglisvert er að í einlitum kerfum á þetta mynstur einnig við þegar kemur að samþættingu lauslega tengdra undirkerfa og neikvæð áhrif af völdum langvinnra viðskipta og samsvarandi auðlindalása koma fram.

Í tengslum við viðskiptaferla okkar í BPM stíl, reynist það vera mjög auðvelt að innleiða „Sagas“: einstök skref „Saga“ geta verið tilgreind sem starfsemi innan viðskiptaferlisins, og viðvarandi ástand viðskiptaferlisins er einnig ákvarðar innra ástand „Sögunnar“. Það er, við krefjumst ekki neins viðbótarsamhæfingarkerfis. Allt sem þú þarft er skilaboðamiðlari sem styður „að minnsta kosti einu sinni“ ábyrgðir sem flutning.

En þessi lausn hefur líka sitt eigið „verð“:

  • viðskiptarökfræðin verður flóknari: það þarf að vinna bætur;
  • það verður að hætta að fullu samræmi, sem getur verið sérstaklega viðkvæmt fyrir einhæf kerfi;
  • Arkitektúrinn verður aðeins flóknari og aukin þörf fyrir skilaboðamiðlara birtist;
  • þörf er á frekari eftirlits- og stjórnunarverkfærum (þótt þetta sé almennt gott: gæði kerfisþjónustunnar munu aukast).

Fyrir einhæf kerfi er réttlætingin fyrir því að nota "Sag" ekki svo augljós. Fyrir örþjónustur og aðra SOA, þar sem líklegast er að miðlari er þegar til staðar og fullri samkvæmni er fórnað við upphaf verkefnisins, geta ávinningurinn af því að nota þetta mynstur vegið verulega þyngra en ókostirnir, sérstaklega ef það er þægilegt API í viðskiptarökfræðinni. stigi.

Inniheldur viðskiptarökfræði í örþjónustu

Þegar við byrjuðum að gera tilraunir með örþjónustur vaknaði eðlileg spurning: hvar á að staðsetja viðskiptarökfræði lénsins í tengslum við þjónustuna sem tryggir viðvarandi lénsgögn?

Þegar arkitektúr ýmissa BPMS er skoðaður kann að virðast sanngjarnt að aðskilja viðskiptarökfræði frá þrautseigju: búa til lag af vettvangi og lénsóháðum örþjónustum sem mynda umhverfi og ílát til að framkvæma lénsviðskiptarökfræði, og hanna þrautseigju lénsgagna sem sérstakt lag af mjög einföldum og léttum örþjónustum. Viðskiptaferlar í þessu tilfelli framkvæma skipulagningu á þjónustu þrautseigjulagsins.

BPM stíl samþætting

Þessi nálgun hefur mjög stóran kost: þú getur aukið virkni vettvangsins eins mikið og þú vilt og aðeins samsvarandi lag af örþjónustu vettvangs verður „feit“ af þessu. Viðskiptaferlar frá hvaða léni sem er geta strax notað nýja virkni vettvangsins um leið og hún er uppfærð.

Nánari rannsókn leiddi í ljós verulega ókosti þessarar aðferðar:

  • vettvangsþjónusta sem framkvæmir viðskiptarök margra léna í einu felur í sér mikla áhættu sem einn bilunarpunkt. Tíðar breytingar á viðskiptarökfræði auka hættuna á villum sem leiða til bilana í kerfinu;
  • frammistöðuvandamál: viðskiptarökfræði vinnur með gögnum sínum í gegnum þröngt og hægt viðmót:
    • gögnunum verður aftur raðað og dælt í gegnum netstaflann;
    • lénsþjónusta mun oft veita fleiri gögn en þarf til að viðskiptarökfræði geti unnið úr vegna ófullnægjandi getu til að stilla beiðnir um færibreytur á stigi ytra API þjónustunnar;
    • nokkrir sjálfstæðir hlutar viðskiptarökfræði geta endurtekið beðið um sömu gögnin til vinnslu (þetta vandamál er hægt að draga úr með því að bæta við lotuhlutum sem vista gögn, en þetta flækir arkitektúrinn enn frekar og skapar vandamál sem tengjast gögnum og ógildingu skyndiminni);
  • viðskiptavandamál:
    • viðskiptaferli með viðvarandi ástandi, sem er geymt af vettvangsþjónustu, eru í ósamræmi við lénsgögn og það eru engar auðveldar leiðir til að leysa þetta vandamál;
    • að setja lénsgögn fyrir utan viðskiptin: ef viðskiptarökfræði lénsins þarf að gera breytingar eftir að hafa fyrst athugað réttmæti núverandi gagna, er nauðsynlegt að útiloka möguleikann á samkeppnisbreytingum á unnum gögnum. Ytri gagnablokkun getur hjálpað til við að leysa vandamálið, en slík lausn hefur í för með sér viðbótaráhættu og dregur úr heildaráreiðanleika kerfisins;
  • viðbótarerfiðleikar við uppfærslu: í sumum tilfellum þarf að uppfæra þrautseigjuþjónustuna og viðskiptarökfræðina samstillt eða í ströngri röð.

Á endanum urðum við að fara aftur í grunnatriðin: safna lénsgögnum og lénsrökfræði í eina örþjónustu. Þessi nálgun einfaldar skynjun á smáþjónustu sem óaðskiljanlegur hluti kerfisins og gefur ekki tilefni til ofangreindra vandamála. Þetta er heldur ekki gefið ókeypis:

  • API stöðlun er nauðsynleg fyrir samskipti við viðskiptarökfræði (sérstaklega til að veita notendastarfsemi sem hluta af viðskiptaferlum) og API vettvangsþjónustu; krefst vandlegrar athygli á API breytingum, fram og aftur samhæfni;
  • það er nauðsynlegt að bæta við viðbótar keyrslusöfnum til að tryggja virkni viðskiptarökfræði sem hluta af hverri slíkri örþjónustu, og það gefur tilefni til nýrra krafna um slík bókasöfn: léttleika og lágmarks breytileg ósjálfstæði;
  • viðskiptarökfræðihönnuðir þurfa að fylgjast með útgáfum bókasafna: ef örþjónusta hefur ekki verið fullgerð í langan tíma, þá mun hún líklegast innihalda úrelta útgáfu af bókasöfnunum. Þetta getur verið óvænt hindrun við að bæta við nýjum eiginleikum og gæti þurft að flytja gamla viðskiptarökfræði slíkrar þjónustu yfir í nýjar útgáfur af bókasöfnum ef ósamrýmanlegar breytingar voru á milli útgáfur.

BPM stíl samþætting

Lag af vettvangsþjónustu er einnig til staðar í slíkum arkitektúr, en þetta lag myndar ekki lengur ílát til að framkvæma viðskiptarökfræði léns, heldur aðeins umhverfi þess, sem býður upp á auka „vettvang“ aðgerðir. Slíkt lag er ekki aðeins nauðsynlegt til að viðhalda léttu eðli örþjónustu léna, heldur einnig til að miðstýra stjórnun.

Til dæmis, aðgerðir notenda í viðskiptaferlum búa til verkefni. Hins vegar, þegar unnið er með verkefni, verður notandinn að sjá verkefni frá öllum lénum á almenna listanum, sem þýðir að það verður að vera til samsvarandi verkefnaskráningarþjónusta á vettvangi, hreinsuð af viðskiptarökfræði léna. Það er frekar erfitt að viðhalda innhjúpun viðskiptarökfræði í slíku samhengi og þetta er önnur málamiðlun þessarar byggingarlistar.

Samþætting viðskiptaferla með augum forritara

Eins og nefnt er hér að ofan verður forritara að taka frá tæknilegum og verkfræðilegum eiginleikum þess að innleiða samspil nokkurra forrita svo hægt sé að treysta á góða framleiðni í þróun.

Við skulum reyna að leysa frekar erfitt samþættingarvandamál, sérstaklega fundið upp fyrir greinina. Þetta verður „leikja“ verkefni sem felur í sér þrjú forrit, þar sem hvert þeirra skilgreinir ákveðið lén: „app1“, „app2“, „app3“.

Inni í hverju forriti eru viðskiptaferli sett af stað sem byrja að „leika boltanum“ í gegnum samþættingarrútuna. Skilaboð með nafninu „Ball“ munu virka sem bolti.

Reglur leiksins:

  • fyrsti leikmaðurinn er frumkvöðullinn. Hann býður öðrum spilurum í leikinn, byrjar leikinn og getur endað hann hvenær sem er;
  • aðrir leikmenn lýsa yfir þátttöku sinni í leiknum, „kynnast“ hver öðrum og fyrsta leikmanninum;
  • eftir að hafa fengið boltann velur leikmaðurinn annan þátttakanda og gefur honum boltann. Heildarfjöldi sendinga er talinn;
  • Hver leikmaður hefur "orku" sem minnkar með hverri sendingu boltans frá þeim leikmanni. Þegar orkan klárast yfirgefur leikmaðurinn leikinn og tilkynnir afsögn sína;
  • ef leikmaðurinn er einn eftir, tilkynnir hann þegar í stað brottför sína;
  • Þegar allir leikmenn eru felldir lýsir fyrsti leikmaðurinn því yfir að leiknum sé lokið. Ef hann fer snemma úr leiknum á hann eftir að fylgjast með leiknum til að klára hann.

Til að leysa þetta vandamál mun ég nota DSL okkar fyrir viðskiptaferla, sem gerir okkur kleift að lýsa rökfræðinni í Kotlin á einfaldan hátt, með að minnsta kosti ketill.

Viðskiptaferli fyrsta leikmannsins (aka upphafsmanns leiksins) mun virka í app1 forritinu:

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

Auk þess að framkvæma viðskiptarökfræði getur ofangreindur kóði framleitt hlutalíkan af viðskiptaferli, sem hægt er að sjá í formi skýringarmyndar. Við höfum ekki innleitt sjóntækið ennþá, svo við þurftum að eyða smá tíma í að teikna (hér einfaldaði ég aðeins BPMN merkinguna varðandi notkun hliða til að bæta samkvæmni skýringarmyndarinnar með kóðanum hér að neðan):

BPM stíl samþætting

app2 mun innihalda viðskiptaferli hins leikmannsins:

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

Skýringarmynd:

BPM stíl samþætting

Í app3 forritinu munum við búa til leikmann með aðeins öðruvísi hegðun: í stað þess að velja næsta leikmann af handahófi mun hann starfa samkvæmt hringlaga reikniritinu:

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

Að öðrum kosti er hegðun leikmannsins ekki frábrugðin þeirri fyrri, þannig að skýringarmyndin breytist ekki.

Nú þurfum við próf til að keyra þetta allt. Ég mun aðeins gefa upp kóðann fyrir prófið sjálft, svo að greinin verði ekki ringulreið (reyndar notaði ég prófunarumhverfið sem búið var til áðan til að prófa samþættingu annarra viðskiptaferla):

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

Við skulum keyra prófið og líta á annálinn:

framleiðsla á vélinni

Взята блокировка ключа 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!

Af öllu þessu getum við dregið nokkrar mikilvægar ályktanir:

  • með nauðsynlegum verkfærum geta forritarar búið til samþættingarsamskipti milli forrita án þess að trufla viðskiptarökfræði;
  • flókið samþættingarverkefni sem krefst verkfræðilegrar hæfni getur leynst innan rammans ef það er upphaflega innifalið í arkitektúr rammans. Ekki er hægt að fela erfiðleika vandamáls, þannig að lausnin á erfiðu vandamáli í kóða mun líta þannig út;
  • Þegar samþættingarrökfræði er þróað er brýnt að taka tillit til endanlegrar samræmis og skorts á línulegri breytileika breytinga á ástandi allra þátttakenda samþættingar. Þetta neyðir okkur til að flækja rökfræðina til að gera hana ónæmir fyrir röðinni sem ytri atburðir gerast í. Í okkar dæmi er leikmaðurinn neyddur til að taka þátt í leiknum eftir að hann lýsir yfir að hann sé hættur úr leiknum: aðrir leikmenn munu halda áfram að senda boltann til hans þar til upplýsingarnar um brottför hans berast og eru unnar af öllum þátttakendum. Þessi rökfræði fylgir ekki leikreglunum og er málamiðlunarlausn innan ramma valinnar byggingarlistar.

Næst munum við tala um hinar ýmsu ranghala lausnir okkar, málamiðlanir og önnur atriði.

Öll skilaboð eru í einni biðröð

Öll samþætt forrit vinna með einum samþættingarrútu, sem er kynntur í formi ytri miðlara, einni BPMQueue fyrir skilaboð og eitt BPMTopic efni fyrir merki (atburði). Að setja öll skilaboð í eina biðröð er í sjálfu sér málamiðlun. Á viðskiptarökfræðistigi geturðu nú kynnt eins margar nýjar skilaboðagerðir og þú vilt án þess að gera breytingar á kerfisskipulaginu. Þetta er umtalsverð einföldun, en henni fylgir ákveðnar áhættur, sem í samhengi við dæmigerð verkefni okkar virtust okkur ekki svo mikilvæg.

BPM stíl samþætting

Hins vegar er ein fínleiki hér: hvert forrit síar „sín“ skilaboð úr biðröðinni við innganginn, eftir nafni lénsins. Einnig er hægt að tilgreina lénið í merkjum ef þú þarft að takmarka „sýnileikaumfang“ merkisins við eitt forrit. Þetta ætti að auka strætóafköst, en viðskiptarökfræðin verður nú að starfa með lénsheitum: til að taka á skilaboðum - skylda, fyrir merki - æskilegt.

Að tryggja áreiðanleika samþættingarrútu

Áreiðanleiki samanstendur af nokkrum atriðum:

  • Valinn skilaboðamiðlari er mikilvægur þáttur í arkitektúrnum og einn bilunarpunktur: hann verður að vera nægilega bilunarþolinn. Þú ættir aðeins að nota tímaprófaðar útfærslur, með góðum stuðningi og stóru samfélagi;
  • það er nauðsynlegt að tryggja mikið framboð á skilaboðamiðlaranum, þar sem hann verður að vera líkamlega aðskilinn frá samþættum forritum (miklu erfiðara og dýrara að tryggja mikið framboð á forritum með hagnýtri viðskiptarökfræði);
  • miðlari er skylt að veita „að minnsta kosti einu sinni“ afhendingu tryggingar. Þetta er skyldubundin krafa fyrir áreiðanlega notkun samþættingarrútunnar. Það er engin þörf á „nákvæmlega einu sinni“ stigi tryggingar: viðskiptaferlar eru að jafnaði ekki viðkvæmir fyrir endurteknum komu skilaboða eða viðburða og í sérstökum verkefnum þar sem þetta er mikilvægt er auðveldara að bæta við viðbótarávísunum við fyrirtækið rökfræði en að nota stöðugt ansi "dýrar" " tryggingar;
  • Sending skilaboða og merkja verður að taka þátt í heildarviðskiptum með breytingum á stöðu viðskiptaferla og lénsgagna. Æskilegur kosturinn væri að nota mynstur Viðskiptaúthólf, en það mun þurfa viðbótartöflu í gagnagrunninum og endurvarpa. Í JEE forritum er hægt að einfalda þetta með því að nota staðbundinn JTA stjórnanda, en tengingin við valinn miðlara verður að geta virkað í XA;
  • meðhöndlun skilaboða og atburða sem berast verða einnig að vinna með færslu sem breytir stöðu viðskiptaferlis: ef slík færslu er afturkölluð, þá verður að hætta við móttöku skilaboðanna;
  • skilaboð sem ekki tókst að koma til skila vegna villna verða að geyma í sérstakri geymslu D.L.Q. (Dead Letter Queue). Í þessu skyni bjuggum við til sérstaka vettvangsörþjónustu sem geymir slík skilaboð í geymslunni sinni, skráir þau eftir eiginleikum (fyrir fljótlega flokkun og leit) og afhjúpar API til að skoða, endursenda á áfangastað og eyða skilaboðum. Kerfisstjórar geta unnið með þessa þjónustu í gegnum vefviðmótið sitt;
  • í miðlarastillingunum þarftu að stilla fjölda endurtekinna afhendingartilrauna og seinkana á milli sendingar til að draga úr líkum á því að skilaboð berist inn í DLQ (það er næstum ómögulegt að reikna út bestu færibreyturnar, en þú getur hagað þér með reynslu og breytt þeim meðan á notkun stendur );
  • Stöðugt verður að fylgjast með DLQ versluninni og eftirlitskerfið þarf að gera kerfisstjórum viðvart þannig að þegar óafgreidd skilaboð eiga sér stað geti þeir brugðist við eins fljótt og auðið er. Þetta mun draga úr „áhrifasvæði“ bilunar eða viðskiptarökvillu;
  • samþættingarrútan verður að vera ónæm fyrir tímabundinni fjarveru forrita: áskrift að efni verður að vera varanleg og lén forritsins verður að vera einstakt þannig að á meðan forritið er fjarverandi mun einhver annar ekki reyna að vinna úr skilaboðum þess frá biðröð.

Að tryggja þráðaröryggi viðskiptarökfræðinnar

Sama tilvik af viðskiptaferli getur tekið á móti nokkrum skilaboðum og atburðum í einu, en vinnsla þeirra hefst samhliða. Á sama tíma, fyrir forritara, ætti allt að vera einfalt og þráðlaust.

Viðskiptarökfræði ferlis vinnur úr hverjum ytri atburði sem hefur áhrif á það viðskiptaferli fyrir sig. Slíkir atburðir gætu verið:

  • setja af stað viðskiptaferlisdæmi;
  • aðgerð notenda sem tengist virkni innan viðskiptaferlis;
  • móttaka skilaboða eða merki sem viðskiptaferlistilvik er áskrifandi að;
  • kveikja á tímamæli sem settur er af viðskiptaferlisdæmi;
  • stjórna aðgerð í gegnum API (til dæmis truflun á ferli).

Hver slíkur atburður getur breytt stöðu viðskiptaferlistilviks: sumum athöfnum getur lokið og önnur geta hafist og gildi viðvarandi eiginleika geta breyst. Lokun einhverrar starfsemi getur leitt til þess að ein eða fleiri af eftirfarandi athöfnum sé virkjað. Þeir geta aftur á móti hætt að bíða eftir öðrum atburðum eða, ef þeir þurfa ekki frekari gögn, geta þeir lokið í sömu viðskiptum. Áður en færslunni er lokað er nýtt ástand viðskiptaferlisins vistað í gagnagrunninum, þar sem það mun bíða eftir að næsta ytri atburður eigi sér stað.

Viðvarandi viðskiptaferlisgögn sem eru geymd í venslagagnagrunni eru mjög þægilegur punktur til að samstilla vinnslu ef þú notar SELECT FOR UPDATE. Ef einni færslu tókst að fá stöðu viðskiptaferlis frá grunni til að breyta því, þá mun engin önnur samhliða færslu geta fengið sama ástand fyrir aðra breytingu, og eftir að fyrri færslunni er lokið er sú seinni tryggt að fá hið þegar breytta ástand.

Með því að nota svartsýna læsa á DBMS hlið uppfyllum við allar nauðsynlegar kröfur ACID, og halda einnig getu til að skala forritið með viðskiptarökfræði með því að fjölga hlaupandi tilvikum.

Hins vegar, svartsýnir læsingar ógna okkur með kyrrstöðu, sem þýðir að SELECT FOR UPDATE ætti samt að vera takmörkuð við einhvern hæfilegan tímafrest ef stöðnun kemur upp í sumum alvarlegum tilfellum í viðskiptarökfræðinni.

Annað vandamál er samstilling við upphaf viðskiptaferlis. Þó að það sé ekkert dæmi um viðskiptaferli er ekkert ástand í gagnagrunninum, þannig að aðferðin sem lýst er mun ekki virka. Ef þú þarft að tryggja sérstöðu viðskiptaferlistilviks í tilteknu umfangi, þá þarftu einhvers konar samstillingarhlut sem tengist ferliflokknum og samsvarandi umfangi. Til að leysa þetta vandamál notum við annan læsingarbúnað sem gerir okkur kleift að taka læsingu á handahófskennda auðlind sem tilgreind er með lykli á URI sniði í gegnum ytri þjónustu.

Í dæmunum okkar inniheldur InitialPlayer viðskiptaferlið yfirlýsingu

uniqueConstraint = UniqueConstraints.singleton

Þess vegna inniheldur skráin skilaboð um að taka og losa lásinn á samsvarandi lykli. Það eru engin slík skilaboð fyrir önnur viðskiptaferli: uniqueConstraint er ekki stillt.

Vandamál viðskiptaferla með viðvarandi ástandi

Stundum hjálpar það ekki aðeins að hafa viðvarandi ástand, heldur hindrar það líka þroska.
Vandamál hefjast þegar gera þarf breytingar á viðskiptarökfræði og/eða viðskiptaferlislíkani. Ekki eru allar slíkar breytingar í samræmi við gamla stöðu viðskiptaferla. Ef það eru mörg lifandi tilvik í gagnagrunninum, þá getur það að gera ósamrýmanlegar breytingar valdið miklum vandræðum, sem við lentum oft í þegar við notum jBPM.

Það fer eftir dýpt breytinganna, þú getur hagað þér á tvo vegu:

  1. búa til nýja tegund viðskiptaferlis til að gera ekki ósamrýmanlegar breytingar á því gamla og nota það í stað þess gamla þegar ný tilvik eru opnuð. Gömul eintök munu halda áfram að virka „eins og áður“;
  2. flytja viðvarandi stöðu viðskiptaferla þegar viðskiptarökfræði er uppfærð.

Fyrsta leiðin er einfaldari en hefur sínar takmarkanir og galla, til dæmis:

  • tvíverknað viðskiptarökfræði í mörgum viðskiptaferlislíkönum, sem eykur magn viðskiptarökfræðinnar;
  • Oft er þörf á tafarlausri umskipti yfir í nýja viðskiptarökfræði (hvað varðar samþættingarverkefni - næstum alltaf);
  • verktaki veit ekki á hvaða tímapunkti úreltum gerðum er hægt að eyða.

Í reynd notum við báðar aðferðirnar en höfum tekið nokkrar ákvarðanir til að gera líf okkar auðveldara:

  • Í gagnagrunninum er viðvarandi ástand viðskiptaferlis geymt á auðlæsilegu og auðvinnanlega formi: í JSON sniði streng. Þetta gerir flutningum kleift að framkvæma bæði innan forritsins og utan. Sem síðasta úrræði geturðu leiðrétt það handvirkt (sérstaklega gagnlegt í þróun meðan á kembiforriti stendur);
  • samþættingarviðskiptarökfræðin notar ekki nöfn viðskiptaferla, þannig að hvenær sem er er hægt að skipta út innleiðingu eins af þátttökuferlunum fyrir nýtt með nýju nafni (til dæmis „InitialPlayerV2“). Bindingin á sér stað í gegnum skilaboða- og merkjanöfn;
  • ferlilíkanið hefur útgáfunúmer, sem við hækkum ef við gerum ósamrýmanlegar breytingar á þessu líkani, og þetta númer er vistað ásamt stöðu ferlisins;
  • viðvarandi ástand ferlisins er lesið úr gagnagrunninum fyrst yfir í þægilegt hlutalíkan sem flutningsferlið getur unnið með ef útgáfunúmer líkansins hefur breyst;
  • flutningsaðferðin er sett við hlið viðskiptarökfræðinnar og er kölluð „latur“ fyrir hvert tilvik viðskiptaferlisins þegar það er endurheimt úr gagnagrunninum;
  • ef þú þarft að flytja stöðu allra vinnslutilvika hratt og samstillt eru notaðar klassískari gagnaflutningslausnir, en þú verður að vinna með JSON.

Þarftu annan ramma fyrir viðskiptaferla?

Lausnirnar sem lýst er í greininni leyfðu okkur að einfalda líf okkar verulega, auka úrval mála sem leyst eru á þróunarstigi forrita og gera hugmyndina um að aðgreina viðskiptarökfræði í örþjónustur aðlaðandi. Til að ná þessu fram var mikil vinna lögð í það, búið til mjög „létt“ ramma fyrir viðskiptaferla sem og þjónustuþætti til að leysa tilgreind vandamál í samhengi við margvísleg forritunarvandamál. Við höfum löngun til að deila þessum niðurstöðum og gera þróun sameiginlegra íhluta opinn aðgang undir ókeypis leyfi. Þetta mun krefjast nokkurrar fyrirhafnar og tíma. Skilningur á eftirspurn eftir slíkum lausnum gæti verið okkur hvatning til viðbótar. Í fyrirhugaðri grein er mjög lítill gaumur gefinn að getu rammans sjálfs, en sumir þeirra eru sýnilegir af dæmunum sem sett eru fram. Ef við birtum ramma okkar verður sérstök grein helguð því. Í millitíðinni værum við þakklát ef þú skilur eftir smá viðbrögð með því að svara spurningunni:

Aðeins skráðir notendur geta tekið þátt í könnuninni. Skráðu þig inn, takk.

Þarftu annan ramma fyrir viðskiptaferla?

  • 18,8%Já, ég er búinn að vera að leita að einhverju svona lengi

  • 12,5%Ég hef áhuga á að læra meira um útfærsluna þína, hún gæti verið gagnleg2

  • 6,2%Við notum einn af núverandi ramma en erum að hugsa um að skipta út1

  • 18,8%Við notum einn af núverandi ramma, allt er í lagi3

  • 18,8%við stjórnum án ramma3

  • 25,0%skrifaðu þitt 4

16 notendur kusu. 7 notendur sátu hjá.

Heimild: www.habr.com

Bæta við athugasemd