Integrazione di stile BPM

Integrazione di stile BPM

Bonghjornu, Habr!

A nostra cumpagnia hè specializata in u sviluppu di suluzioni di software di classi ERP, in quale a parte di u leone hè occupata da sistemi transazzione cù una quantità enorme di logica cummerciale è flussu di travagliu à l'EDMS. E versioni muderni di i nostri prudutti sò basati nantu à tecnulugii JavaEE, ma avemu ancu sperimentatu attivamente cù i microservizi. Una di e zone più problematiche di tali suluzioni hè l'integrazione di diversi sottosistemi ligati à i domini adiacenti. I travaglii d'integrazione ci anu sempre datu un grande mal di testa, indipendentemente da i stili architettonici, stacks di tecnulugia è frameworks chì usemu, ma recentemente ci hè statu prugressu in a risoluzione di tali prublemi.

In l'articulu purtatu à a vostra attenzione, parleraghju di l'esperienza è a ricerca architettonica di NPO Krista in l'area designata. Avemu da cunsiderà ancu un esempiu di una suluzione simplice à un prublema d'integrazione da u puntu di vista di un sviluppatore di l'applicazione è scopre ciò chì si nasconde daretu à sta simplicità.

Disclaimer

E suluzioni architettoniche è tecniche descritte in l'articulu sò pruposti da mè basatu annantu à l'esperienza persunale in u cuntestu di i travaglii specifichi. Queste suluzioni ùn pretendenu micca esse universale è ùn pò micca esse ottimali in altre cundizioni d'usu.

Chì ci hà da fà BPM?

Per risponde à sta quistione, avemu bisognu di sfondate un pocu in i specifichi di i prublemi applicati di e nostre suluzioni. A parte principale di a logica di l'affari in u nostru sistema transazionale tipicu hè di inserisce dati in a basa di dati per interfacce d'utilizatori, cuntrollà manualmente è automaticamente sti dati, passendu per qualchì flussu di travagliu, publicendu in un altru sistema / basa di dati analitica / archiviu, generendu rapporti. Cusì, a funzione chjave di u sistema per i clienti hè l'automatizazione di i so prucessi di cummerciale internu.

Per a cunvenzione, usemu u terminu "documentu" in a cumunicazione cum'è una certa astrazione di un settore di dati, unitu da una chjave cumuni, à quale un flussu di travagliu specificu pò esse "attaccatu".
Ma chì ne di a logica di integrazione ? Dopu tuttu, u compitu di integrazione hè generatu da l'architettura di u sistema, chì hè "segatu" in parti NON à a dumanda di u cliente, ma sottu a influenza di fatturi completamente differenti:

  • sottu l'influenza di a lege di Conway;
  • per via di a reutilizazione di sottosistemi sviluppati prima per altri prudutti;
  • cum'è decisu da l'architettu, basatu nantu à esigenze non-funziunali.

Ci hè una grande tentazione di separà a logica d'integrazione da a logica di l'affari di u flussu di travagliu principale per ùn impurtà a logica di l'affari cù l'artefacti di integrazione è salvà u sviluppatore di l'applicazioni da avè da sfondà in e peculiarità di u paisaghju architettonicu di u sistema. Stu approcciu hà una quantità di vantaghji, ma a pratica mostra a so inefficienza:

  • risolve i prublemi d'integrazione di solitu slides down to the simples options in the form of synchronous calls due to the limited extension points in the implementation of the main workflow (più nantu à i difetti di l'integrazione sincrona sottu);
  • l'artefatti di integrazione penetranu sempre in a logica principale di l'affari quandu u feedback da un altru subsistema hè necessariu;
  • u sviluppatore di l'applicazioni ignora l'integrazione è pò facilmente rompellu cambiendu u flussu di travagliu;
  • u sistema cessà di esse un unicu tutale da u puntu di vista di l'utilizatori, "seams" trà i sottosistemi diventanu notevuli, l'operazioni redundante di l'utilizatori appariscenu chì inizianu u trasferimentu di dati da un subsistema à l'altru.

Un altru approcciu hè di cunsiderà l'interazzione d'integrazione cum'è una parte integrante di a logica di l'affari core è u flussu di travagliu. Per guardà i bisogni di cumpetenze di i sviluppatori di l'applicazioni da a crescita, a creazione di novi interazzioni d'integrazione deve esse fatta facilmente è naturali, cù l'opzioni minimu per sceglie una suluzione. Questu hè più difficiuli di ciò chì pare: l'uttellu deve esse abbastanza putente per furnisce l'utilizatori cù a varietà necessaria di l'opzioni per u so usu è à u stessu tempu ùn permettenu micca di sparà in u pede. Ci hè parechje dumande chì un ingegnere deve risponde in u cuntestu di i travaglii di integrazione, ma chì un sviluppatore di l'applicazione ùn deve micca pensà à u so travagliu di ogni ghjornu: cunfini di transazzione, coerenza, atomità, sicurezza, scala, distribuzione di carica è risorse, routing, marshaling, cuntesti di propagazione è di cunversione, etc. Hè necessariu di offre à i sviluppatori di l'applicazioni mudelli di decisione abbastanza simplici, in quale e risposte à tutte queste dumande sò digià oculate. Questi mudelli anu da esse abbastanza sicuru: a logica di l'affari cambia assai spessu, chì aumenta u risicu di intruduce errori, u costu di l'errori deve esse à un livellu abbastanza bassu.

Ma ancu, chì BPM hà da fà cun questu? Ci hè parechje opzioni per implementà u flussu di travagliu ...
In verità, un'altra implementazione di i prucessi di cummerciale hè assai populari in e nostre soluzioni - per mezu di l'ambienti dichjarativi di u diagramma di transizione di u statu è cunnessu i gestori cù a logica cummerciale à e transizioni. À u listessu tempu, u statu chì determina a pusizione attuale di u "documentu" in u prucessu cummerciale hè un attributu di u "documentu" stessu.

Integrazione di stile BPM
Questu hè cumu u prucessu s'assumiglia à u principiu di u prugettu

A popularità di una tale implementazione hè dovuta à a relativa simplicità è a rapidità di creazione di prucessi di cummerciale lineari. In ogni casu, cum'è i sistemi di software diventanu più cumplessi, a parte automatizata di u prucessu cummerciale cresce è diventa più cumplessa. Ci hè bisognu di a descomposizione, a reutilizazione di parti di i prucessi, è ancu di i prucessi di forking in modu chì ogni ramu hè eseguitu in parallelu. In tali cundizioni, l'uttellu diventa inconveniente, è u diagramma di transizione statale perde u so cuntenutu di l'infurmazioni (l'interazzione di integrazione ùn sò micca riflesse in u diagramma).

Integrazione di stile BPM
Questu hè ciò chì u prucessu s'assumiglia dopu à parechje iterazioni di clarificà i requisiti

U modu fora di sta situazione era l'integrazione di u mutore jBPM in certi prudutti cù i prucessi di cummerciale più cumplessi. In u cortu termini, sta suluzione hà avutu qualchì successu: hè diventatu pussibule di implementà prucessi di cummerciale cumplessi, mantenendu un diagramma abbastanza informativu è aghjurnatu in a notazione. BPMN2.

Integrazione di stile BPM
Una piccula parte di un prucessu cummerciale cumplessu

À longu andà, a suluzione ùn hà micca rispostu à l'aspettattivi: l'alta intensità di travagliu di creazione di prucessi di cummerciale per mezu di strumenti visuali ùn permettenu micca di ottene indicatori di produtividade accettabili, è l'uttellu stessu hè diventatu unu di i più disgustati trà i sviluppatori. Ci era ancu lagnanza nantu à a struttura interna di u mutore, chì hà purtatu à l'apparizione di parechji "patch" è "crutches".

L'aspettu pusitivu principalu di l'usu di jBPM era a realizazione di i benefici è i danni di avè u so propiu statu persistente per una istanza di prucessu cummerciale. Avemu vistu ancu a pussibilità di utilizà un approcciu di prucessu per implementà protokolli cumplessi di integrazione trà e diverse applicazioni utilizendu interazzioni asincrone per mezu di signali è missaghji. A prisenza di un statu persistente ghjoca un rolu cruciale in questu.

Basatu nantu à ciò chì sopra, pudemu cuncludi: L'approcciu di prucessu in u stilu BPM ci permette di risolve una larga gamma di compiti per l'automatizazione di i prucessi di cummerciale sempre più cumplessi, adatta in armunia l'attività di integrazione in questi prucessi è mantene a capacità di visualizà visualmente u prucessu implementatu in una notazione adatta.

I svantaghji di e chjama sincrone cum'è un mudellu d'integrazione

L'integrazione sincrona si riferisce à a chjama di bloccu più simplice. Un subsistema agisce cum'è u latu di u servitore è espone l'API cù u metudu desideratu. Un altru subsistema agisce cum'è una parte di u cliente è, à u mumentu propiu, face una chjama cù l'aspettativa di un risultatu. Sicondu l'architettura di u sistema, i lati di u cliente è di u servitore ponu esse ospitu in a listessa applicazione è prucessu, o in diverse. In u sicondu casu, avete bisognu di applicà una certa implementazione di RPC è furnisce u marshalling di i paràmetri è u risultatu di a chjama.

Integrazione di stile BPM

Un tali mudellu di integrazione hà un inseme abbastanza grande di inconvenienti, ma hè assai utilizatu in a pratica per via di a so simplicità. A rapidità di l'implementazione captivate è vi fa applicà una volta è una volta in e cundizioni di i termini "bruciati", scrivendu a suluzione in u debitu tecnicu. Ma succèri ancu chì i sviluppatori inexperienced l'utilizanu inconscientemente, simpricimenti ùn capiscenu micca e cunsequenze negative.

In più di l'aumentu più evidenti di a cunnessione di i sottosistemi, ci sò prublemi menu evidenti cù e transacciones "spreading" è "stretching". Infatti, se a logica di l'affari face cambiamenti, e transazzione sò indispensabili, è e transazzione, à u turnu, chjude certe risorse di l'applicazione affettate da questi cambiamenti. Questu hè, finu à chì un subsistema aspetta una risposta da un altru, ùn serà micca capaci di cumpiendu a transazzione è liberate chjusi. Questu aumenta significativamente u risicu di una varietà di effetti:

  • a risposta di u sistema hè persa, l'utilizatori aspettanu assai tempu per e risposte à e dumande;
  • u servitore generalmente cessà di risponde à e dumande di l'utilizatori per via di una piscina di fili chì sbocca: a maiò parte di i filamenti "stand" nantu à a serratura di a risorsa occupata da a transazzione;
  • i deadlocks cumincianu à apparisce: a probabilità di a so occurrence dipende assai da a durazione di e transazzione, a quantità di logica cummerciale è i chjusi implicati in a transazzione;
  • I errori di scadenza di u timeout di transazzione appariscenu;
  • u servitore "casca" in OutOfMemory se u compitu hè bisognu di trasfurmà è cambià una grande quantità di dati, è a presenza di integrazioni sincrone rende assai difficiuli di split the processing in transacciones "più ligeri".

Da un puntu di vista architetturale, l'utilizazione di i chjami bluccati durante l'integrazione porta à una perdita di cuntrollu di qualità di i subsistemi individuali: hè impussibile d'assicurà i miri di qualità di un subsistema isolatu da i miri di qualità di un altru subsistema. Se i sottosistemi sò sviluppati da diverse squadre, questu hè un grande prublema.

E cose diventanu ancu più interessanti se i sottosistemi chì sò integrati sò in diverse applicazioni è i cambiamenti sincroni devenu esse fatti da i dui lati. Cumu fà questi cambiamenti transazzione?

Se i cambiamenti sò fatti in transazzioni separati, allora a gestione robusta di l'eccezzioni è a compensazione deve esse furnita, è questu elimina completamente u vantaghju principali di integrazioni sincrone - simplicità.

E transacciones distribuite venenu ancu in mente, ma ùn l'utilizamu micca in i nostri suluzioni: hè difficiule di assicurà a fiducia.

"Saga" cum'è una suluzione à u prublema di transazzione

Cù a pupularità crescente di i microservizi, ci hè una dumanda crescente Saga Pattern.

Stu mudellu risolve perfettamente i prublemi di sopra di e transazzioni longu, è ancu espansione e pussibilità di gestisce u statu di u sistema da u latu di a logica di l'affari: a compensazione dopu una transazzione senza successu ùn pò micca rinvià u sistema à u so statu originale, ma furnisce una alternativa. rotta di trasfurmazioni di dati. Si permette dinù tù ùn ripetiri passi di trasfurmazioni di dati cumpleta successu quandu vi pruvà à purtà u prucessu à un "bonu" fini.

Curiosamente, in i sistemi monolitichi, stu mudellu hè ancu pertinenti quandu si tratta di l'integrazione di sottosistemi chjude è ci sò effetti negativi causati da transazzioni longu è i chjusi di risorse currispondenti.

In quantu à i nostri prucessi di cummerciale in u stilu BPM, hè assai faciule per implementà e Sagas: i passi individuali di i Sagas ponu esse definiti cum'è attività in u prucessu di cummerciale, è u statu persistente di u prucessu cummerciale determina, trà altre cose, u statu internu di i Sagas. Vale à dì, ùn avemu micca bisognu di alcun mecanismu di coordinazione supplementu. Tuttu ciò chì avete bisognu hè un broker di missaghju cù supportu per "almenu una volta" guarantisci cum'è trasportu.

Ma una tale suluzione hà ancu u so propiu "prezzu":

  • a logica di l'affari diventa più cumplessa: avete bisognu di travaglià una compensazione;
  • serà necessariu di abbandunà a cuerenza completa, chì pò esse particularmente sensibile per i sistemi monolitichi;
  • l'architettura diventa un pocu più cumplicata, ci hè un bisognu supplementu per un broker di messagiu;
  • Strumenti supplementari di monitoraghju è amministrazione seranu necessarii (ancu in generale questu hè ancu bonu: a qualità di u serviziu di u sistema aumenterà).

Per i sistemi monolitici, a ghjustificazione per l'usu di "Sags" ùn hè micca cusì evidenti. Per i microservizi è altri SOA, induve, assai prubabilmente, ci hè digià un broker, è a cuerenza cumpleta hè stata sacrificata à l'iniziu di u prugettu, i benefici di l'usu di stu mudellu pò superà significativamente i svantaghji, soprattuttu s'ellu ci hè una API cunvene à u livellu di logica cummerciale.

Incapsulazione di a logica cummerciale in i microservizi

Quandu avemu cuminciatu à sperimentà i microservizi, una quistione ragiunate hè stata: induve mette a logica cummerciale di u duminiu in relazione à u serviziu chì furnisce a persistenza di dati di u duminiu?

Quandu si vede l'architettura di diversi BPMS, pò sembrà ragiunate per separà a logica di l'affari da a persistenza: crea una strata di piattaforma è microservizi indipendenti di u duminiu chì formanu l'ambiente è u cuntainer per eseguisce a logica cummerciale di u duminiu, è organizà a persistenza di dati di u duminiu cum'è un separatu. strata di microservizi assai simplici è ligeri. I prucessi di cummerciale in questu casu orchestranu i servizii di a capa di persistenza.

Integrazione di stile BPM

Stu approcciu hà un grande plus grande: pudete aumentà a funziunalità di a piattaforma quantu vulete, è solu a capa currispondente di i microservizii di a piattaforma "ingrassarà" da questu. I prucessi di cummerciale da ogni duminiu ricevenu immediatamente l'uppurtunità di utilizà a nova funziunalità di a piattaforma appena hè aghjurnata.

Un studiu più detallatu hà revelatu difetti significativi di stu approcciu:

  • un serviziu di piattaforma chì eseguisce a logica cummerciale di parechji duminii à una volta porta grandi risichi cum'è un puntu unicu di fallimentu. I cambiamenti frequenti à a logica cummerciale aumentanu u risicu di bugs chì portanu à fallimenti in tuttu u sistema;
  • prublemi di rendiment: a logica cummerciale travaglia cù i so dati attraversu una interfaccia stretta è lenta:
    • i dati tornanu à esse marshalled and pumped through the network stack;
    • u serviziu di u duminiu spessu torna più dati chì a logica di l'affari richiede per u processu, per via di capacità di parametrizzazione di query insufficiente à u livellu di l'API esterna di u serviziu;
    • parechji pezzi indipindenti di logica di l'affari ponu ripetutamente dumandà a listessa data per u processu (pudete mitigà stu prublema aghjustendu fasgioli di sessione chì cache data, ma questu più complicate l'architettura è crea prublemi di freschezza di dati è invalidazione di cache);
  • prublemi transazzione:
    • i prucessi di cummerciale cù un statu persistente guardatu da u serviziu di a piattaforma sò inconsistenti cù e dati di u duminiu, è ùn sò micca manere faciuli di risolve stu prublema;
    • spustà a serratura di dati di duminiu fora di a transazzione: se a logica cummerciale di u duminiu hà bisognu di fà cambiamenti, dopu avè prima verificatu a correttezza di e dati attuali, hè necessariu escludiri a pussibilità di un cambiamentu competitivu in i dati processati. U bluccatu esternu di e dati pò aiutà à risolve u prublema, ma una tale suluzione porta risichi supplementari è riduce l'affidabilità generale di u sistema;
  • cumplicazioni supplementari à l'aghjurnamentu: in certi casi, avete bisognu di aghjurnà u serviziu di persistenza è a logica di l'affari in modu sincronu o in sequenza stretta.

À a fine, aghju avutu à vultà à i principii: incapsulate i dati di u duminiu è a logica cummerciale di u duminiu in un microserviziu. Stu approcciu simplifica a percepzioni di u microserviziu cum'è un cumpunente integrale in u sistema è ùn dà micca i prublemi sopra. Questu hè ancu micca liberu:

  • A standardizazione API hè necessaria per l'interazzione cù a logica cummerciale (in particulare, per furnisce l'attività di l'utilizatori cum'è parte di i prucessi di cummerciale) è i servizii di piattaforma API; Attenzione più attenta à i cambiamenti API, cumpatibilità avanti è retrocede hè necessariu;
  • hè necessariu di aghjunghje biblioteche di runtime supplementari per assicurà u funziunamentu di a logica cummerciale cum'è parte di ogni tali microserviziu, è questu dà nascita à novi esigenze per tali biblioteche: ligerezza è un minimu di dependenzi transitivi;
  • I sviluppatori di logica di l'affari anu bisognu di seguità di e versioni di a biblioteca: se un microserviziu ùn hè micca statu finalizatu per un bellu pezzu, allora probabilmente cuntene una versione obsoleta di e biblioteche. Questu pò esse un ostaculu inespettatu per aghjunghje una nova funzione è pò esse bisognu di a vechja logica cummerciale di un tali serviziu per esse migratu à e versioni novi di e biblioteche s'ellu ci era cambiamenti incompatibili trà e versioni.

Integrazione di stile BPM

Una strata di servizii di piattaforma hè ancu presente in una tale architettura, ma sta strata ùn forma più un cuntinuu per eseguisce a logica cummerciale di u duminiu, ma solu u so ambiente, chì furnisce funzioni "piattaforma" ausiliari. Un tali stratu hè necessariu micca solu per mantene a ligerezza di i microservizii di u duminiu, ma ancu per centralizà a gestione.

Per esempiu, l'attività di l'utilizatori in i prucessi di cummerciale generanu attività. In ogni casu, quandu u travagliu cù i travaglii, l'utilizatore deve vede i travaglii da tutti i domini in a lista generale, chì significa chì deve esse un serviziu di piattaforma di registrazione di u travagliu adattatu, sbulicatu da a logica cummerciale di u duminiu. Mantene l'incapsulazione di a logica cummerciale in questu cuntestu hè abbastanza problematicu, è questu hè un altru cumprumissu di questa architettura.

Integrazione di prucessi cummerciale attraversu l'ochji di un sviluppatore di applicazioni

Comu digià citatu sopra, u sviluppatore di l'applicazione deve esse astrattu da e caratteristiche tecniche è ingegneria di l'implementazione di l'interazzione di parechje applicazioni per pudè cuntà una bona produtividade di sviluppu.

Pruvemu di risolve un prublema di integrazione piuttostu difficiule, inventatu apposta per l'articulu. Questu serà un compitu di "ghjocu" chì implica trè applicazioni, induve ognunu definisce un nome di duminiu: "app1", "app2", "app3".

Dentru ogni applicazione, i prucessi di cummerciale sò lanciati chì cumincianu à "giocà à a bola" attraversu u bus d'integrazione. I missaghji chjamati "Ball" agiranu cum'è u ballu.

Regoli di u ghjocu:

  • u primu ghjucadore hè l'iniziatore. Invita à l'altri ghjucatori à u ghjocu, cumencia u ghjocu è pò finisce in ogni mumentu;
  • altri ghjucadori dichjaranu a so participazione à u ghjocu, "cunniscite" cù l'altri è u primu ghjucatore;
  • dopu avè ricivutu u ballò, u ghjucatore sceglie un altru ghjucadore participante è passa u ballò à ellu. U numeru tutale di passaghji hè cuntatu;
  • ogni ghjucadore hà "energia", chì diminuisce cù ogni passu di u ballò da quellu ghjucatore. Quandu l'energia scorri, u ghjucatore hè eliminatu da u ghjocu, annunziendu a so ritirata;
  • se u ghjucatore hè lasciatu solu, dichjarà subitu a so partenza;
  • quandu tutti i ghjucatori sò eliminati, u primu ghjucatore dichjara a fine di u ghjocu. S'ellu abbandunò u ghjocu prima, allora resta à seguità u ghjocu per compie.

Per risolve stu prublema, aghju aduprà u nostru DSL per i prucessi di cummerciale, chì vi permette di discrive a logica in Kotlin compactly, cù un minimu di un boilerplate.

In l'applicazione app1, u prucessu cummerciale di u primu ghjucatore (hè ancu l'iniziatore di u ghjocu) hà da travaglià:

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

In più di eseguisce a logica cummerciale, u codice sopra pò pruduce un mudellu d'ughjettu di un prucessu cummerciale chì pò esse visualizatu cum'è un diagramma. Ùn avemu micca implementatu ancu u visualizatore, cusì avemu da passà un pocu di tempu di disegnu (qui aghju simplificatu un pocu a notazione BPMN in quantu à l'usu di e porte per migliurà a coherenza di u diagramma cù u codice sopra):

Integrazione di stile BPM

app2 includerà u prucessu cummerciale di un altru ghjucatore:

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

Diagramma:

Integrazione di stile BPM

In l'applicazione app3, faremu u ghjucatore cun un cumpurtamentu ligeramente sfarente: invece di sceglie aleatoriamente u prossimu ghjucatore, agirà secondu l'algoritmu round-robin:

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

Altrimenti, u cumpurtamentu di u ghjucatore ùn hè micca diffirenti da u precedente, cusì u diagramma ùn cambia micca.

Avà avemu bisognu di una prova per eseguisce tuttu. Daraghju solu u codice di a prova stessu, per ùn sbulicà l'articulu cù un boilerplate (in fattu, aghju utilizatu l'ambiente di prova creatu prima per pruvà l'integrazione di altri prucessi di cummerciale):

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

Eseguite a prova, fighjate à u logu:

output di cunsola

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

Diversi cunclusioni impurtanti ponu esse tratte da tuttu questu:

  • se l'uttene necessarii sò dispunibuli, i sviluppatori di l'applicazioni ponu creà interazzione d'integrazione trà l'applicazioni senza rompe da a logica cummerciale;
  • a cumplessità (a cumplessità) di un compitu d'integrazione chì esige cumpetenze ingegneria pò esse ammucciatu in u quadru s'ellu hè inizialmente stabilitu in l'architettura di u quadru. A difficultà di u compitu (difficultà) ùn pò esse ammucciatu, cusì a suluzione à un compitu difficiuli in u codice hà da vede cusì;
  • quandu u sviluppu logica integrazione, ci vole à piglià in contu eventuale cuerenza è a mancanza di linearizability di u cambiamentu di statu di tutti i participanti integrazione. Questu ci obbliga à cumplicà a logica per rende insensibile à l'ordine in quale l'avvenimenti esterni sò. In u nostru esempiu, u ghjucatore hè furzatu à participà à u ghjocu dopu avè annunziatu a so surtita da u ghjocu: l'altri ghjucatori cuntinueghjanu à passà a bola à ellu finu à chì l'infurmazioni nantu à a so surtita righjunghji è sò processati da tutti i participanti. Sta logica ùn seguita micca da e regule di u ghjocu è hè una suluzione di cumprumissu in u quadru di l'architettura scelta.

Dopu, parlemu di e diverse suttilità di a nostra suluzione, cumprumessi è altri punti.

Tutti i missaghji in una fila

Tutte l'applicazioni integrate travaglianu cù un bus d'integrazione, chì hè prisentatu cum'è un broker esternu, un BPMQueue per i missaghji è un tema BPMTopic per i signali (eventi). Passà tutti i missaghji per una sola fila hè in sè stessu un cumprumissu. À u livellu di a logica di l'affari, pudete avà intruduce quant'è novi tipi di messagi chì vulete senza fà cambiamenti à a struttura di u sistema. Questa hè una simplificazione significativa, ma porta certi risichi, chì, in u cuntestu di i nostri compiti tipici, ci parevanu micca cusì significativu.

Integrazione di stile BPM

Tuttavia, ci hè una sutilezza quì: ogni applicazione filtra "i so" missaghji da a fila à l'entrata, da u nome di u so duminiu. Inoltre, u duminiu pò esse specificatu in i signali, se avete bisognu di limità u "scopu" di u signale à una sola applicazione. Questu deve aumentà a larghezza di banda di l'autobus, ma a logica di l'affari deve avà opera cù nomi di duminiu: ubligatoriu per indirizzà i missaghji, desiderate per i signali.

Assicurendu l'affidabilità di u bus d'integrazione

L'affidabilità hè custituita da parechje cose:

  • U broker di missaghju sceltu hè un cumpunente criticu di l'architettura è un puntu unicu di fallimentu: deve esse abbastanza tolerante à i difetti. Duvete aduprà solu implementazioni testate in u tempu cù un bonu sustegnu è una grande cumunità;
  • hè necessariu d'assicurà una alta dispunibilità di u broker di messagiu, per quale deve esse siparatu fisicamente da l'applicazioni integrate (l'alta dispunibilità di l'applicazioni cù a logica cummerciale applicata hè assai più difficiuli è caru di furnisce);
  • u broker hè obligatu à furnisce "almenu una volta" garanzii di consegna. Questu hè un requisitu obligatoriu per u funziunamentu affidabile di u bus d'integrazione. Ùn ci hè micca bisognu di garanzii di livellu "esattamente una volta": i prucessi di cummerciale sò generalmente micca sensibili à l'arrivata ripetuta di messagi o avvenimenti, è in i travaglii spiciali induve questu hè impurtante, hè più faciule d'aghjunghje cuntrolli supplementari à a logica di l'affari chè di utilizà constantemente. piuttostu "caru" "garantii;
  • mandendu missaghji è signali deve esse implicatu in una transazzione cumuna cù un cambiamentu in u statu di i prucessi di cummerciale è e dati di duminiu. L'opzione preferita seria di utilizà u mudellu Outbox transazionale, ma avarà bisognu di una tabella supplementu in a basa di dati è un relay. In l'applicazioni JEE, questu pò esse simplificatu usendu un gestore JTA locale, ma a cunnessione cù u broker sceltu deve esse capace di travaglià in modu. XA;
  • i gestori di i missaghji è l'avvenimenti entranti anu da travaglià ancu cù a transazzione di cambià u statu di u prucessu di l'affari: se una tale transazzione hè ritruvata, allora a ricezione di u missaghju deve esse ancu annullata;
  • i missaghji chì ùn puderanu micca esse mandati per errore deve esse guardati in una tenda separata D.L.Q. (Coda di Lettere Morte). Per fà questu, avemu creatu un microserviziu di piattaforma separata chì guarda tali messagi in u so almacenamentu, l'indicizza per attributi (per un raggruppamentu rapidu è a ricerca), è espone l'API per vede, rinvià à l'indirizzu di destinazione, è sguassà i missaghji. L'amministratori di u sistema ponu travaglià cù stu serviziu attraversu a so interfaccia web;
  • in i paràmetri di u broker, avete bisognu di aghjustà u numeru di tentativi di consegna è ritardi trà e spedizioni per riduce a probabilità di messagi chì entranu in u DLQ (hè quasi impussibile di calculà i paràmetri ottimali, ma pudete agisce empiricamente è aghjustate durante operazione);
  • u magazinu DLQ deve esse monitoratu in permanenza, è u sistema di surviglianza deve avvisà l'amministratori di u sistema per ch'elli ponu risponde u più prestu pussibule quandu i missaghji ùn sò micca consegnati. Questu riducerà a "zona di dannu" di un fallimentu o errore di logica cummerciale;
  • u bus d'integrazione deve esse insensibile à l'absenza temporale di l'applicazioni: l'abbunamentu di u tema deve esse durable, è u nome di duminiu di l'applicazione deve esse unicu per chì qualcunu altru ùn pruvate micca di processà u so messagiu da a fila durante l'absenza di l'applicazione.

Assicurendu a sicurezza di filu di a logica cummerciale

A listessa istanza di un prucessu cummerciale pò riceve parechji messagi è avvenimenti à una volta, u prucessu di quale hà da principià in parallelu. À u listessu tempu, per un sviluppatore di applicazioni, tuttu deve esse simplice è sicuru.

A logica cummerciale di u prucessu processa ogni avvenimentu esternu chì affetta stu prucessu cummerciale individualmente. Questi avvenimenti ponu esse:

  • lanciari una istanza di prucessu cummerciale;
  • una azzione di l'utilizatore ligata à una attività in un prucessu cummerciale;
  • ricivutu di un missaghju o signale à quale hè sottumessu una istanza di prucessu cummerciale;
  • scadenza di u timer stabilitu da l'istanza di u prucessu cummerciale;
  • azzione di cuntrollu via API (per esempiu, abortu di prucessu).

Ogni tali avvenimentu pò cambià u statu di una istanza di prucessu cummerciale: alcune attività ponu finisce è altre cumincianu, i valori di proprietà persistenti ponu cambià. Chiudere ogni attività pò esse risultatu in l'attivazione di una o più di e seguenti attività. Quelli, à u turnu, ponu piantà d'aspittà per altri avvenimenti, o, s'ellu ùn anu micca bisognu di dati supplementari, ponu compie in a listessa transazzione. Prima di chjude a transazzione, u novu statu di u prucessu cummerciale hè guardatu in a basa di dati, induve aspittà per u prossimu avvenimentu esternu.

I dati di u prucessu di cummerciale persistenti almacenati in una basa di dati relazionale sò un puntu di sincronizazione di trasfurmazioni assai convenientu quandu si usa SELECT FOR UPDATE. Se una transazzione hà sappiutu ottene u statu di u prucessu di l'affari da a basa di dati per cambià, allora nisuna transazzione in parallelu serà capace di ottene u listessu statu per un altru cambiamentu, è dopu à a fine di a prima transazzione, u sicondu hè. guarantisci di riceve u statu digià cambiatu.

Utilizendu chjusi pessimisti nantu à u latu DBMS, cumpiendu tutti i requisiti necessarii àcitu, è ancu mantene a capacità di scala l'applicazione cù a logica cummerciale aumentendu u numeru di istanze in esecuzione.

Tuttavia, i chjusi pessimisti ci minaccianu di blocchi, chì significa chì SELECT FOR UPDATE deve esse sempre limitatu à qualchì timeout raghjone in casu di blocchi in certi casi egregious in a logica cummerciale.

Un altru prublema hè a sincronizazione di u principiu di u prucessu cummerciale. Mentre ùn ci hè nisuna istanza di prucessu cummerciale, ùn ci hè nè statu in a basa di dati, cusì u metudu descrittu ùn funziona micca. Se vulete assicurà l'unicità di una istanza di prucessu cummerciale in un scopu particulari, allora avete bisognu di qualchì tipu d'ughjettu di sincronizazione assuciatu cù a classa di prucessu è u scopu currispundente. Per risolve stu prublema, usemu un mecanismu di chjusu differenti chì ci permette di piglià una serratura nantu à una risorsa arbitraria specificata da una chjave in formatu URI attraversu un serviziu esternu.

In i nostri esempi, u prucessu di cummerciale InitialPlayer cuntene una dichjarazione

uniqueConstraint = UniqueConstraints.singleton

Dunque, u logu cuntene missaghji nantu à piglià è liberà a serratura di a chjave currispundente. Ùn ci hè micca tali missaghji per altri prucessi di cummerciale: uniqueConstraint ùn hè micca stabilitu.

Prublemi di prucessu cummerciale cù u statu persistente

A volte, avè un statu persistente ùn solu aiuta, ma ancu veramente impedisce u sviluppu.
I prublemi cumincianu quandu avete bisognu di fà cambiamenti à a logica cummerciale è / o mudellu di prucessu cummerciale. Ùn si trova micca tali cambiamenti cumpatibili cù u vechju statu di i prucessi di cummerciale. Se ci sò parechje istanze "live" in a basa di dati, allora fà cambiamenti incompatibili pò causà assai prublemi, chì avemu spessu scontru cù jBPM.

Sicondu a prufundità di u cambiamentu, pudete agisce in dui maneri:

  1. creanu un novu tipu di prucessu di cummerciale per ùn fà cambiamenti incompatibili à u vechju, è l'utilizanu invece di u vechju quandu cumincianu novi casi. I vechji casi continuanu à travaglià "u modu anticu";
  2. migrate u statu persistente di i prucessi di cummerciale quandu aghjurnà a logica cummerciale.

U primu modu hè più simplice, ma hà i so limitazioni è svantaghji, per esempiu:

  • duplicazione di a logica cummerciale in parechji mudelli di prucessu di cummerciale, un aumentu di u voluminu di a logica cummerciale;
  • spessu una transizione istantanea à una nova logica cummerciale hè necessaria (quasi sempre in quantu à e funzioni di integrazione);
  • u sviluppatore ùn sapi à quale puntu hè pussibule di sguassà mudelli obsoleti.

In pratica, usemu i dui approcci, ma avemu fattu una quantità di decisioni per simplificà a nostra vita:

  • in a basa di dati, u statu persistente di u prucessu cummerciale hè guardatu in una forma facilmente leggibile è facilmente processata: in una stringa di formatu JSON. Questu permette di fà migrazioni sia in l'applicazione sia fora. In i casi estremi, pudete ancu aghjustà cù manichi (in particulare utile in u sviluppu durante a debugging);
  • a logica di l'integrazione di l'affari ùn usa micca i nomi di i prucessi di cummerciale, perchè in ogni mumentu hè pussibule rimpiazzà l'implementazione di unu di i prucessi chì participanu cù un novu, cù un novu nome (per esempiu, "InitialPlayerV2"). U ligame si faci per mezu di i nomi di missaghji è signali;
  • u mudellu di prucessu hà un numeru di versione, chì avemu aumentatu s'ellu facemu cambiamenti incompatibili à stu mudellu, è stu numeru hè guardatu cù u statu di l'istanza di u prucessu;
  • u statu persistente di u prucessu hè lettu da a basa prima in un mudellu d'ughjettu cunvene chì a prucedura di migrazione pò travaglià se u numeru di versione di u mudellu hà cambiatu;
  • a prucedura di migrazione hè situata vicinu à a logica cummerciale è hè chjamata "lazy" per ogni istanza di u prucessu cummerciale à u mumentu di a so risturazione da a basa di dati;
  • se avete bisognu di migrà u statu di tutte l'istanze di u prucessu rapidamente è in modu sincronu, solu suluzione di migrazione di basa di dati più classica sò aduprate, ma avete da travaglià cù JSON quì.

Aghju bisognu di un altru quadru per i prucessi di cummerciale?

E soluzioni descritte in l'articulu ci anu permessu di simplificà significativamente a nostra vita, espansione a gamma di prublemi risolti à u livellu di sviluppu di l'applicazioni, è rende l'idea di separà a logica cummerciale in microservizi più attraente. Per questu, assai travagliu hè statu fattu, un quadru assai "ligeru" per i prucessi di cummerciale hè statu creatu, è ancu cumpunenti di serviziu per risolve i prublemi identificati in u cuntestu di una larga gamma di travaglii applicati. Avemu a vulintà di sparte questi risultati, per portà u sviluppu di cumpunenti cumuni in accessu apertu sottu una licenza libera. Questu hà bisognu di qualchì sforzu è tempu. Capisce a dumanda di tali suluzioni puderia esse un incentive supplementu per noi. In l'articulu prupostu, assai poca attenzione hè pagata à e capacità di u quadru stessu, ma alcuni di elli sò visibili da l'esempii presentati. S'è no publichemu u nostru quadru, un articulu separatu li serà dedicatu. Intantu, vi saremu grati se lasciate un pocu feedback rispondendu à a quistione:

Solu l'utilizatori registrati ponu participà à l'indagine. Firmà lu, per piacè.

Aghju bisognu di un altru quadru per i prucessi di cummerciale?

  • 18,8%Iè, aghju cercatu qualcosa cusì per un bellu pezzu.

  • 12,5%hè interessante per sapè più nantu à a vostra implementazione, pò esse utile2

  • 6,2%usemu unu di i quadri esistenti, ma pensemu à rimpiazzà lu1

  • 18,8%usemu unu di i frameworks esistenti, tuttu cunvene3

  • 18,8%affruntà senza quadru 3

  • 25,0%scrivite u vostru propiu4

16 utilizatori anu vutatu. 7 utilizatori si sò astenuti.

Source: www.habr.com

Add a comment