BPM estiloaren integrazioa

BPM estiloaren integrazioa

Kaixo, Habr!

Gure enpresa ERP mailako software-soluzioen garapenean espezializatuta dago, eta horien zati handiena negozio-logika eta dokumentu-fluxu handia duten transakzio-sistemek hartzen dute EDMSa. Gure produktuen egungo bertsioak JavaEE teknologietan oinarritzen dira, baina mikrozerbitzuekin ere aktiboki esperimentatzen ari gara. Soluzio horien arlorik problematikoenetako bat ondoko domeinuetako hainbat azpisistemaren integrazioa da. Integrazio arazoek buruhauste handia eman digute beti, erabiltzen ditugun arkitektura-estiloak, teknologia-pilak eta markoak kontuan hartu gabe, baina azkenaldian aurrerapausoak eman dira horrelako arazoak konpontzen.

Zure arreta jartzen dizudan artikuluan NPO Kristak izendatutako eremuan duen esperientzia eta ikerketa arkitektonikoaz hitz egingo dut. Era berean, aplikazioen garatzaile baten ikuspegitik integrazio-arazo baten irtenbide sinple baten adibide bat ikusiko dugu eta sinpletasun horren atzean zer ezkutatzen den ezagutuko dugu.

Erantzukizuna

Artikuluan deskribatzen diren irtenbide arkitektonikoak eta teknikoak nik proposatzen ditut zeregin zehatzen testuinguruko esperientzia pertsonalean oinarrituta. Irtenbide hauek ez dute unibertsalak direnik eta baliteke beste erabilera-baldintzetan optimoak ez izatea.

Zer zerikusi du BPM-k?

Galdera honi erantzuteko, gure soluzioen aplikatutako arazoen zehaztasunetan apur bat sakondu behar dugu. Gure transakzio-sistema tipikoko negozio-logikaren zati nagusia datuak datu-basean sartzea da erabiltzaile-interfazeen bidez, datu horien egiaztapen eskuz eta automatizatua, lan-fluxu batzuen bidez egitea, beste sistema / datu base analitikoa / artxibo batean argitaratzea, txostenak sortzea. . Horrela, bezeroentzako sistemaren funtsezko funtzioa beren barneko negozio-prozesuen automatizazioa da.

Erosotasunerako, komunikazioan "dokumentu" terminoa erabiltzen dugu lan-fluxu jakin bat "lotu" daitekeen gako komun batek batutako datu multzo baten abstrakzio gisa.
Baina zer gertatzen da integrazio logikarekin? Azken finean, integrazio-zeregina sistemaren arkitekturak sortzen du, eta zatitan "moztu" egiten da EZ bezeroak eskatuta, baizik eta faktore guztiz desberdinen eraginpean:

  • Conwayren legearen menpe;
  • aurretik beste produktu batzuetarako garatutako azpisistemak berrerabiltzearen ondorioz;
  • arkitektoaren erabakiz, baldintza ez-funtzionaletan oinarrituta.

Tentazio handia dago integrazio-logika lan-fluxu nagusiaren negozio-logikatik bereizteko, negozio-logika integrazio-artefaktuekin ez kutsatzeko eta aplikazioen garatzailea sistemaren paisaia arkitektonikoaren berezitasunetan sakontzeko beharra arintzeko. Ikuspegi honek abantaila ugari ditu, baina praktikak bere eraginkortasunik eza erakusten du:

  • integrazio-arazoak konpontzea normalean dei sinkronikoen moduan aukerarik sinpleenetara itzuliko da lan-fluxu nagusiaren ezarpenean luzapen-puntu mugatuak direla eta (integrazio sinkronikoaren desabantailak behean eztabaidatzen dira);
  • integrazio-artefaktuak negozioaren oinarrizko logika sartzen dira oraindik beste azpisistema baten feedbacka behar denean;
  • aplikazioaren garatzaileak integrazioa alde batera uzten du eta erraz hautsi dezake lan-fluxua aldatuz;
  • sistemak osotasun bakarra izateari uzten dio erabiltzailearen ikuspuntutik, azpisistemen arteko β€œjunturak” nabaritzen dira eta erabiltzaileen eragiketa erredundanteak agertzen dira, azpisistema batetik bestera datuak transferitzeari hasiera emanez.

Beste ikuspegi bat integrazio-interakzioak negozioaren oinarrizko logikaren eta lan-fluxuaren osagai gisa hartzea da. Aplikazioen garatzaileen kualifikazioak gora egitea saihesteko, integrazio-interakzio berriak sortzea erraza eta esfortzurik gabea izan behar da, irtenbide bat aukeratzeko aukera minimoarekin. Hori egitea dirudiena baino zailagoa da: tresnak nahikoa indartsua izan behar du erabiltzaileari bere erabilerarako behar dituen aukera ugari eskaintzeko, "oinean tiro egiten" utzi gabe. Ingeniari batek integrazio-zereginen testuinguruan erantzun behar dituen galdera asko daude, baina aplikazioen garatzaileak bere eguneroko lanean pentsatu behar ez dituenak: transakzioen mugak, koherentzia, atomizazioa, segurtasuna, eskalatzea, karga eta baliabideen banaketa, bideratzea, marshaling, banaketa- eta aldatze-testuinguruak, etab. Beharrezkoa da aplikazio-garatzaileei irtenbide nahiko soil-txantiloiak eskaintzea, non galdera horien erantzunak dagoeneko ezkutatuta dauden. Txantiloi hauek nahiko seguruak izan behar dute: negozio-logika oso maiz aldatzen da, eta horrek akatsak sartzeko arriskua areagotzen du, akatsen kostua nahiko maila baxuan mantendu behar da.

Baina zer zerikusi du BPM-k? Lan-fluxua ezartzeko aukera asko daude...
Izan ere, negozio-prozesuen beste inplementazio bat oso ezaguna da gure soluzioetan - egoera trantsizio-diagrama baten definizio deklaratiboaren bidez eta kudeatzaileak trantsizioetarako negozio-logikarekin lotuz. Kasu honetan, negozio-prozesuan "dokumentuaren" egungo posizioa zehazten duen egoera "dokumentuaren" beraren atributua da.

BPM estiloaren integrazioa
Horrelako itxura du prozesuak proiektu baten hasieran

Inplementazio honen ospea negozio-prozesu linealak sortzeko sinpletasun eta abiadura erlatiboari zor zaio. Hala ere, software-sistemak etengabe konplexuagoak diren heinean, negozio-prozesuaren zati automatizatua hazi eta konplexuagoa bihurtzen da. Deskonposizioa, prozesuen zatiak berrerabiltzea eta adarkatze prozesuak behar dira, adar bakoitza paraleloan exekutatu dadin. Baldintza horietan, tresna deseroso bihurtzen da, eta egoera-trantsizio-diagramak informazio-edukia galtzen du (integrazio-interakzioak ez dira diagraman batere islatzen).

BPM estiloaren integrazioa
Hau da prozesuak eskakizunak argitzeko hainbat iterazioren ondoren.

Egoera horretatik ateratzeko bidea motorra integratzea izan zen jBPM negozio-prozesu konplexuenak dituzten produktu batzuetan. Epe laburrean, irtenbide honek nolabaiteko arrakasta izan zuen: negozio-prozesu konplexuak ezartzea posible izan zen, idazkeran diagrama nahiko informatiboa eta garrantzitsua mantenduz. BPMN2.

BPM estiloaren integrazioa
Negozio prozesu konplexu baten zati txiki bat

Epe luzera, irtenbideak ez zituen itxaropenak bete: tresna bisualen bidez negozio-prozesuak sortzeko lan intentsitate handiak ez zuen produktibitate-adierazle onargarriak lortzea ahalbidetzen, eta tresna bera garatzaileen artean gustukoenetako bat bihurtu zen. Motorraren barne egiturari buruzko kexak ere egon ziren, eta horrek β€œadabaki” eta β€œmakulu” asko agertzea ekarri zuen.

jBPM erabiltzearen alderdi positibo nagusia negozio prozesu-instantzia baten egoera iraunkorra izatearen onuren eta kalteen kontzientzia izan zen. Era berean, prozesu-ikuspegia erabiltzeko aukera ikusi genuen aplikazio ezberdinen artean integrazio-protokolo konplexuak ezartzeko seinaleen eta mezuen bidez interakzio asinkronoak erabiliz. Egoera iraunkor baten presentzia funtsezkoa da horretan.

Aurrekoa kontuan hartuta, honakoa ondoriozta dezakegu: BPM estiloko prozesuen ikuspegiari esker, zeregin sorta zabala ebazteko aukera ematen digu gero eta konplexuagoak diren negozio-prozesuak automatizatzeko, prozesu horietan integrazio-jarduerak harmoniatsu egokitzeko eta inplementatutako prozesua idazkera egoki batean bisualki bistaratzeko gaitasuna mantentzeko.

Dei sinkronikoen desabantailak integrazio eredu gisa

Integrazio sinkronikoa blokeo-dei errazena da. Azpisistema batek zerbitzariaren alde egiten du eta APIa eskatzen duen metodoarekin erakusten du. Beste azpisistema batek bezeroaren alde egiten du eta une egokian dei bat egiten du eta emaitzaren zain geratzen da. Sistemaren arkitekturaren arabera, bezeroaren eta zerbitzariaren aldeak aplikazio eta prozesu berean egon daitezke, edo desberdinetan. Bigarren kasuan, RPC inplementazioren bat aplikatu eta parametroen eta deiaren emaitzaren marshalling eman behar duzu.

BPM estiloaren integrazioa

Integrazio eredu honek desabantaila multzo nahiko handia du, baina praktikan oso erabilia da bere sinpletasuna dela eta. Inplementazio-abiadurak liluratu eta behin eta berriro erabiltzera behartzen zaitu epe larrien aurrean, irtenbidea zor tekniko gisa erregistratuz. Baina esperientziarik gabeko garatzaileek inkontzienteki erabiltzen dutela ere gertatzen da, ondorio negatiboak konturatu gabe.

Azpisistemen konektibitatearen gehikuntzarik nabarmenenaz gain, transakzio "hazi" eta "luzatzeko" arazo ez hain nabariak ere badaude. Izan ere, negozio-logikak aldaketa batzuk egiten baditu, orduan transakzioak ezin dira saihestu, eta transakzioek, aldi berean, aldaketa horiek eragindako aplikazio-baliabide batzuk blokeatzen dituzte. Hau da, azpisistema batek bestearen erantzunaren zain egon arte, ezin izango du transakzioa burutu eta blokeoak kendu. Horrek nabarmen handitzen du hainbat ondorio izateko arriskua:

  • Sistemaren erantzuna galtzen da, erabiltzaileek denbora luzez itxaron egiten dute kontsulten erantzunak;
  • zerbitzariak, oro har, erabiltzaileen eskaerei erantzutea uzten du gainezka dagoen hari multzoa dela eta: hari gehienak transakzio batek okupatutako baliabide batean blokeatuta daude;
  • Blokeoak agertzen hasten dira: haien agerraldiaren probabilitatea transakzioen iraupenaren, negozio logikaren eta transakzioan parte hartzen duten blokeoen araberakoa da;
  • transakzio denbora-muga akatsak agertzen dira;
  • zerbitzariak "huts" egiten du OutOfMemory-rekin, zereginak datu-kopuru handiak prozesatu eta aldatzea eskatzen badu, eta integrazio sinkronoak egoteak oso zaila egiten du prozesatzea transakzio "arinagoetan" banatzea.

Arkitekturaren ikuspuntutik, integrazioan deiak blokeatzeak erabiltzeak azpisistema indibidualen kalitatearen gaineko kontrola galtzea dakar: ezinezkoa da azpisistema baten xede-kalitate-adierazleak beste azpisistema bateko kalitate-adierazleetatik isolatuta bermatzea. Azpisistemak talde ezberdinek garatzen badituzte, arazo handia da.

Gauzak are interesgarriagoak dira integratzen ari diren azpisistemak aplikazio ezberdinetan badaude eta bi aldeetatik aldaketa sinkronoak egin behar badituzu. Nola ziurtatu aldaketa horien transakzionaltasuna?

Aldaketak transakzio bereizietan egiten badira, salbuespenen kudeaketa eta konpentsazio fidagarriak eman beharko dituzu, eta horrek integrazio sinkronikoen onura nagusia ezabatzen du: sinpletasuna.

Banatutako transakzioak ere etortzen zaizkigu burura, baina ez ditugu gure soluzioetan erabiltzen: zaila da fidagarritasuna ziurtatzea.

"Saga" transakzio arazoaren irtenbide gisa

Mikrozerbitzuen ospea gero eta handiagoarekin, eskaria Saga Eredua.

Eredu honek ezin hobeto konpontzen ditu lehen aipatutako transakzio luzeen arazoak, eta sistemaren egoera kudeatzeko gaitasunak ere zabaltzen ditu negozio-logikaren aldetik: huts egin duen transakzio baten ondoriozko kalte-ordainak baliteke sistema bere jatorrizko egoerara itzultzea, baina ematen du. datuak prozesatzeko bide alternatibo bat. Horrek, gainera, arrakastaz amaitutako datuak prozesatzeko urratsak errepikatzea saihesteko aukera ematen du prozesua amaiera "onera" eramaten saiatzean.

Interesgarria da sistema monolitikoetan eredu hau garrantzitsua da akoplatutako azpisistemen baxuen integrazioari dagokionez eta epe luzeko transakzioek eta dagozkien baliabideen blokeoek eragindako ondorio negatiboak ikusten direnean.

BPM estiloko gure negozio prozesuei dagokienez, oso erraza da "Sagas" ezartzea: "Saga"ren urrats indibidualak negozio-prozesuaren barruan jarduera gisa zehaztu daitezke, eta negozio-prozesuaren egoera iraunkorra ere. "Saga"ren barne-egoera zehazten du. Hau da, ez dugu koordinazio mekanismo gehigarririk behar. Behar duzun guztia garraio gisa "gutxienez behin" bermeak onartzen dituen mezu-artekari bat da.

Baina irtenbide honek bere "prezioa" ere badu:

  • negozio-logika konplexuagoa bihurtzen da: kalte-ordainak landu behar dira;
  • beharrezkoa izango da koherentzia osoa alde batera utzi, sistema monolitikoetarako bereziki sentikorra izan daitekeena;
  • Arkitektura apur bat konplikatuagoa bihurtzen da, eta mezu-artekari baten behar gehigarri bat agertzen da;
  • monitorizazio eta administrazio tresna osagarriak beharko dira (nahiz eta, oro har, ona den: sistemaren zerbitzuaren kalitatea handituko da).

Sistema monolitikoetarako, "Sag" erabiltzearen justifikazioa ez da hain agerikoa. Mikrozerbitzuetarako eta beste SOA batzuetarako, ziurrenik jada broker bat dagoen, eta proiektuaren hasieran koherentzia osoa sakrifikatzen den, eredu hau erabiltzearen onurak desabantailak nabarmen gaindi ditzake, batez ere negozio-logikan API eroso bat badago. maila.

Negozio-logika mikrozerbitzuetan kapsulatzea

Mikrozerbitzuekin esperimentatzen hasi ginenean, arrazoizko galdera bat sortu zen: non kokatu domeinuaren negozio-logika domeinuaren datuen iraupena ziurtatzen duen zerbitzuaren aldean?

Hainbat BPMSren arkitekturari begira, arrazoizkoa dirudi negozio-logika iraunkortasunetik bereiztea: plataforma eta domeinutik independenteak diren mikrozerbitzuen geruza bat sortu, domeinuaren negozio-logika exekutatzeko ingurune eta edukiontzi bat osatzen dutenak, eta domeinuko datuen iraunkortasuna diseinatzea. mikrozerbitzu oso sinple eta arinen geruza bereizia. Negozio-prozesuek kasu honetan iraunkortasun-geruzaren zerbitzuen orkestrazioa egiten dute.

BPM estiloaren integrazioa

Ikuspegi honek abantaila oso handia du: plataformaren funtzionaltasuna nahi adina handitu dezakezu, eta horri dagokion plataformako mikrozerbitzuen geruza baino ez da "lodi" bihurtuko. Edozein domeinutako negozio-prozesuek plataformaren funtzionaltasun berria berehala erabiltzeko gai dira, eguneratu bezain pronto.

Azterketa zehatzago batek ikuspegi honen desabantaila nabarmenak agerian utzi zituen:

  • Domeinu askoren negozio-logika aldi berean exekutatzen duen plataforma-zerbitzuak arrisku handiak ditu huts-puntu bakar gisa. Negozio-logikan maiz egiten diren aldaketak sistema osorako hutsegiteak ekartzeko akatsak izateko arriskua areagotzen du;
  • errendimendu-arazoak: negozio-logikak bere datuekin funtzionatzen du interfaze estu eta motel baten bidez:
    • datuak berriro ere sare-pilaren bidez banatu eta ponpatu egingo dira;
    • domeinu-zerbitzu batek negozio-logikak prozesatzeko behar baino datu gehiago emango ditu askotan, zerbitzuaren kanpoko API mailan eskaerak parametrizatzeko gaitasun nahikorik ez duelako;
    • negozio-logikako hainbat pieza independentek behin eta berriz eska ditzakete datu berdinak prozesatzeko (arazo hau arindu daiteke datuak cachean gordetzen dituzten saio-osagaiak gehituz, baina horrek are gehiago zaildu egiten du arkitektura eta datuen garrantzia eta cachea baliogabetzeko arazoak sortzen ditu);
  • transakzio arazoak:
    • egoera iraunkorra duten negozio-prozesuak, plataforma-zerbitzu batek gordetzen dituenak, ez datoz bat domeinuko datuekin, eta ez dago arazo hori konpontzeko modu errazak;
    • domeinuko datuen blokeoa transakziotik kanpo jartzea: domeinuaren negozio-logikak aldaketak egin behar baditu uneko datuen zuzentasuna egiaztatu ondoren, beharrezkoa da prozesatutako datuen lehiakortasuna aldatzeko aukera baztertzea. Kanpoko datuen blokeoak arazoa konpontzen lagun dezake, baina irtenbide horrek arrisku gehigarriak ditu eta sistemaren fidagarritasun orokorra murrizten du;
  • zailtasun gehigarriak eguneratzean: kasu batzuetan, iraunkortasun-zerbitzua eta negozio-logika sinkronoki edo sekuentzia zorrotzean eguneratu behar dira.

Azken finean, oinarrietara itzuli behar izan dugu: domeinuaren datuak eta domeinuaren negozio-logika mikrozerbitzu batean kapsulatu. Ikuspegi honek mikrozerbitzu bat sistemaren osagai integral gisa hautematea errazten du eta ez ditu aurreko arazoak sortzen. Hau ere ez da doan ematen:

  • API estandarizazioa beharrezkoa da negozio-logikarekin (bereziki, erabiltzaile-jarduerak negozio-prozesuen parte gisa eskaintzeko) eta API plataforma-zerbitzuekin elkarreragiteko; arreta handiagoa eskatzen du API aldaketei, aurrera eta atzerako bateragarritasunari;
  • beharrezkoa da exekuzio-liburutegi osagarriak gehitzea mikrozerbitzu bakoitzaren barruan negozio-logikaren funtzionamendua bermatzeko, eta horrek eskakizun berriak sortzen ditu halako liburutegietarako: arintasuna eta mendekotasun iragankorrak minimoak;
  • negozio-logikako garatzaileek liburutegien bertsioak kontrolatu behar dituzte: mikrozerbitzu bat denbora luzez amaitu ez bada, ziurrenik liburutegien bertsio zaharkitu bat edukiko du. Ezaugarri berri bat gehitzeko ustekabeko oztopoa izan daiteke eta zerbitzu baten negozio-logika zaharra liburutegien bertsio berrietara migratzea eska dezake bertsioen artean aldaketa bateraezinak egon badira.

BPM estiloaren integrazioa

Plataforma-zerbitzuen geruza bat ere badago horrelako arkitektura batean, baina geruza honek jada ez du domeinuaren negozio-logika exekutatzeko edukiontzi bat osatzen, bere ingurunea baizik, "plataforma" funtzio osagarriak eskainiz. Geruza hori beharrezkoa da domeinuko mikrozerbitzuen izaera arina mantentzeko ez ezik, kudeaketa zentralizatzeko ere.

Adibidez, negozio-prozesuetako erabiltzaileen jarduerek zereginak sortzen dituzte. Hala ere, zereginekin lan egiten duenean, erabiltzaileak domeinu guztietako zereginak ikusi behar ditu zerrenda orokorrean, hau da, dagokion plataformako atazak erregistratzeko zerbitzu bat egon behar du, domeinuaren negozio-logikatik garbituta. Halako testuinguru batean negozio-logikaren kapsulazioa mantentzea nahiko problematikoa da, eta hau da arkitektura honen beste konpromiso bat.

Negozio-prozesuen integrazioa aplikazioen garatzaile baten begietatik

Arestian esan bezala, aplikazioen garatzaile batek hainbat aplikazioren elkarrekintza ezartzearen ezaugarri tekniko eta ingeniaritzak kendu behar ditu, garapenaren produktibitate ona izan dezan.

Saia gaitezen integrazio arazo zaila konpontzen, artikulurako bereziki asmatua. Hau "joko" zeregina izango da, hiru aplikazio barne hartzen dituena, non haietako bakoitzak domeinu-izen jakin bat definitzen duen: "app1", "app2", "app3".

Aplikazio bakoitzaren barruan, integrazio-busaren bidez "pilotan" hasten diren negozio-prozesuak abiarazten dira. "Pilota" izena duten mezuek pilota gisa jokatuko dute.

Jokoaren arauak:

  • lehenengo jokalaria abiarazlea da. Beste jokalari batzuk jokora gonbidatzen ditu, jokoa hasten du eta edozein unetan amaitu dezake;
  • beste jokalariek jokoan parte hartzen dutela adierazten dute, elkar eta lehen jokalaria β€œezagutu”;
  • baloia jaso ondoren, jokalariak parte hartzen duen beste jokalari bat aukeratzen du eta baloia pasatzen dio. Transmisioen kopuru osoa zenbatzen da;
  • Jokalari bakoitzak "energia" du, jokalari horrek baloiaren pase bakoitzean gutxitzen dena. Energia agortzen denean, jokalariak partida uzten du, bere dimisioa iragarriz;
  • jokalaria bakarrik geratzen bada, berehala iragartzen du bere irteera;
  • Jokalari guztiak kanporatzen direnean, lehenengo jokalariak partida amaitutzat emango du. Partida goiz uzten badu, partida jarraitzeko geratzen da hura osatzeko.

Arazo hau konpontzeko, gure DSL negozio-prozesuetarako erabiliko dut, eta horrek Kotlin-en logika trinkoki deskribatzeko aukera ematen digu, gutxieneko boilerplate batekin.

Lehenengo jokalariaren negozio-prozesuak (jokoaren hastapena deitzen dena) app1 aplikazioan funtzionatuko du:

InitialPlayer klasea

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

Negozio-logika exekutatzeaz gain, goiko kodeak negozio-prozesu baten objektu-eredu bat sor dezake, diagrama moduan ikus daitekeena. Oraindik ez dugu bistaratzailea inplementatu, beraz, denbora pixka bat eman behar izan dugu marrazten (hemen BPMN notazioa apur bat sinplifikatu dut ateen erabilerari dagokionez diagramaren koherentzia hobetzeko beheko kodearekin):

BPM estiloaren integrazioa

app2-k beste jokalariaren negozio-prozesua barne hartuko du:

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

Diagrama:

BPM estiloaren integrazioa

App3 aplikazioan portaera apur bat ezberdina duen jokalari bat egingo dugu: hurrengo jokalaria ausaz hautatu beharrean, round-robin algoritmoaren arabera jokatuko du:

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

Bestela, jokalariaren jokabidea ez da aurrekoaren desberdina, beraz, diagrama ez da aldatzen.

Orain hau guztia exekutatzeko proba bat behar dugu. Probaren beraren kodea bakarrik emango dut, artikulua ez nahasteko boilerplate batekin (izan ere, lehenago sortutako proba-ingurunea erabili nuen beste negozio-prozesuen integrazioa probatzeko):

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

Egin dezagun proba eta begiratu erregistroa:

kontsolaren irteera

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

Guzti honetatik hainbat ondorio garrantzitsu atera ditzakegu:

  • beharrezko tresnekin, aplikazioen garatzaileek aplikazioen arteko integrazio-interakzioak sor ditzakete negozio-logika eten gabe;
  • ingeniaritza gaitasunak behar dituen integrazio-zeregin baten konplexutasuna esparruaren barruan ezkutatu daiteke, hasiera batean markoaren arkitekturan sartzen bada. Arazo baten zailtasuna ezin da ezkutatu, beraz, kodean arazo zail baten irtenbidea itxura izango du;
  • Integrazio-logika garatzean, ezinbestekoa da kontuan hartzea koherentzia eventuala eta integrazio-parte-hartzaile guztien egoera-aldaketen linealizagarritasun eza. Horrek logika zailtzera behartzen gaitu, kanpoko gertaerak gertatzen diren ordenaren aurrean sentibera ez izateko. Gure adibidean, jokalaria jokoan parte hartzera behartuta dago jokotik irtetea adierazi ondoren: beste jokalariek baloia pasatzen jarraituko dute, bere irteerari buruzko informazioa parte-hartzaile guztiek heldu eta prozesatu arte. Logika hori ez da joko-arauetatik jarraitzen eta aukeratutako arkitekturaren esparruan konponbide irtenbide bat da.

Jarraian, gure konponbidearen, konpromisoen eta beste puntu batzuen korapilatsuei buruz hitz egingo dugu.

Mezu guztiak ilara batean daude

Aplikazio integratu guztiek integrazio-bus batekin funtzionatzen dute, kanpoko broker baten moduan aurkezten dena, mezuetarako BPMQueue bat eta seinaleetarako (gertaeretarako) gai bat BPMTopic. Mezu guztiak ilara batean jartzea konpromiso bat da. Negozio-logikan, orain nahi adina mezu mota berri sar ditzakezu sistemaren egituran aldaketarik egin gabe. Sinplifikazio nabarmena da hori, baina arrisku batzuk dakartza, gure ohiko zereginen testuinguruan hain esanguratsuak iruditzen ez zitzaizkigunak.

BPM estiloaren integrazioa

Hala ere, bada hemen sotiltasun bat: aplikazio bakoitzak "bere" mezuak iragazten ditu sarrerako ilaran, bere domeinuaren izenarekin. Domeinua seinaleetan ere zehaztu daiteke seinalearen "ikusgarritasunaren esparrua" aplikazio bakar batera mugatu behar baduzu. Honek autobusaren transmisioa handitu beharko luke, baina negozio-logikak orain domeinu-izenekin funtzionatu behar du: mezuak bideratzeko - derrigorrezkoa, seinaleetarako - desiragarria.

Integrazio Busen fidagarritasuna bermatzea

Fidagarritasuna hainbat puntuk osatzen dute:

  • Hautatutako mezu-artekaria arkitekturaren osagai kritikoa da eta hutsegite puntu bakarra da: akatsekiko tolerantzia nahikoa izan behar du. Denbora probatutako inplementazioak soilik erabili behar dituzu, laguntza onarekin eta komunitate handi batekin;
  • beharrezkoa da mezu-artekariaren erabilgarritasun handia bermatzea, eta horretarako fisikoki bereizi behar da aplikazio integratuetatik (aplikaturiko negozio-logika duten aplikazioen erabilgarritasun handia askoz zailagoa eta garestiagoa da ziurtatzea);
  • artekaria behartuta dago "gutxienez behin" entrega-bermeak ematera. Hau derrigorrezko baldintza da integrazio-busaren funtzionamendu fidagarria izateko. Ez dago "zehazki behin" maila-bermeen beharrik: negozio-prozesuak, oro har, ez dira mezuak edo gertaerak behin eta berriz iristen direnean sentikorrak, eta hori garrantzitsua den zeregin berezietan, errazagoa da negozioari egiaztapen gehigarriak gehitzea. logika "berme nahiko garestiak" etengabe erabiltzea baino;
  • mezuak eta seinaleak bidaltzeak negozio prozesuen eta domeinuko datuen egoeraren aldaketak dituen transakzio orokor batean parte hartu behar du. Aukera hobetsia eredu bat erabiltzea izango litzateke Irteera-ontzi transakzionala, baina datu-basean taula gehigarri bat eta errepikagailu bat beharko ditu. JEE aplikazioetan, tokiko JTA kudeatzaile bat erabiliz sinplifikatu daiteke, baina hautatutako brokerarekiko konexioak funtzionatzeko gai izan behar du. XA;
  • Sarrerako mezuen eta gertaeren kudeatzaileek negozio-prozesu baten egoera aldatzen duen transakzio batekin ere lan egin behar dute: transakzio hori atzera botatzen bada, orduan bertan behera utzi behar da mezua jasotzea;
  • Erroreengatik entregatu ezin izan diren mezuak biltegiratze bereizi batean gorde behar dira D.L.Q. (Hildako gutunen ilara). Horretarako, plataformako mikrozerbitzu bereizi bat sortu dugu, halako mezuak bere biltegian gordetzen dituena, atributuen arabera indexatzen dituena (taldekatze eta bilaketa azkarrak egiteko) eta API bat erakusten duena helmugako helbidera bidaltzeko eta mezuak ezabatzeko. Sistema-administratzaileek zerbitzu honekin lan egin dezakete beren web-interfazearen bidez;
  • Broker-en ezarpenetan, bidalketa-saioen eta bidalketen arteko atzerapenen kopurua egokitu behar duzu mezuak DLQra sartzeko probabilitatea murrizteko (ia ezinezkoa da parametro optimoak kalkulatzea, baina enpirikoki joka dezakezu eta funtzionatzen ari zaren bitartean doi ditzakezu. );
  • DLQ biltegia etengabe kontrolatu behar da, eta monitorizazio sistemak sistema-administratzaileei abisatu behar die, entregatu gabeko mezuak gertatzen direnean, ahalik eta azkarren erantzun ditzaten. Horrek hutsegite edo negozio-logikako akats baten "eragindako eremua" murriztuko du;
  • integrazio-busak ez du izan behar aplikazioen aldi baterako ezaren aurrean: gai baten harpidetzak iraunkorrak izan behar du, eta aplikazioaren domeinu-izena bakarra izan behar da, aplikazioa ez dagoen bitartean, beste norbait ez dadin saiatuko bere mezuak prozesatzen. ilara.

Negozio-logikaren hari segurtasuna bermatzea

Negozio-prozesu baten instantzia berak hainbat mezu eta gertaera jaso ditzake aldi berean, eta horien prozesamendua paraleloan hasiko da. Aldi berean, aplikazioen garatzaile batentzat, dena sinplea eta hari segurua izan behar da.

Prozesu baten negozio-logikak negozio-prozesu horri eragiten dion kanpoko gertaera bakoitza banaka prozesatzen du. Horrelako gertaerak hauek izan daitezke:

  • negozio-prozesuaren instantzia bat abian jartzea;
  • erabiltzailearen ekintza negozio-prozesu bateko jarduerarekin lotutakoa;
  • negozio-prozesuaren instantzia bat harpidetuta dagoen mezu edo seinale bat jasotzea;
  • negozio-prozesuaren instantzia batek ezarritako tenporizadore bat abiaraztea;
  • API bidez kontrolatzeko ekintza (adibidez, prozesua etetea).

Gertaera bakoitzak negozio-prozesuaren instantzia baten egoera alda dezake: jarduera batzuk amaitu eta beste batzuk has daitezke, eta propietate iraunkorren balioak alda daitezke. Edozein jarduera ixteak honako jarduera hauetako bat edo gehiago aktibatzea eragin dezake. Horiek, bestalde, beste gertakari batzuen zain egoteari utzi diezaiokete edo, datu gehigarririk behar ez badute, transakzio berean osa dezakete. Transakzioa itxi aurretik, negozio-prozesuaren egoera berria datu-basean gordetzen da, non hurrengo kanpoko gertaera gertatu arte itxarongo den.

Datu-base erlazional batean gordetako negozio-prozesu iraunkorrak prozesatzea sinkronizatzeko oso puntu erosoa da SELECT FOR UPDATE erabiltzen baduzu. Transakzio batek negozio-prozesu baten egoera aldatzeko oinarritik lortu badu, ez da paraleloan beste transakziorik izango egoera bera lortuko beste aldaketa baterako, eta lehenengo transakzioa amaitu ondoren, bigarrena da. dagoeneko aldatutako egoera jasotzea bermatuta.

DBMS aldean blokeo ezkorrak erabiliz, beharrezko baldintza guztiak betetzen ditugu ACID, eta aplikazioa negozio-logikarekin eskalatzeko gaitasunari eusten dio, exekutatzen diren instantzia kopurua handituz.

Hala ere, blokeo ezkorrek blokeoekin mehatxatzen gaituzte, hau da, HAUTATU FOR UPDATE oraindik arrazoizko denbora-muga batera mugatu beharko litzateke negozio-logikako kasu larri batzuetan blokeoak gertatzen badira.

Beste arazo bat negozio prozesu baten hasierako sinkronizazioa da. Negozio prozesu baten instantziarik ez dagoen arren, datu-basean ez dago egoerarik, beraz, deskribatutako metodoak ez du funtzionatuko. Negozio-prozesu-instantzia baten berezitasuna esparru zehatz batean ziurtatu behar baduzu, prozesu-klasearekin eta dagokion esparruarekin lotutako sinkronizazio-objektu bat beharko duzu. Arazo hau konpontzeko, beste blokeo-mekanismo bat erabiltzen dugu, kanpoko zerbitzu baten bidez giltza baten bidez zehaztutako baliabide arbitrario bati blokeoa hartzeko aukera ematen diona.

Gure adibideetan, InitialPlayer negozio-prozesuak deklarazio bat dauka

uniqueConstraint = UniqueConstraints.singleton

Hori dela eta, erregistroak dagokion giltzaren blokeoa hartzeari eta askatzeari buruzko mezuak ditu. Ez dago horrelako mezurik beste negozio prozesuetarako: uniqueConstraint ez dago ezarrita.

Egoera iraunkorreko negozio-prozesuen arazoak

Batzuetan egoera iraunkor bat izateak laguntzen ez ezik, garapena oztopatzen du.
Arazoak negozio-logikan edo/eta negozio-prozesu-ereduan aldaketak egin behar direnean hasten dira. Aldaketa horiek guztiak ez dira bateragarriak negozio prozesuen egoera zaharrarekin. Datu-basean zuzeneko instantzia asko baldin badaude, aldaketa bateraezinak egiteak arazo asko sor ditzake, jBPM erabiltzean askotan aurkitu ditugunak.

Aldaketen sakontasunaren arabera, bi modutara joka dezakezu:

  1. negozio-prozesu mota berri bat sortu zaharrari aldaketa bateraezinak ez egiteko, eta erabili zaharraren ordez instantzia berriak abiarazteko orduan. Kopia zaharrek Β«lehen bezalaΒ» lanean jarraituko dute;
  2. negozio-prozesuen egoera iraunkorra migratu negozio-logika eguneratzean.

Lehenengo bidea sinpleagoa da, baina bere mugak eta desabantailak ditu, adibidez:

  • negozio-logikaren bikoizketa negozio-prozesu eredu askotan, negozio-logikaren bolumena handituz;
  • Sarritan negozio-logika berrirako berehalako trantsizioa behar da (integrazio-zereginei dagokienez - ia beti);
  • garatzaileak ez daki zein puntutan ezabatu daitezkeen eredu zaharkituak.

Praktikan bi ikuspegiak erabiltzen ditugu, baina gure bizitza errazteko hainbat erabaki hartu ditugu:

  • Datu-basean, negozio-prozesu baten egoera iraunkorra erraz irakurtzeko eta erraz prozesatzeko moduan gordetzen da: JSON formatuko kate batean. Horri esker, migrazioak aplikazio barruan zein kanpoan egin daitezke. Azken aukera gisa, eskuz zuzendu dezakezu (bereziki baliagarria garapenean arazketan);
  • integrazioko negozio-logikak ez ditu negozio-prozesuen izenak erabiltzen, eta, beraz, edozein unetan posible da parte hartzen duten prozesuetako baten ezarpena beste izen berri batekin ordeztea (adibidez, "InitialPlayerV2"). Lotura mezu eta seinale izenen bidez gertatzen da;
  • prozesu-ereduak bertsio-zenbaki bat du, eta eredu horretan aldaketa bateraezinak egiten baditugu gehitzen dugu, eta zenbaki hori prozesu-instantziaren egoerarekin batera gordetzen da;
  • prozesuaren egoera iraunkorra datu-basetik irakurtzen da lehenik objektu-eredu eroso batera, eta migrazio-prozedurak lan egin dezake ereduaren bertsio-zenbakia aldatu bada;
  • migrazio-prozedura negozio-logikaren ondoan jartzen da eta "alferra" deritzo negozio-prozesuaren instantzia bakoitzerako datu-basetik leheneratzen den unean;
  • prozesu-instantzia guztien egoera azkar eta sinkronoki migratu behar baduzu, datu-baseen migrazio-soluzio klasikoagoak erabiltzen dira, baina JSONrekin lan egin behar duzu.

Beste esparru bat behar al duzu negozio prozesuetarako?

Artikuluan deskribatutako konponbideek gure bizitza nabarmen erraztu, aplikazioen garapen mailan ebatzitako arazoen sorta zabaltzeko eta negozio-logika mikrozerbitzuetan bereizteko ideia erakargarriagoa izan dadin. Hori lortzeko, lan asko egin zen, negozio-prozesuetarako oso marko β€œarina” sortu zen, baita zerbitzu-osagaiak ere identifikatutako arazoak aplikazio-arazo ugariren testuinguruan konpontzeko. Emaitza hauek partekatzeko eta osagai komunen garapena lizentzia libre baten bidez sarbide irekia izan dadin nahi dugu. Horrek ahalegin eta denbora pixka bat eskatuko du. Horrelako irtenbideen eskaera ulertzea pizgarri gehigarri bat izan daiteke guretzat. Proposatutako artikuluan, oso arreta gutxi jartzen zaie esparruaren beraren gaitasunei, baina horietako batzuk ikusgai daude aurkeztutako adibideetatik. Gure esparrua argitaratzen badugu, aparteko artikulu bat eskainiko zaio. Bitartean, eskertuko genizuke galderari erantzunez iritzi txiki bat uztea:

Erregistratutako erabiltzaileek soilik parte hartu dezakete inkestan. Hasi saioa, mesedez.

Beste esparru bat behar al duzu negozio prozesuetarako?

  • 18,8%Bai, aspalditik nabil horrelako zerbaiten bila

  • 12,5%Zure inplementazioari buruz gehiago jakitea interesatzen zait, baliagarria izan liteke2

  • 6,2%Dauden esparruetako bat erabiltzen dugu, baina ordezkatzea pentsatzen ari gara1

  • 18,8%Dauden esparruetako bat erabiltzen dugu, dena ondo dago3

  • 18,8%markorik gabe kudeatzen dugu3

  • 25,0%idatzi zurea4

16 erabiltzailek eman dute botoa. 7 erabiltzaile abstenitu ziren.

Iturria: www.habr.com

Gehitu iruzkin berria