Pagsasama ng istilo ng BPM

Pagsasama ng istilo ng BPM

Kumusta Habr!

Ang aming kumpanya ay dalubhasa sa pagbuo ng ERP-class na mga solusyon sa software, kung saan ang malaking bahagi ay inookupahan ng mga transactional system na may malaking halaga ng business logic at workflow a la EDMS. Ang mga modernong bersyon ng aming mga produkto ay batay sa mga teknolohiya ng JavaEE, ngunit kami ay aktibong nag-eeksperimento sa mga microservice. Ang isa sa mga pinaka-problemadong lugar ng naturang mga solusyon ay ang pagsasama-sama ng iba't ibang mga subsystem na nauugnay sa mga katabing domain. Ang mga gawain sa pagsasama ay palaging nagbibigay sa amin ng malaking sakit ng ulo, anuman ang mga istilo ng arkitektura, mga stack ng teknolohiya at mga framework na ginagamit namin, ngunit kamakailan ay nagkaroon ng pag-unlad sa paglutas ng mga naturang problema.

Sa artikulong dinala sa iyong pansin, pag-uusapan ko ang karanasan at pananaliksik sa arkitektura ng NPO Krista sa itinalagang lugar. Isasaalang-alang din namin ang isang halimbawa ng isang simpleng solusyon sa isang problema sa pagsasama mula sa punto ng view ng isang developer ng application at alamin kung ano ang nakatago sa likod ng pagiging simple na ito.

Disclaimer

Ang mga solusyon sa arkitektura at teknikal na inilarawan sa artikulo ay inaalok ko batay sa personal na karanasan sa konteksto ng mga partikular na gawain. Ang mga solusyong ito ay hindi sinasabing pangkalahatan at maaaring hindi pinakamainam sa ilalim ng iba pang mga kondisyon ng paggamit.

Ano ang kinalaman ng BPM dito?

Upang masagot ang tanong na ito, kailangan nating suriin nang kaunti ang mga detalye ng mga inilapat na problema ng ating mga solusyon. Ang pangunahing bahagi ng lohika ng negosyo sa aming karaniwang transactional system ay ang pagpasok ng data sa database sa pamamagitan ng mga user interface, manu-mano at awtomatikong sinusuri ang data na ito, ipinapasa ito sa ilang daloy ng trabaho, pag-publish nito sa ibang system / analytical database / archive, pagbuo ng mga ulat. Kaya, ang pangunahing pag-andar ng system para sa mga customer ay ang automation ng kanilang mga panloob na proseso ng negosyo.

Para sa kaginhawahan, ginagamit namin ang terminong "dokumento" sa komunikasyon bilang ilang abstraction ng isang set ng data, na pinagsama ng isang karaniwang key, kung saan ang isang partikular na daloy ng trabaho ay maaaring "nakalakip".
Ngunit ano ang tungkol sa lohika ng pagsasama? Pagkatapos ng lahat, ang gawain ng pagsasama ay nabuo ng arkitektura ng system, na kung saan ay "sawed" sa mga bahagi HINDI sa kahilingan ng customer, ngunit sa ilalim ng impluwensya ng ganap na magkakaibang mga kadahilanan:

  • sa ilalim ng impluwensya ng batas ng Conway;
  • bilang resulta ng muling paggamit ng mga subsystem na dating binuo para sa iba pang mga produkto;
  • bilang napagpasyahan ng arkitekto, batay sa mga hindi gumaganang kinakailangan.

Mayroong isang mahusay na tukso na paghiwalayin ang logic ng pagsasama mula sa lohika ng negosyo ng pangunahing daloy ng trabaho upang hindi marumihan ang lohika ng negosyo na may mga artifact ng pagsasama at i-save ang developer ng application mula sa kinakailangang pag-aralan ang mga kakaibang tanawin ng arkitektura ng system. Ang diskarte na ito ay may isang bilang ng mga pakinabang, ngunit ang pagsasanay ay nagpapakita ng kawalan nito:

  • ang paglutas ng mga problema sa pagsasama ay kadalasang bumababa sa pinakasimpleng mga opsyon sa anyo ng mga magkakasabay na tawag dahil sa limitadong mga punto ng extension sa pagpapatupad ng pangunahing daloy ng trabaho (higit pa sa mga pagkukulang ng kasabay na pagsasama sa ibaba);
  • ang mga artifact ng integration ay tumagos pa rin sa pangunahing lohika ng negosyo kapag kinakailangan ang feedback mula sa isa pang subsystem;
  • binabalewala ng developer ng application ang pagsasama at madaling masira ito sa pamamagitan ng pagbabago sa daloy ng trabaho;
  • ang system ay tumigil na maging isang solong kabuuan mula sa pananaw ng gumagamit, ang "mga tahi" sa pagitan ng mga subsystem ay nagiging kapansin-pansin, ang mga kalabisan na operasyon ng gumagamit ay lilitaw na nagpapasimula ng paglipat ng data mula sa isang subsystem patungo sa isa pa.

Ang isa pang diskarte ay isaalang-alang ang mga pakikipag-ugnayan sa pagsasama bilang isang mahalagang bahagi ng pangunahing lohika ng negosyo at daloy ng trabaho. Upang panatilihin ang mga kinakailangan sa kasanayan ng mga developer ng application mula sa skyrocketing, ang paglikha ng mga bagong integrasyon na pakikipag-ugnayan ay dapat gawin nang madali at natural, na may kaunting mga pagpipilian para sa pagpili ng isang solusyon. Ito ay mas mahirap kaysa sa hitsura nito: ang tool ay dapat na sapat na malakas upang mabigyan ang gumagamit ng kinakailangang iba't ibang mga opsyon para sa paggamit nito at sa parehong oras ay hindi pinapayagan ang kanilang sarili na mabaril sa paa. Maraming tanong na dapat sagutin ng isang engineer sa konteksto ng mga gawain sa pagsasama, ngunit hindi dapat isipin ng isang developer ng application sa kanilang pang-araw-araw na gawain: mga hangganan ng transaksyon, pagkakapare-pareho, atomicity, seguridad, pag-scale, pag-load at pamamahagi ng mapagkukunan, pagruruta, pag-marshaling, pagpapalaganap at paglipat ng mga konteksto, atbp. Kinakailangang mag-alok sa mga developer ng application ng medyo simpleng mga template ng desisyon, kung saan nakatago na ang mga sagot sa lahat ng naturang tanong. Ang mga pattern na ito ay dapat na sapat na ligtas: ang lohika ng negosyo ay madalas na nagbabago, na nagpapataas ng panganib ng pagpapakilala ng mga error, ang halaga ng mga error ay dapat manatili sa isang medyo mababang antas.

Ngunit gayon pa man, ano ang kinalaman ng BPM dito? Mayroong maraming mga pagpipilian para sa pagpapatupad ng daloy ng trabaho ...
Sa katunayan, ang isa pang pagpapatupad ng mga proseso ng negosyo ay napakapopular sa aming mga solusyon - sa pamamagitan ng deklaratibong setting ng state transition diagram at pagkonekta ng mga humahawak na may lohika ng negosyo sa mga transition. Kasabay nito, ang estado na tumutukoy sa kasalukuyang posisyon ng "dokumento" sa proseso ng negosyo ay isang katangian ng "dokumento" mismo.

Pagsasama ng istilo ng BPM
Ganito ang hitsura ng proseso sa simula ng proyekto

Ang katanyagan ng naturang pagpapatupad ay dahil sa kamag-anak na pagiging simple at bilis ng paglikha ng mga linear na proseso ng negosyo. Gayunpaman, habang nagiging mas kumplikado ang mga software system, ang automated na bahagi ng proseso ng negosyo ay lumalaki at nagiging mas kumplikado. May pangangailangan para sa agnas, muling paggamit ng mga bahagi ng mga proseso, pati na rin ang mga proseso ng forking upang ang bawat sangay ay maisakatuparan nang magkatulad. Sa ilalim ng gayong mga kundisyon, ang tool ay nagiging hindi maginhawa, at ang state transition diagram ay nawawala ang nilalaman ng impormasyon nito (ang mga interaksyon ng pagsasama ay hindi makikita sa diagram).

Pagsasama ng istilo ng BPM
Ito ang hitsura ng proseso pagkatapos ng ilang pag-ulit ng paglilinaw sa mga kinakailangan

Ang paraan sa labas ng sitwasyong ito ay ang pagsasama ng makina jBPM sa ilang mga produkto na may pinakamasalimuot na proseso ng negosyo. Sa maikling panahon, ang solusyon na ito ay nagkaroon ng ilang tagumpay: naging posible na ipatupad ang mga kumplikadong proseso ng negosyo habang pinapanatili ang isang medyo nagbibigay-kaalaman at up-to-date na diagram sa notasyon BPMN2.

Pagsasama ng istilo ng BPM
Isang maliit na bahagi ng isang kumplikadong proseso ng negosyo

Sa mahabang panahon, ang solusyon ay hindi naabot ang mga inaasahan: ang mataas na lakas ng paggawa ng paglikha ng mga proseso ng negosyo sa pamamagitan ng mga visual na tool ay hindi nagpapahintulot sa pagkamit ng mga katanggap-tanggap na tagapagpahiwatig ng pagiging produktibo, at ang tool mismo ay naging isa sa mga pinaka-ayaw sa mga developer. Mayroon ding mga reklamo tungkol sa panloob na istraktura ng makina, na humantong sa paglitaw ng maraming "mga patch" at "mga saklay".

Ang pangunahing positibong aspeto ng paggamit ng jBPM ay ang pagsasakatuparan ng mga benepisyo at pinsala ng pagkakaroon ng sarili nitong patuloy na estado para sa isang halimbawa ng proseso ng negosyo. Nakita rin namin ang posibilidad ng paggamit ng diskarte sa proseso upang ipatupad ang mga kumplikadong protocol ng pagsasama sa pagitan ng iba't ibang mga application gamit ang mga asynchronous na pakikipag-ugnayan sa pamamagitan ng mga signal at mensahe. Ang pagkakaroon ng isang patuloy na estado ay gumaganap ng isang mahalagang papel dito.

Batay sa itaas, maaari nating tapusin: Ang diskarte sa proseso sa istilong BPM ay nagbibigay-daan sa amin upang malutas ang isang malawak na hanay ng mga gawain para sa pag-automate ng mas kumplikadong mga proseso ng negosyo, maayos na umaangkop sa mga aktibidad ng pagsasama sa mga prosesong ito at mapanatili ang kakayahang biswal na ipakita ang ipinatupad na proseso sa isang angkop na notasyon.

Mga disadvantages ng mga sabaysabay na tawag bilang isang pattern ng pagsasama

Ang kasabay na pagsasama ay tumutukoy sa pinakasimpleng pagharang na tawag. Ang isang subsystem ay gumaganap bilang bahagi ng server at inilalantad ang API gamit ang gustong paraan. Ang isa pang subsystem ay kumikilos bilang panig ng kliyente at, sa tamang oras, ay tumatawag nang may inaasahan ng isang resulta. Depende sa arkitektura ng system, ang panig ng kliyente at server ay maaaring i-host sa parehong aplikasyon at proseso, o sa magkaibang mga. Sa pangalawang kaso, kailangan mong ilapat ang ilang pagpapatupad ng RPC at magbigay ng marshalling ng mga parameter at ang resulta ng tawag.

Pagsasama ng istilo ng BPM

Ang ganitong pattern ng pagsasama ay may medyo malaking hanay ng mga disbentaha, ngunit ito ay napakalawak na ginagamit sa pagsasanay dahil sa pagiging simple nito. Ang bilis ng pagpapatupad ay nakakabighani at ginagawa mong ilapat ito nang paulit-ulit sa mga kondisyon ng "nasusunog" na mga deadline, na nagsusulat ng solusyon sa teknikal na utang. Ngunit nangyayari rin na ang mga walang karanasan na mga developer ay gumagamit nito nang hindi sinasadya, hindi lamang napagtatanto ang mga negatibong kahihinatnan.

Bilang karagdagan sa pinaka-halatang pagtaas sa pagkakakonekta ng mga subsystem, may mga hindi gaanong halatang problema sa "pagkalat" at "pag-uunat" na mga transaksyon. Sa katunayan, kung ang lohika ng negosyo ay gumawa ng anumang mga pagbabago, kung gayon ang mga transaksyon ay kailangang-kailangan, at ang mga transaksyon, sa turn, ay nakakandado ng ilang mga mapagkukunan ng application na apektado ng mga pagbabagong ito. Iyon ay, hanggang sa maghintay ang isang subsystem ng tugon mula sa isa pa, hindi nito makukumpleto ang transaksyon at ilalabas ang mga lock. Ito ay makabuluhang pinatataas ang panganib ng iba't ibang mga epekto:

  • nawala ang pagtugon ng system, ang mga user ay naghihintay ng mahabang panahon para sa mga sagot sa mga kahilingan;
  • karaniwang humihinto ang server sa pagtugon sa mga kahilingan ng user dahil sa umaapaw na thread pool: karamihan sa mga thread ay "nakatayo" sa lock ng resource na inookupahan ng transaksyon;
  • ang mga deadlock ay nagsisimulang lumitaw: ang posibilidad ng kanilang paglitaw ay lubos na nakasalalay sa tagal ng mga transaksyon, ang halaga ng lohika ng negosyo at mga kandado na kasangkot sa transaksyon;
  • lumilitaw ang mga error sa pag-expire ng timeout ng transaksyon;
  • ang server ay "bumagsak" sa OutOfMemory kung ang gawain ay nangangailangan ng pagproseso at pagbabago ng malaking halaga ng data, at ang pagkakaroon ng magkakasabay na pagsasama ay nagpapahirap na hatiin ang pagproseso sa "mas magaan" na mga transaksyon.

Mula sa pananaw ng arkitektura, ang paggamit ng pagharang sa mga tawag sa panahon ng pagsasama ay humahantong sa pagkawala ng kontrol sa kalidad ng mga indibidwal na subsystem: imposibleng matiyak ang kalidad ng mga target ng isang subsystem na nakahiwalay mula sa mga target na kalidad ng isa pang subsystem. Kung ang mga subsystem ay binuo ng iba't ibang mga koponan, ito ay isang malaking problema.

Ang mga bagay ay nagiging mas kawili-wili kung ang mga subsystem na isinama ay nasa iba't ibang mga aplikasyon at ang mga magkakasabay na pagbabago ay kailangang gawin sa magkabilang panig. Paano gawing transactional ang mga pagbabagong ito?

Kung ang mga pagbabago ay ginawa sa magkahiwalay na mga transaksyon, kung gayon ang matatag na paghawak sa pagbubukod at kabayaran ay kailangang ibigay, at ganap nitong inaalis ang pangunahing bentahe ng magkakasabay na pagsasama - pagiging simple.

Naiisip din ang mga ipinamamahaging transaksyon, ngunit hindi namin ginagamit ang mga ito sa aming mga solusyon: mahirap tiyakin ang pagiging maaasahan.

"Saga" bilang solusyon sa problema ng mga transaksyon

Sa lumalagong katanyagan ng mga microservice, tumataas ang pangangailangan para sa Pattern ng Saga.

Ang pattern na ito ay perpektong nilulutas ang mga problema sa itaas ng mahabang transaksyon, at pinalawak din ang mga posibilidad ng pamamahala sa estado ng system mula sa panig ng lohika ng negosyo: ang kabayaran pagkatapos ng isang hindi matagumpay na transaksyon ay maaaring hindi ibalik ang system sa orihinal nitong estado, ngunit magbigay ng isang alternatibo ruta ng pagproseso ng data. Nagbibigay-daan din ito sa iyo na huwag ulitin ang matagumpay na nakumpletong mga hakbang sa pagproseso ng data kapag sinubukan mong dalhin ang proseso sa isang "magandang" pagtatapos.

Kapansin-pansin, sa mga monolithic system, ang pattern na ito ay may kaugnayan din pagdating sa integration ng loosely coupled subsystems at may mga negatibong epekto na dulot ng mahabang transaksyon at ang kaukulang resource lock.

Tungkol sa aming mga proseso ng negosyo sa istilong BPM, lumalabas na napakadaling ipatupad ang Sagas: ang mga indibidwal na hakbang ng Sagas ay maaaring itakda bilang mga aktibidad sa loob ng proseso ng negosyo, at ang patuloy na estado ng proseso ng negosyo ay tumutukoy, kasama ng iba pang mga bagay, ang panloob na estado ng Sagas. Ibig sabihin, hindi namin kailangan ng anumang karagdagang mekanismo ng koordinasyon. Ang kailangan mo lang ay isang message broker na may suporta para sa "kahit isang beses" na garantiya bilang isang transportasyon.

Ngunit ang gayong solusyon ay mayroon ding sariling "presyo":

  • nagiging mas kumplikado ang lohika ng negosyo: kailangan mong gumawa ng kabayaran;
  • kakailanganing iwanan ang buong pagkakapare-pareho, na maaaring maging sensitibo lalo na para sa mga monolitikong sistema;
  • ang arkitektura ay nagiging mas kumplikado, mayroong karagdagang pangangailangan para sa isang broker ng mensahe;
  • kakailanganin ang karagdagang mga tool sa pagsubaybay at pangangasiwa (bagaman sa pangkalahatan ito ay mabuti pa: tataas ang kalidad ng serbisyo ng system).

Para sa mga monolitikong sistema, ang katwiran para sa paggamit ng "Sags" ay hindi masyadong halata. Para sa mga microservice at iba pang mga SOA, kung saan, malamang, mayroon nang isang broker, at ang buong pagkakapare-pareho ay isinakripisyo sa simula ng proyekto, ang mga benepisyo ng paggamit ng pattern na ito ay maaaring makabuluhang lumampas sa mga disadvantages, lalo na kung mayroong isang maginhawang API sa antas ng lohika ng negosyo.

Encapsulation ng business logic sa microservices

Noong nagsimula kaming mag-eksperimento sa mga microservice, lumitaw ang isang makatwirang tanong: saan ilalagay ang lohika ng negosyo ng domain kaugnay ng serbisyong nagbibigay ng pagtitiyaga ng data ng domain?

Kapag tinitingnan ang arkitektura ng iba't ibang BPMS, maaaring mukhang makatwirang paghiwalayin ang lohika ng negosyo mula sa pagtitiyaga: lumikha ng isang layer ng platform at mga domain-independent na microservice na bumubuo sa kapaligiran at lalagyan para sa pagpapatupad ng lohika ng negosyo ng domain, at ayusin ang pagtitiyaga ng data ng domain bilang isang hiwalay layer ng napakasimple at magaan na microservice. Ang mga proseso ng negosyo sa kasong ito ay nag-oorkestrate ng mga serbisyo ng layer ng pagtitiyaga.

Pagsasama ng istilo ng BPM

Ang diskarte na ito ay may napakalaking plus: maaari mong dagdagan ang pag-andar ng platform hangga't gusto mo, at tanging ang kaukulang layer ng mga microservice ng platform ang "mataba" mula dito. Ang mga proseso ng negosyo mula sa anumang domain ay agad na nakakakuha ng pagkakataon na gamitin ang bagong functionality ng platform sa sandaling ito ay na-update.

Ang isang mas detalyadong pag-aaral ay nagsiwalat ng mga makabuluhang pagkukulang ng pamamaraang ito:

  • isang serbisyo sa platform na nagpapatupad ng lohika ng negosyo ng maraming domain nang sabay-sabay na nagdadala ng malalaking panganib bilang isang punto ng pagkabigo. Ang mga madalas na pagbabago sa lohika ng negosyo ay nagpapataas ng panganib ng mga bug na humahantong sa mga pagkabigo sa buong system;
  • mga isyu sa pagganap: gumagana ang logic ng negosyo sa data nito sa pamamagitan ng makitid at mabagal na interface:
    • ang data ay muling i-marshall at pumped sa pamamagitan ng network stack;
    • ang serbisyo ng domain ay madalas na magbabalik ng higit pang data kaysa sa kinakailangan ng lohika ng negosyo para sa pagproseso, dahil sa hindi sapat na mga kakayahan ng parameterization ng query sa antas ng panlabas na API ng serbisyo;
    • ilang mga independiyenteng piraso ng lohika ng negosyo ay maaaring paulit-ulit na humiling ng parehong data para sa pagproseso (maaari mong pagaanin ang problemang ito sa pamamagitan ng pagdaragdag ng mga session beans na nag-cache ng data, ngunit ito ay higit na nagpapalubha sa arkitektura at lumilikha ng mga problema sa pagiging bago ng data at pagkawala ng bisa ng cache);
  • mga isyu sa transaksyon:
    • ang mga proseso ng negosyo na may patuloy na estado na nakaimbak ng serbisyo ng platform ay hindi naaayon sa data ng domain, at walang madaling paraan upang malutas ang problemang ito;
    • ang paglipat ng lock ng data ng domain sa labas ng transaksyon: kung ang lohika ng negosyo ng domain ay kailangang gumawa ng mga pagbabago, pagkatapos munang suriin ang kawastuhan ng aktwal na data, kinakailangang ibukod ang posibilidad ng isang mapagkumpitensyang pagbabago sa naprosesong data. Ang panlabas na pagharang ng data ay maaaring makatulong sa paglutas ng problema, ngunit ang ganitong solusyon ay nagdadala ng mga karagdagang panganib at binabawasan ang pangkalahatang pagiging maaasahan ng system;
  • karagdagang mga komplikasyon kapag nag-a-update: sa ilang mga kaso, kailangan mong i-update ang serbisyo sa pagtitiyaga at lohika ng negosyo nang sabay-sabay o sa mahigpit na pagkakasunud-sunod.

Sa huli, kailangan kong bumalik sa mga pangunahing kaalaman: i-encapsulate ang data ng domain at lohika ng negosyo ng domain sa isang microservice. Pinapasimple ng diskarteng ito ang pang-unawa ng microservice bilang isang mahalagang bahagi sa system at hindi nagdudulot ng mga problema sa itaas. Hindi rin ito libre:

  • Kinakailangan ang standardisasyon ng API para sa pakikipag-ugnayan sa lohika ng negosyo (sa partikular, upang magbigay ng mga aktibidad ng user bilang bahagi ng mga proseso ng negosyo) at mga serbisyo ng platform ng API; mas maingat na atensyon sa mga pagbabago sa API, kailangan ang pasulong at paatras na pagkakatugma;
  • kinakailangan na magdagdag ng mga karagdagang runtime na aklatan upang matiyak ang paggana ng lohika ng negosyo bilang bahagi ng bawat naturang microservice, at ito ay nagbibigay ng mga bagong kinakailangan para sa naturang mga aklatan: kagaanan at isang minimum na transitive dependencies;
  • kailangan ng mga developer ng business logic na subaybayan ang mga bersyon ng library: kung ang isang microservice ay hindi pa natatapos sa mahabang panahon, malamang na naglalaman ito ng hindi napapanahong bersyon ng mga library. Maaari itong maging isang hindi inaasahang balakid sa pagdaragdag ng bagong feature at maaaring mangailangan ng lumang lohika ng negosyo ng naturang serbisyo na ilipat sa mga bagong bersyon ng mga aklatan kung may mga hindi tugmang pagbabago sa pagitan ng mga bersyon.

Pagsasama ng istilo ng BPM

Ang isang layer ng mga serbisyo sa platform ay naroroon din sa naturang arkitektura, ngunit ang layer na ito ay hindi na bumubuo ng isang lalagyan para sa pagpapatupad ng lohika ng negosyo ng domain, ngunit ang kapaligiran lamang nito, na nagbibigay ng mga pantulong na "platform" na pag-andar. Ang ganitong layer ay kinakailangan hindi lamang upang mapanatili ang kagaanan ng mga microservice ng domain, kundi pati na rin upang isentro ang pamamahala.

Halimbawa, ang mga aktibidad ng user sa mga proseso ng negosyo ay bumubuo ng mga gawain. Gayunpaman, kapag nagtatrabaho sa mga gawain, dapat makita ng user ang mga gawain mula sa lahat ng domain sa pangkalahatang listahan, na nangangahulugan na dapat mayroong naaangkop na serbisyo sa platform ng pagpaparehistro ng gawain, na na-clear sa lohika ng negosyo ng domain. Ang pagpapanatili ng encapsulation ng lohika ng negosyo sa kontekstong ito ay medyo may problema, at ito ay isa pang kompromiso ng arkitektura na ito.

Pagsasama-sama ng mga proseso ng negosyo sa pamamagitan ng mga mata ng isang developer ng application

Tulad ng nabanggit na sa itaas, ang developer ng application ay dapat na i-abstract mula sa mga teknikal at engineering na tampok ng pagpapatupad ng pakikipag-ugnayan ng ilang mga application upang mabilang sa mahusay na produktibo sa pag-unlad.

Subukan nating lutasin ang isang medyo mahirap na problema sa pagsasama, na espesyal na imbento para sa artikulo. Ito ay magiging isang "laro" na gawain na kinasasangkutan ng tatlong application, kung saan ang bawat isa sa kanila ay tumutukoy sa ilang domain name: "app1", "app2", "app3".

Sa loob ng bawat aplikasyon, inilunsad ang mga proseso ng negosyo na nagsisimulang "maglaro ng bola" sa pamamagitan ng integration bus. Ang mga mensaheng pinangalanang "Ball" ay gaganap bilang bola.

Panuntunan ng laro:

  • ang unang manlalaro ay ang nagpasimula. Inaanyayahan niya ang iba pang mga manlalaro sa laro, simulan ang laro at maaaring tapusin ito anumang oras;
  • idineklara ng ibang mga manlalaro ang kanilang pakikilahok sa laro, "kilalanin" ang isa't isa at ang unang manlalaro;
  • pagkatapos matanggap ang bola, pipili ang manlalaro ng isa pang kalahok na manlalaro at ipapasa ang bola sa kanya. Ang kabuuang bilang ng mga pumasa ay binibilang;
  • bawat manlalaro ay may "enerhiya", na bumababa sa bawat pagpasa ng bola ng manlalarong iyon. Kapag naubos na ang enerhiya, ang manlalaro ay aalisin sa laro, na nagpapahayag ng kanilang pagreretiro;
  • kung ang manlalaro ay naiwang mag-isa, agad niyang idineklara ang kanyang pag-alis;
  • kapag naalis ang lahat ng manlalaro, idineklara ng unang manlalaro ang pagtatapos ng laro. Kung umalis siya sa laro nang mas maaga, pagkatapos ay nananatili itong sundin ang laro upang makumpleto ito.

Upang malutas ang problemang ito, gagamitin ko ang aming DSL para sa mga proseso ng negosyo, na nagbibigay-daan sa iyong ilarawan ang lohika sa Kotlin nang compact, na may pinakamababang boilerplate.

Sa application ng app1, gagana ang proseso ng negosyo ng unang manlalaro (siya rin ang nagpasimula ng laro):

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

Bilang karagdagan sa pagpapatupad ng lohika ng negosyo, ang code sa itaas ay maaaring gumawa ng object model ng isang proseso ng negosyo na maaaring makita bilang isang diagram. Hindi pa namin ipinapatupad ang visualizer, kaya kailangan naming gumugol ng ilang oras sa pagguhit (dito ko bahagyang pinasimple ang notasyon ng BPMN tungkol sa paggamit ng mga gate upang mapabuti ang pagkakapare-pareho ng diagram sa code sa itaas):

Pagsasama ng istilo ng BPM

isasama ng app2 ang proseso ng negosyo ng isa pang manlalaro:

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

Diagram:

Pagsasama ng istilo ng BPM

Sa app3 application, gagawin namin ang player na may bahagyang naiibang pag-uugali: sa halip na random na piliin ang susunod na player, kikilos siya ayon sa round-robin algorithm:

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

Kung hindi, ang pag-uugali ng manlalaro ay hindi naiiba sa nauna, kaya hindi nagbabago ang diagram.

Ngayon kailangan namin ng isang pagsubok upang patakbuhin ang lahat. Ibibigay ko lamang ang code ng pagsubok mismo, upang hindi masira ang artikulo sa isang boilerplate (sa katunayan, ginamit ko ang kapaligiran ng pagsubok na nilikha nang mas maaga upang subukan ang pagsasama ng iba pang mga proseso ng negosyo):

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

Patakbuhin ang pagsubok, tingnan ang log:

output ng console

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

Maraming mahahalagang konklusyon ang maaaring makuha mula sa lahat ng ito:

  • kung ang mga kinakailangang tool ay magagamit, ang mga developer ng application ay maaaring lumikha ng mga integrasyong pakikipag-ugnayan sa pagitan ng mga application nang hindi humihiwalay sa lohika ng negosyo;
  • ang pagiging kumplikado (complexity) ng isang gawain sa pagsasanib na nangangailangan ng mga kakayahan sa engineering ay maaaring maitago sa loob ng balangkas kung ito ay unang inilatag sa arkitektura ng balangkas. Ang kahirapan ng gawain (kahirapan) ay hindi maitatago, kaya ang solusyon sa isang mahirap na gawain sa code ay magiging hitsura nang naaayon;
  • kapag bumubuo ng lohika ng pagsasama, kinakailangang isaalang-alang sa huli ang pagkakapare-pareho at ang kakulangan ng linearizability ng pagbabago ng estado ng lahat ng mga kalahok sa pagsasama. Pinipilit tayo nitong gawing kumplikado ang lohika upang gawin itong hindi sensitibo sa pagkakasunud-sunod kung saan nangyayari ang mga panlabas na kaganapan. Sa aming halimbawa, ang manlalaro ay napipilitang makilahok sa laro pagkatapos niyang ianunsyo ang kanyang pag-alis sa laro: ang ibang mga manlalaro ay patuloy na ipapasa sa kanya ang bola hanggang sa ang impormasyon tungkol sa kanyang paglabas ay umabot at naproseso ng lahat ng mga kalahok. Ang lohika na ito ay hindi sumusunod sa mga patakaran ng laro at isang kompromiso na solusyon sa loob ng balangkas ng napiling arkitektura.

Susunod, pag-usapan natin ang iba't ibang mga subtleties ng aming solusyon, mga kompromiso at iba pang mga punto.

Lahat ng mensahe sa isang pila

Gumagana ang lahat ng pinagsamang application sa isang integration bus, na ipinakita bilang isang external na broker, isang BPMQueue para sa mga mensahe at isang BPMTopic na paksa para sa mga signal (mga kaganapan). Ang pagpasa sa lahat ng mensahe sa iisang pila ay isang kompromiso mismo. Sa antas ng lohika ng negosyo, maaari ka na ngayong magpakilala ng maraming bagong uri ng mga mensahe hangga't gusto mo nang hindi gumagawa ng mga pagbabago sa istraktura ng system. Ito ay isang makabuluhang pagpapagaan, ngunit nagdadala ito ng ilang mga panganib, na, sa konteksto ng aming mga tipikal na gawain, tila sa amin ay hindi gaanong mahalaga.

Pagsasama ng istilo ng BPM

Gayunpaman, mayroong isang subtlety dito: sinasala ng bawat application ang "nito" na mga mensahe mula sa pila sa pasukan, sa pamamagitan ng pangalan ng domain nito. Gayundin, maaaring tukuyin ang domain sa mga signal, kung kailangan mong limitahan ang "saklaw" ng signal sa isang application. Dapat nitong taasan ang bandwidth ng bus, ngunit ang lohika ng negosyo ay dapat na ngayong gumana sa mga pangalan ng domain: mandatory para sa pagtugon sa mga mensahe, kanais-nais para sa mga signal.

Tinitiyak ang pagiging maaasahan ng integration bus

Ang pagiging maaasahan ay binubuo ng ilang bagay:

  • Ang napiling broker ng mensahe ay isang kritikal na bahagi ng arkitektura at isang punto ng kabiguan: dapat itong sapat na mapagparaya. Dapat mong gamitin lamang ang nasubok sa oras na mga pagpapatupad na may mahusay na suporta at isang malaking komunidad;
  • kinakailangan upang matiyak ang mataas na kakayahang magamit ng broker ng mensahe, kung saan dapat itong pisikal na ihiwalay mula sa mga pinagsama-samang aplikasyon (ang mataas na kakayahang magamit ng mga aplikasyon na may inilapat na lohika ng negosyo ay mas mahirap at mahal na ibigay);
  • obligado ang broker na magbigay ng "kahit isang beses" na mga garantiya sa paghahatid. Ito ay isang ipinag-uutos na kinakailangan para sa maaasahang operasyon ng integration bus. Hindi na kailangan para sa "eksaktong isang beses" na mga garantiya sa antas: ang mga proseso ng negosyo ay karaniwang hindi sensitibo sa paulit-ulit na pagdating ng mga mensahe o kaganapan, at sa mga espesyal na gawain kung saan ito ay mahalaga, mas madaling magdagdag ng mga karagdagang pagsusuri sa lohika ng negosyo kaysa sa patuloy na paggamit sa halip "mahal" " mga garantiya;
  • ang pagpapadala ng mga mensahe at signal ay dapat na kasangkot sa isang karaniwang transaksyon na may pagbabago sa estado ng mga proseso ng negosyo at data ng domain. Ang ginustong opsyon ay ang paggamit ng pattern Transaksyonal na Outbox, ngunit mangangailangan ito ng karagdagang talahanayan sa database at isang relay. Sa mga aplikasyon ng JEE, maaari itong gawing simple sa pamamagitan ng paggamit ng isang lokal na tagapamahala ng JTA, ngunit ang koneksyon sa napiling broker ay dapat na gumana sa mode XA;
  • ang mga humahawak ng mga papasok na mensahe at mga kaganapan ay dapat ding gumana sa transaksyon ng pagbabago ng estado ng proseso ng negosyo: kung ang naturang transaksyon ay i-roll back, kung gayon ang pagtanggap ng mensahe ay dapat ding kanselahin;
  • ang mga mensahe na hindi maihatid dahil sa mga error ay dapat na nakaimbak sa isang hiwalay na tindahan DLQ (Dead Letter Queue). Upang gawin ito, gumawa kami ng hiwalay na platform microservice na nag-iimbak ng mga naturang mensahe sa storage nito, nag-i-index sa mga ito ayon sa mga katangian (para sa mabilis na pagpapangkat at paghahanap), at inilalantad ang API para sa pagtingin, muling pagpapadala sa patutunguhang address, at pagtanggal ng mga mensahe. Maaaring gumana ang mga system administrator sa serbisyong ito sa pamamagitan ng kanilang web interface;
  • sa mga setting ng broker, kailangan mong ayusin ang bilang ng mga muling pagsubok sa paghahatid at pagkaantala sa pagitan ng mga paghahatid upang mabawasan ang posibilidad ng mga mensahe na makapasok sa DLQ (halos imposibleng kalkulahin ang pinakamainam na mga parameter, ngunit maaari kang kumilos nang empirically at ayusin ang mga ito sa panahon ng operasyon);
  • ang tindahan ng DLQ ay dapat na patuloy na subaybayan, at ang sistema ng pagsubaybay ay dapat na abisuhan ang mga tagapangasiwa ng system upang makatugon sila nang mabilis hangga't maaari kapag naganap ang mga hindi naihatid na mensahe. Ito ay magbabawas sa "damage zone" ng isang pagkabigo o error sa lohika ng negosyo;
  • ang integration bus ay dapat na insensitive sa pansamantalang kawalan ng mga application: ang mga subscription sa paksa ay dapat na matibay, at ang domain name ng application ay dapat na natatangi upang ang ibang tao ay hindi subukang iproseso ang mensahe nito mula sa queue habang wala ang application.

Tinitiyak ang kaligtasan ng thread ng lohika ng negosyo

Ang parehong halimbawa ng isang proseso ng negosyo ay maaaring makatanggap ng ilang mga mensahe at kaganapan nang sabay-sabay, ang pagproseso nito ay magsisimula nang magkatulad. Kasabay nito, para sa isang developer ng application, ang lahat ay dapat na simple at ligtas sa thread.

Pinoproseso ng lohika ng proseso ng negosyo ang bawat panlabas na kaganapan na indibidwal na nakakaapekto sa proseso ng negosyong ito. Ang mga kaganapang ito ay maaaring:

  • paglulunsad ng isang halimbawa ng proseso ng negosyo;
  • isang pagkilos ng user na nauugnay sa isang aktibidad sa loob ng proseso ng negosyo;
  • pagtanggap ng mensahe o senyales kung saan naka-subscribe ang isang halimbawa ng proseso ng negosyo;
  • pag-expire ng timer na itinakda ng halimbawa ng proseso ng negosyo;
  • kontrolin ang pagkilos sa pamamagitan ng API (hal. pag-abort ng proseso).

Ang bawat naturang kaganapan ay maaaring magbago ng estado ng isang halimbawa ng proseso ng negosyo: ang ilang mga aktibidad ay maaaring magwakas at ang iba ay magsisimula, ang mga halaga ng mga patuloy na katangian ay maaaring magbago. Ang pagsasara ng anumang aktibidad ay maaaring magresulta sa pag-activate ng isa o higit pa sa mga sumusunod na aktibidad. Ang mga iyon, sa turn, ay maaaring huminto sa paghihintay para sa iba pang mga kaganapan, o, kung hindi nila kailangan ng anumang karagdagang data, maaari silang kumpletuhin sa parehong transaksyon. Bago isara ang transaksyon, ang bagong estado ng proseso ng negosyo ay naka-imbak sa database, kung saan maghihintay ito para sa susunod na panlabas na kaganapan.

Ang tuluy-tuloy na data ng proseso ng negosyo na naka-imbak sa isang relational database ay isang napaka-maginhawang processing synchronization point kapag gumagamit ng SELECT FOR UPDATE. Kung ang isang transaksyon ay pinamamahalaang upang makuha ang estado ng proseso ng negosyo mula sa database upang baguhin ito, kung gayon walang ibang transaksyon na magkatulad ang makakakuha ng parehong estado para sa isa pang pagbabago, at pagkatapos makumpleto ang unang transaksyon, ang pangalawa ay garantisadong matatanggap ang nabago nang estado.

Gamit ang mga pessimistic lock sa gilid ng DBMS, tinutupad namin ang lahat ng kinakailangang kinakailangan ACID, at panatilihin din ang kakayahang palakihin ang application gamit ang lohika ng negosyo sa pamamagitan ng pagtaas ng bilang ng mga tumatakbong pagkakataon.

Gayunpaman, ang mga pessimistic na lock ay nagbabanta sa amin ng mga deadlock, na nangangahulugan na ang PUMILI PARA SA I-UPDATE ay dapat pa ring limitado sa ilang makatwirang timeout kung sakaling magkaroon ng deadlock sa ilang malalang kaso sa lohika ng negosyo.

Ang isa pang problema ay ang pag-synchronize ng pagsisimula ng proseso ng negosyo. Bagama't walang halimbawa ng proseso ng negosyo, wala ring estado sa database, kaya hindi gagana ang inilarawang paraan. Kung gusto mong tiyakin ang pagiging natatangi ng isang halimbawa ng proseso ng negosyo sa isang partikular na saklaw, kailangan mo ng ilang uri ng bagay sa pag-synchronize na nauugnay sa klase ng proseso at sa kaukulang saklaw. Upang malutas ang problemang ito, gumagamit kami ng ibang mekanismo ng pag-lock na nagbibigay-daan sa amin na kumuha ng lock sa isang arbitrary na mapagkukunan na tinukoy ng isang key sa URI na format sa pamamagitan ng isang panlabas na serbisyo.

Sa aming mga halimbawa, ang proseso ng negosyo ng InitialPlayer ay naglalaman ng isang deklarasyon

uniqueConstraint = UniqueConstraints.singleton

Samakatuwid, ang log ay naglalaman ng mga mensahe tungkol sa pagkuha at pagpapakawala ng lock ng kaukulang key. Walang ganoong mensahe para sa ibang mga proseso ng negosyo: hindi nakatakda ang uniqueConstraint.

Mga problema sa proseso ng negosyo na may patuloy na estado

Minsan ang pagkakaroon ng patuloy na estado ay hindi lamang nakakatulong, ngunit talagang humahadlang sa pag-unlad.
Magsisimula ang mga problema kapag kailangan mong gumawa ng mga pagbabago sa lohika ng negosyo at/o modelo ng proseso ng negosyo. Walang anumang naturang pagbabago ang nakitang tumutugma sa lumang estado ng mga proseso ng negosyo. Kung mayroong maraming "live" na mga pagkakataon sa database, ang paggawa ng mga hindi tugmang pagbabago ay maaaring magdulot ng maraming problema, na madalas nating nararanasan kapag gumagamit ng jBPM.

Depende sa lalim ng pagbabago, maaari kang kumilos sa dalawang paraan:

  1. lumikha ng bagong uri ng proseso ng negosyo upang hindi makagawa ng mga hindi tugmang pagbabago sa luma, at gamitin ito sa halip na ang luma kapag nagsisimula ng mga bagong pagkakataon. Ang mga lumang pagkakataon ay patuloy na gagana "sa lumang paraan";
  2. ilipat ang patuloy na estado ng mga proseso ng negosyo kapag ina-update ang lohika ng negosyo.

Ang unang paraan ay mas simple, ngunit may mga limitasyon at disadvantage nito, halimbawa:

  • pagdoble ng lohika ng negosyo sa maraming mga modelo ng proseso ng negosyo, isang pagtaas sa dami ng lohika ng negosyo;
  • madalas ay kinakailangan ang isang agarang paglipat sa isang bagong lohika ng negosyo (halos palagi sa mga tuntunin ng mga gawain sa pagsasama);
  • hindi alam ng developer kung saang punto posibleng tanggalin ang mga hindi na ginagamit na modelo.

Sa pagsasagawa, ginagamit namin ang parehong mga diskarte, ngunit gumawa ng ilang mga desisyon upang pasimplehin ang aming mga buhay:

  • sa database, ang tuluy-tuloy na estado ng proseso ng negosyo ay naka-imbak sa isang madaling mabasa at madaling maproseso na form: sa isang JSON format string. Nagbibigay-daan ito sa iyong magsagawa ng mga paglilipat sa loob ng application at sa labas. Sa matinding mga kaso, maaari mo ring i-tweak ito gamit ang mga hawakan (lalo na kapaki-pakinabang sa pag-unlad sa panahon ng pag-debug);
  • ang logic ng negosyo ng integration ay hindi gumagamit ng mga pangalan ng mga proseso ng negosyo, upang sa anumang oras posible na palitan ang pagpapatupad ng isa sa mga kalahok na proseso ng isang bago, na may bagong pangalan (halimbawa, "InitialPlayerV2"). Ang pagbubuklod ay nangyayari sa pamamagitan ng mga pangalan ng mga mensahe at signal;
  • ang modelo ng proseso ay may numero ng bersyon, na dinaragdagan namin kung gagawa kami ng mga hindi tugmang pagbabago sa modelong ito, at ang numerong ito ay nakaimbak kasama ng estado ng instance ng proseso;
  • ang patuloy na estado ng proseso ay binabasa mula sa base muna sa isang maginhawang modelo ng bagay na maaaring gumana ang pamamaraan ng paglipat kung nagbago ang numero ng bersyon ng modelo;
  • ang pamamaraan ng paglipat ay inilalagay sa tabi ng lohika ng negosyo at tinatawag na "tamad" para sa bawat pagkakataon ng proseso ng negosyo sa oras ng pagpapanumbalik nito mula sa database;
  • kung kailangan mong i-migrate ang estado ng lahat ng mga instance ng proseso nang mabilis at sabay-sabay, mas maraming klasikong solusyon sa paglilipat ng database ang ginagamit, ngunit kailangan mong magtrabaho kasama ang JSON doon.

Kailangan ko ba ng isa pang balangkas para sa mga proseso ng negosyo?

Ang mga solusyon na inilarawan sa artikulo ay nagpapahintulot sa amin na makabuluhang gawing simple ang aming mga buhay, palawakin ang hanay ng mga isyu na nalutas sa antas ng pagbuo ng application, at gawing mas kaakit-akit ang ideya ng paghihiwalay ng lohika ng negosyo sa mga microservice. Para dito, maraming trabaho ang nagawa, isang napaka "magaan" na balangkas para sa mga proseso ng negosyo ay nilikha, pati na rin ang mga bahagi ng serbisyo para sa paglutas ng mga natukoy na problema sa konteksto ng isang malawak na hanay ng mga inilapat na gawain. Mayroon kaming pagnanais na ibahagi ang mga resultang ito, upang dalhin ang pagbuo ng mga karaniwang bahagi sa bukas na pag-access sa ilalim ng isang libreng lisensya. Mangangailangan ito ng ilang pagsisikap at oras. Ang pag-unawa sa pangangailangan para sa mga naturang solusyon ay maaaring maging karagdagang insentibo para sa amin. Sa iminungkahing artikulo, napakakaunting pansin ang binabayaran sa mga kakayahan ng mismong balangkas, ngunit ang ilan sa mga ito ay makikita mula sa mga halimbawang ipinakita. Kung maglalathala pa kami ng aming balangkas, isang hiwalay na artikulo ang ilalaan dito. Pansamantala, magpapasalamat kami kung mag-iiwan ka ng kaunting feedback sa pamamagitan ng pagsagot sa tanong:

Ang mga rehistradong user lamang ang maaaring lumahok sa survey. Mag-sign in, pakiusap

Kailangan ko ba ng isa pang balangkas para sa mga proseso ng negosyo?

  • 18,8%Oo, matagal na akong naghahanap ng ganito.

  • 12,5%ito ay kagiliw-giliw na matuto nang higit pa tungkol sa iyong pagpapatupad, maaaring ito ay kapaki-pakinabang2

  • 6,2%ginagamit namin ang isa sa mga umiiral na frameworks, ngunit iniisip namin na palitan ito1

  • 18,8%ginagamit namin ang isa sa mga umiiral na balangkas, lahat ay nababagay3

  • 18,8%pagharap nang walang balangkas3

  • 25,0%sumulat ng iyong sarili4

16 user ang bumoto. 7 na user ang umiwas.

Pinagmulan: www.habr.com

Magdagdag ng komento