BPM Stil Integratioun

BPM Stil Integratioun

Hallo, Habr!

Eis Gesellschaft spezialiséiert op d'Entwécklung vun ERP-Klass Software Léisungen, de Léiw d'Undeel vun deenen ass vun Transaktiounssystemer besat mat enger enormer Quantitéit vun Affär Logik an Dokument Flux a la EDMS. Aktuell Versioune vun eise Produkter baséieren op JavaEE Technologien, awer mir experimentéieren och aktiv mat Mikroservicer. Ee vun de problemateschste Beräicher vun esou Léisungen ass d'Integratioun vu verschiddenen Ënnersystemer, déi zu benachbaren Domainen gehéieren. Integratiounsproblemer hunn eis ëmmer e grousse Kappwéi ginn, onofhängeg vun den architektonesche Stiler, Technologiestapelen a Kaderen déi mir benotzen, awer viru kuerzem ass et Fortschrëtter fir sou Probleemer ze léisen.

Am Artikel, deen ech op Är Opmierksamkeet bréngen, wäert ech iwwer d'Erfahrung an d'architektonesch Fuerschung schwätzen, déi NPO Krista am designéierte Beräich huet. Mir kucken och e Beispill vun enger einfacher Léisung fir en Integratiounsproblem aus der Siicht vun engem Applikatiounsentwéckler an erauszefannen wat hannert dëser Einfachheet verstoppt ass.

Verzichterklärung

Déi architektonesch an technesch Léisungen, déi am Artikel beschriwwe ginn, gi vu mir proposéiert op Basis vu perséinlecher Erfahrung am Kontext vu spezifesche Aufgaben. Dës Léisunge behaapten net universell ze sinn a kënnen net optimal sinn ënner anerem Gebrauchsbedingungen.

Wat huet BPM domat ze dinn?

Fir dës Fro ze beäntweren, musse mir e bësse méi déif an d'Spezifizitéiten vun den ugewandte Probleemer vun eise Léisungen verdéiwen. Den Haaptdeel vun der Geschäftslogik an eisem typesche Transaktiounssystem ass d'Daten an d'Datebank duerch User-Interfaces anzeginn, manuell an automatiséiert Verifizéierung vun dësen Donnéeën, duerch e puer Workflow ze féieren, se an en anere System / analytesch Datebank / Archiv ze publizéieren, a Berichter ze generéieren . Also ass d'Schlësselfunktioun vum System fir Clienten d'Automatiséierung vun hiren internen Geschäftsprozesser.

Fir d'Bequemlechkeet benotze mir de Begrëff "Dokument" an der Kommunikatioun als eng Abstraktioun vun enger Rei vun Donnéeën vereenegt duerch e gemeinsame Schlëssel, mat deem e gewësse Workflow "verlinkt" ka sinn.
Awer wéi ass et mat der Integratiounslogik? No allem ass d'Integratiounsaufgab vun der Architektur vum System generéiert, deen an Deeler "geschnidden" gëtt NET op Ufro vum Client, awer ënner dem Afloss vu ganz verschiddene Faktoren:

  • ënnerleien dem Conway Gesetz;
  • als Resultat vun der Wiederverwendung vun Subsystemer, déi virdru fir aner Produkter entwéckelt goufen;
  • op der Diskretioun vum Architekt, baséiert op net-funktionell Ufuerderunge.

Et gëtt eng grouss Versuchung fir d'Integratiounslogik vun der Geschäftslogik vum Haaptworkflow ze trennen, fir d'Geschäftslogik net mat Integratiounsartefakte ze verschmotzen an den Applikatiounsentwéckler vun der Bedierfnes ze entlaaschten an d'Spezifizitéiten vun der architektonescher Landschaft vum System ze verdéiwen. Dës Approche huet eng Rei Virdeeler, awer d'Praxis weist seng Ineffektivitéit:

  • D'Léisung vun Integratiounsproblemer fällt normalerweis zréck op déi einfachsten Optiounen a Form vu Synchron-Uriff wéinst de limitéierten Extensiounspunkten an der Ëmsetzung vum Haaptworkflow (d'Nodeeler vun der Synchron-Integratioun ginn hei ënnen diskutéiert);
  • Integratioun Artefakte penetréieren nach ëmmer Kärgeschäftslogik wann Feedback vun engem aneren Ënnersystem erfuerderlech ass;
  • den Applikatioun Entwéckler ignoréiert d'Integratioun a kann et einfach briechen andeems de Workflow geännert gëtt;
  • de System hält op en eenzegt Ganzt aus der Siicht vum Benotzer ze sinn, "Nähte" tëscht Subsystemer ginn bemierkbar, an iwwerflësseg Benotzeroperatiounen erschéngen, déi den Transfer vun Daten vun engem Subsystem an en anert initiéieren.

Eng aner Approche ass d'Integratiounsinteraktiounen als en integralen Deel vun der Kerngeschäftslogik a Workflow ze betruechten. Fir Applikatioun Entwéckler Qualifikatiounen aus Skyrocket ze verhënneren, nei Integratioun Interaktiounen schafen soll einfach an Effort sinn, mat minimal Méiglechkeet eng Léisung ze wielen. Dëst ass méi schwéier ze maachen wéi et schéngt: d'Instrument muss staark genuch sinn fir de Benotzer déi erfuerderlech Varietéit vun Optiounen fir seng Notzung ze bidden, ouni him ze erlaben "sech selwer an de Fouss ze schéissen." Et gi vill Froen, déi en Ingenieur am Kontext vun Integratiounsaufgaben beäntweren muss, awer iwwer déi en Applikatiounsentwéckler a senger alldeeglecher Aarbecht net sollt nodenken: Transaktiounsgrenzen, Konsistenz, Atomitéit, Sécherheet, Skaléieren, Last- a Ressourceverdeelung, Routing, Marshaling, Verdeelung a Wiesselkontexter, asw Et muss een Applikatiounsentwéckler zimlech einfach Léisungsschabloune ubidden, an deenen d'Äntwerten op all esou Froen scho verstoppt sinn. Dës Schabloune musse ganz sécher sinn: d'Geschäftslogik ännert sech ganz dacks, wat d'Risiko erhéicht fir Feeler z'informéieren, d'Käschte vun de Feeler mussen op engem zimlech nidderegen Niveau bleiwen.

Awer wat huet BPM domat ze dinn? Et gi vill Optiounen fir Workflow ëmzesetzen ...
Tatsächlech ass eng aner Ëmsetzung vu Geschäftsprozesser ganz populär an eise Léisungen - duerch d'deklarativ Definitioun vun engem Staatstransitiounsdiagramm an d'Verbindung vun Handler mat Geschäftslogik fir Iwwergäng. An dësem Fall ass de Staat, deen déi aktuell Positioun vum "Dokument" am Geschäftsprozess bestëmmt, en Attribut vum "Dokument" selwer.

BPM Stil Integratioun
Dëst ass wéi de Prozess am Ufank vun engem Projet ausgesäit

D'Popularitéit vun dëser Implementatioun ass wéinst der relativer Einfachheet a Geschwindegkeet fir linear Geschäftsprozesser ze kreéieren. Wéi och ëmmer, wéi Softwaresystemer kontinuéierlech méi komplex ginn, wiisst den automatiséierten Deel vum Geschäftsprozess a gëtt méi komplex. Et gëtt e Besoin fir Zersetzung, Wiederverwendung vun Deeler vu Prozesser, souwéi Verzweigungsprozesser, sou datt all Branche parallel ausgefouert gëtt. Ënner esou Konditiounen gëtt d'Instrument onbequem, an de Staatstransitiounsdiagramm verléiert säin Informatiounsinhalt (Integratiounsinteraktiounen ginn guer net am Diagramm reflektéiert).

BPM Stil Integratioun
Dëst ass wéi de Prozess no e puer Iteratiounen vun Ufuerderunge Klärung ausgesäit.

De Wee aus dëser Situatioun war d'Integratioun vum Motor jBPM an e puer Produkter mat de komplexste Geschäftsprozesser. Kuerzfristeg huet dës Léisung e puer Erfolleg: et gouf méiglech komplex Geschäftsprozesser ëmzesetzen an e relativ informativen a relevanten Diagramm an der Notatioun ze halen BPMN2.

BPM Stil Integratioun
E klengen Deel vun engem komplexe Geschäftsprozess

Op laang Siicht huet d'Léisung net un d'Erwaardungen erlieft: déi héich Aarbechtsintensitéit fir Geschäftsprozesser duerch visuell Tools ze kreéieren huet et net erlaabt akzeptabel Produktivitéitsindikatoren z'erreechen, an d'Instrument selwer gouf ee vun de meescht net gär ënnert Entwéckler. Et goufen och Reklamatiounen iwwer d'intern Struktur vum Motor, wat zu der Erscheinung vu ville "Flécke" a "Krütchen" gefouert huet.

Den Haaptpositiven Aspekt vun der Benotzung vun jBPM war d'Bewosstsinn vun de Virdeeler a Schued vun engem eegene persistente Staat vun enger Geschäftsprozessinstanz. Mir hunn och d'Méiglechkeet gesinn eng Prozess Approche ze benotzen fir komplex Integratiounsprotokoller tëscht verschiddenen Uwendungen ëmzesetzen mat asynchronen Interaktiounen duerch Signaler a Messagen. D'Präsenz vun engem persistent Staat spillt eng entscheedend Roll an dësem.

Baséierend op der uewen, kënne mir ofschléissen: D'Prozess Approche am BPM-Stil erlaabt eis eng breet Palette vun Aufgaben ze léisen fir ëmmer méi komplex Geschäftsprozesser ze automatiséieren, harmonesch Integratiounsaktivitéiten an dës Prozesser ze passen an d'Fähigkeit z'erhalen fir den implementéierten Prozess visuell an enger passender Notatioun ze weisen.

Nodeeler vun Synchron Appellen als Integratioun Muster

Synchron Integratioun bezitt sech op den einfachsten Blockéierungsopruff. Een Ënnersystem handelt als Server Säit an exponéiert d'API mat der erfuerderter Method. En aneren Subsystem handelt als Client Säit a mécht zu der richteger Zäit en Uruff a waart op d'Resultat. Ofhängeg vun der Systemarchitektur kënnen d'Client- a Serversäiten entweder an der selwechter Applikatioun a Prozess oder a verschiddene sinn. Am zweete Fall musst Dir e puer RPC-Implementatioun applizéieren an d'Marshalling vun de Parameteren an d'Resultat vum Uruff ubidden.

BPM Stil Integratioun

Dëst Integratioun Muster huet eng zimlech grouss Rei vun Nodeeler, mä et ass ganz wäit an der Praxis benotzt wéinst senger Einfachheet. D'Geschwindegkeet vun der Implementatioun begeeschtert a forcéiert Iech et ëmmer erëm ze benotzen am Gesiicht vun pressen Deadlines, d'Léisung als technesch Schold opzehuelen. Awer et geschitt och datt onerfueren Entwéckler et onbewosst benotzen, einfach net déi negativ Konsequenzen realiséieren.

Zousätzlech zu der offensichtlechste Erhéijung vun der Subsystem Konnektivitéit, ginn et och manner offensichtlech Probleemer mat Transaktiounen "wuessen" an "Stretching". Tatsächlech, wann d'Geschäftslogik e puer Ännerunge mécht, da kënnen Transaktiounen net vermeit ginn, an Transaktiounen blockéieren dann och verschidde Applikatiounsressourcen, déi vun dësen Ännerungen betraff sinn. Dat ass, bis een Subsystem op eng Äntwert vum aneren waart, wäert et net fäeg sinn d'Transaktioun ofzeschléissen an d'Spären ze läschen. Dëst erhéicht de Risiko vu verschiddenen Effekter wesentlech:

  • D'Reaktiounsfäegkeet vum System ass verluer, Benotzer waarden laang op Äntwerten op Ufroen;
  • de Server hält allgemeng op d'Benotzer Ufroe reagéieren wéinst engem iwwerfëllte Fuedempool: d'Majoritéit vun de Threads sinn op enger Ressource gespaart, déi vun enger Transaktioun besat ass;
  • Deadlocks fänken un: d'Wahrscheinlechkeet vun hirem Optriede hänkt staark vun der Dauer vun den Transaktiounen, der Quantitéit vun der Geschäftslogik an der Transaktioun involvéiert Spären;
  • Transaktioun Timeout Feeler erschéngen;
  • de Server "fehlt" mat OutOfMemory wann d'Aufgab d'Veraarbechtung an d'Verännerung vu grousser Quantitéit un Daten erfuerdert, an d'Präsenz vu Synchron-Integratioune mécht et ganz schwéier fir d'Veraarbechtung an "liicht" Transaktiounen opzedeelen.

Aus enger architektonescher Siicht féiert d'Benotzung vu Blockéierungsappellen während der Integratioun zu engem Verloscht vu Kontroll iwwer d'Qualitéit vun eenzelne Subsystemer: et ass onméiglech fir d'Zilqualitéitsindikatoren vun engem Subsystem isoléiert vun de Qualitéitsindikatoren vun engem aneren Subsystem ze garantéieren. Wann Ënnersystemer vu verschiddenen Teams entwéckelt ginn, ass dëst e grousse Problem.

D'Saache ginn nach méi interessant wann d'Ënnersystemer déi integréiert sinn a verschiddenen Uwendungen sinn an Dir musst synchron Ännerunge op béide Säiten maachen. Wéi d'Transaktiounsfäegkeet vun dësen Ännerungen ze garantéieren?

Wann Ännerungen an getrennten Transaktioune gemaach ginn, da musst Dir zouverlässeg Ausnahmshandhabung a Kompensatioun ubidden, an dëst eliminéiert komplett den Haaptvirdeel vun Synchron-Integratioun - Einfachheet.

Verdeelt Transaktiounen kommen och am Kapp, awer mir benotzen se net an eise Léisungen: et ass schwéier Zouverlässegkeet ze garantéieren.

"Saga" als Léisung fir d'Transaktiounsproblem

Mat der wuessender Popularitéit vu Mikroservicer ass d'Nofro fir Saga Muster.

Dëst Muster léist perfekt déi uewe genannte Probleemer vu laanger Transaktiounen, an erweidert och d'Fäegkeeten fir den Zoustand vum System vun der Säit vun der Geschäftslogik ze managen: Kompensatioun no enger gescheitert Transaktioun kann de System net an hiren ursprénglechen Zoustand zréckrollen, awer ubidden. eng alternativ Datenveraarbechtungsroute. Dëst erlaabt Iech och ze vermeiden, erfollegräich ofgeschloss Datenveraarbechtungsschrëtt ze widderhuelen wann Dir probéiert de Prozess op e "gutt" Enn ze bréngen.

Spannen, an monolithic Systemer ass dëst Muster och relevant wann et drëms geet, fir d'Integratioun vun loose gekoppelter subsystems an negativ Auswierkunge vun laang-Lafen Transaktiounen an entspriechend Ressource Spär observéiert ginn.

Am Bezuch zu eise Geschäftsprozesser am BPM-Stil ass et ganz einfach "Sagas" ëmzesetzen: eenzel Schrëtt vun der "Saga" kënnen als Aktivitéiten am Geschäftsprozess spezifizéiert ginn, an de persistent Zoustand vum Geschäftsprozess och bestëmmt den internen Zoustand vun der "Saga". Dat heescht, mir brauchen keen zousätzleche Koordinatiounsmechanismus. Alles wat Dir braucht ass e Message Broker deen "op d'mannst eemol" Garantien als Transport ënnerstëtzt.

Awer dës Léisung huet och säin eegene "Präis":

  • Geschäftslogik gëtt méi komplex: Kompensatioun muss ausgeschafft ginn;
  • et wäert néideg sinn voll Konsequenz opzeginn, wat besonnesch sensibel fir monolithesch Systemer ka sinn;
  • D'Architektur gëtt e bësse méi komplizéiert, an en zousätzleche Besoin fir e Message Broker schéngt;
  • zousätzlech Iwwerwachung an Administratioun Handwierksgeschir wäert néideg sinn (obwuel am Allgemengen ass dat gutt: d'Qualitéit vum System Service wäert eropgoen).

Fir monolithesch Systemer ass d'Begrënnung fir "Sag" ze benotzen net sou offensichtlech. Fir Mikroservicer an aner SOA, wou héchstwahrscheinlech schonn e Broker gëtt, a voll Konsistenz am Ufank vum Projet geaffert gëtt, kënnen d'Virdeeler vun dësem Muster d'Nodeeler wesentlech iwwerwannen, besonnesch wann et eng bequem API an der Geschäftslogik gëtt. Niveau.

Encapsuléieren Geschäftslogik a Mikroservicer

Wéi mir ugefaang hunn mat Mikroservicer ze experimentéieren, ass eng raisonnabel Fro opgestan: wou ass d'Domain Business Logik a Relatioun zum Service deen d'Persistenz vun den Domaindaten garantéiert?

Wann Dir d'Architektur vu verschiddene BPMSs kuckt, kann et raisonnabel schéngen d'Geschäftslogik vu Persistenz ze trennen: eng Schicht vu Plattform an Domain-onofhängege Mikroservicer erstellen, déi en Ëmfeld a Container bilden fir d'Domain Geschäftslogik auszeféieren, an d'Persistenz vun Domaindaten designen als eng separat Schicht vu ganz einfachen a liichte Mikroservicer. Geschäftsprozesser an dësem Fall féieren d'Orchestratioun vun de Servicer vun der Persistenzschicht.

BPM Stil Integratioun

Dës Approche huet e ganz grousse Virdeel: Dir kënnt d'Funktionalitéit vun der Plattform sou vill erhéijen wéi Dir wëllt, an nëmmen déi entspriechend Schicht vu Plattformmikroservicer wäerte vun dësem "Fett" ginn. Geschäftsprozesser vun all Domain kënnen direkt déi nei Funktionalitéit vun der Plattform benotzen soubal se aktualiséiert gëtt.

Eng méi detailléiert Etude huet bedeitend Nodeeler vun dëser Approche opgedeckt:

  • e Plattformservice deen d'Geschäftslogik vu ville Domainen gläichzäiteg ausféiert dréit grouss Risiken als eenzege Punkt vum Echec. Heefeg Ännerunge fir d'Geschäftslogik erhéijen de Risiko vu Feeler, déi zu systembreet Feeler féieren;
  • Leeschtungsproblemer: Geschäftslogik funktionnéiert mat sengen Donnéeën duerch eng schmuel a lues Interface:
    • d'Donnéeë ginn nach eng Kéier marshalled an duerch de Reseau Stack gepompelt ginn;
    • en Domain Service gëtt dacks méi Daten wéi néideg fir Geschäftslogik ze veraarbecht wéinst net genuch Fäegkeeten fir Ufroen um Niveau vun der externer API vum Service ze parameteriséieren;
    • verschidde onofhängeg Stécker vun der Geschäftslogik kënnen ëmmer erëm déiselwecht Donnéeën fir d'Veraarbechtung ufroen (dëse Problem kann ofgeschaaft ginn andeems Sessiounskomponenten bäigefüügt ginn, déi Daten cache, awer dëst komplizéiert d'Architektur weider a schaaft Problemer vun der Daterelevanz an der Cache-Invalidatioun);
  • Transaktiounsproblemer:
    • Geschäftsprozesser mat persistentem Zoustand, dee vun engem Plattformservice gespäichert ass, sinn onkonsequent mat Domaindaten, an et gi keng einfach Weeër fir dëse Problem ze léisen;
    • Placement Domain Daten Blockéierung ausserhalb vun der Transaktioun: wann d'Domain Business Logik Ännerunge muss maachen nodeems Dir d'Korrektheet vun den aktuellen Donnéeën iwwerpréift huet, ass et néideg d'Méiglechkeet vun enger kompetitiver Ännerung an de veraarbechten Donnéeën auszeschléissen. Extern Dateblockéierung kann hëllefen de Problem ze léisen, awer sou eng Léisung bréngt zousätzlech Risiken a reduzéiert d'allgemeng Zouverlässegkeet vum System;
  • zousätzlech Schwieregkeeten beim Update: an e puer Fäll muss de Persistenzservice an d'Geschäftslogik synchron oder a strikt Sequenz aktualiséiert ginn.

Schlussendlech hu mir missen zréck an d'Basics goen: d'Domaindaten an d'Domaingeschäftslogik an ee Mikroservice kapselen. Dës Approche vereinfacht d'Perceptioun vun engem Mikroservice als integralen Bestanddeel vum System a bréngt net zu den uewe genannte Probleemer. Dëst gëtt och net gratis ginn:

  • API Standardiséierung ass erfuerderlech fir Interaktioun mat Geschäftslogik (besonnesch fir Benotzeraktivitéiten als Deel vu Geschäftsprozesser ze bidden) an API Plattform Servicer; erfuerdert méi virsiichteg Opmierksamkeet op API Ännerungen, no vir an zréck Kompatibilitéit;
  • et ass néideg fir zousätzlech Runtime-Bibliothéiken ze addéieren fir de Fonctionnement vun der Geschäftslogik als Deel vun all esou Mikroservicer ze garantéieren, an dëst entsteet nei Ufuerderunge fir sou Bibliothéiken: Liichtegkeet an e Minimum vu transitive Ofhängegkeeten;
  • Business Logik Entwéckler mussen d'Bibliothéik Versiounen iwwerwaachen: wann e Mikroservice fir eng laang Zäit net finaliséiert ass, da wäert et héchstwahrscheinlech eng al Versioun vun de Bibliothéiken enthalen. Dëst kann en onerwaart Hindernis sinn fir eng nei Feature ze addéieren a kann déi al Geschäftslogik vun esou engem Service op nei Versioune vu Bibliothéiken migréieren wann et inkompatibel Ännerungen tëscht Versioune goufen.

BPM Stil Integratioun

Eng Schicht vu Plattformservicer ass och an esou enger Architektur präsent, awer dës Schicht bildt net méi e Container fir d'Domaingeschäftslogik auszeféieren, awer nëmmen seng Ëmwelt, déi Hilf "Plattform" Funktiounen ubitt. Sou eng Schicht ass net nëmme gebraucht fir déi liicht Natur vun Domain Mikroservicer z'erhalen, awer och fir d'Gestioun ze zentraliséieren.

Zum Beispill, Benotzeraktivitéiten a Geschäftsprozesser generéieren Aufgaben. Wéi och ëmmer, wann Dir mat Aufgaben schafft, muss de Benotzer Aufgaben aus all Domainen an der allgemenger Lëscht gesinn, dat heescht datt et e entspriechende Plattform Taskregistrierungsservice muss sinn, geläscht vun der Domain Business Logik. D'Verkapselung vun der Geschäftslogik an esou engem Kontext erhalen ass zimlech problematesch, an dëst ass en anere Kompromëss vun dëser Architektur.

Integratioun vu Geschäftsprozesser duerch d'Ae vun engem Applikatiounsentwéckler

Wéi uewen ernimmt, muss en Applikatioun Entwéckler vun den techneschen an Ingenieursfeatures abstrachéiert ginn fir d'Interaktioun vu verschiddenen Uwendungen ëmzesetzen, sou datt een op eng gutt Entwécklungsproduktivitéit ziele kann.

Loosst eis probéieren en zimlech schwieregen Integratiounsproblem ze léisen, speziell fir den Artikel erfonnt. Dëst wäert eng "Spill" Aufgab sinn mat dräi Applikatiounen, wou jidderee vun hinnen e bestëmmten Domain Numm definéiert: "app1", "app2", "app3".

Bannen an all Applikatioun ginn Geschäftsprozesser lancéiert, déi ufänken ze "ballen" duerch den Integratiounsbus. Messagen mam Numm "Ball" handelen als Ball.

Regele vum Spill:

  • den éischte Spiller ass den Initiator. Hien invitéiert aner Spiller op d'Spill, fänkt d'Spill un a kann et zu all Moment ophalen;
  • aner Spiller erklären hir Participatioun am Spill, "léieren" all aner an den éischte Spiller;
  • nodeems de Ball kritt huet, wielt de Spiller en aneren deelhuelende Spiller a gitt de Ball him. D'Gesamtzuel vun den Iwwerdroungen gëtt gezielt;
  • All Spiller huet "Energie" déi mat all Pass vum Ball vun deem Spiller erofgeet. Wann d'Energie leeft, verléisst de Spiller d'Spill, annoncéiert seng Demissioun;
  • wann de Spiller eleng gelooss gëtt, annoncéiert hien direkt säin Depart;
  • Wann all Spiller éliminéiert sinn, erklärt den éischte Spiller d'Spill eriwwer. Wann hien d'Spill fréi verléisst, bleift hien d'Spill ze verfollegen fir et fäerdeg ze maachen.

Fir dëse Problem ze léisen, wäert ech eis DSL fir Geschäftsprozesser benotzen, wat eis erlaabt d'Logik an Kotlin kompakt ze beschreiwen, mat engem Minimum vu Kesselplat.

De Geschäftsprozess vum éischte Spiller (och den Initiator vum Spill) funktionnéiert an der App1 Applikatioun:

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

Zousätzlech fir d'Geschäftslogik auszeféieren, kann den uewe genannte Code en Objektmodell vun engem Geschäftsprozess produzéieren, deen a Form vun engem Diagramm visualiséiert ka ginn. Mir hunn de Visualizer nach net implementéiert, also hu mir e bëssen Zäit missen zeechnen (hei hunn ech d'BPMN Notatioun liicht vereinfacht iwwer d'Benotzung vu Paarte fir d'Konsistenz vum Diagramm mam Code hei ënnen ze verbesseren):

BPM Stil Integratioun

app2 wäert de Geschäftsprozess vum anere Spiller enthalen:

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

Diagramm:

BPM Stil Integratioun

An der App3 Applikatioun wäerte mir e Spiller mat engem liicht anescht Verhalen maachen: amplaz de nächste Spiller zoufälleg ze wielen, handelt hien no dem Round-Robin Algorithmus:

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

Soss ënnerscheet d'Spiller d'Behuele net aus der viregter, sou datt d'Diagramm net ännert.

Elo brauche mir en Test fir dëst alles auszeféieren. Ech ginn nëmmen de Code vum Test selwer, fir den Artikel net mat engem Kesselplat ze räissen (tatsächlech hunn ech d'Testëmfeld benotzt, déi virdru erstallt gouf fir d'Integratioun vun anere Geschäftsprozesser ze testen):

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

Loosst eis den Test lafen a kucken de Logbuch:

Konsol Ausgang

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

Aus all deem kënne mir e puer wichteg Conclusiounen zéien:

  • mat den néidegen Tools kënnen d'Applikatiounsentwéckler Integratiounsinteraktiounen tëscht Applikatiounen erstellen ouni d'Geschäftslogik z'ënnerbriechen;
  • d'Komplexitéit vun enger Integratiounsaufgab, déi Ingenieurskompetenzen erfuerdert, kann am Kader verstoppt ginn, wann dëst am Ufank an d'Architektur vum Kader abegraff ass. D'Schwieregkeet vun engem Problem kann net verstoppt ginn, sou datt d'Léisung fir e schwieregen Problem am Code ausgesäit;
  • Wann Dir Integratiounslogik entwéckelt, ass et néideg fir eventuell Konsistenz an de Mangel u Lineariséierung vun Ännerungen am Staat vun all Integratiounsparticipanten ze berücksichtegen. Dëst forcéiert eis d'Logik ze komplizéiere fir se onsensibel ze maachen fir d'Uerdnung an där extern Eventer optrieden. An eisem Beispill ass de Spiller gezwongen um Spill deelzehuelen nodeems hien säin Austrëtt aus dem Spill deklaréiert: aner Spiller wäerte weider him de Ball weiderginn bis d'Informatioun iwwer seng Sortie erreecht a vun all Participanten veraarbecht gëtt. Dës Logik follegt net vun de Regele vum Spill an ass eng Kompromissléisung am Kader vun der gewielter Architektur.

Als nächst wäerte mir iwwer déi verschidde Schwieregkeeten vun eiser Léisung schwätzen, Kompromësser an aner Punkten.

All Messagen sinn an enger Schlaang

All integréiert Uwendungen funktionnéieren mat engem Integratiounsbus, deen a Form vun engem externen Broker presentéiert gëtt, eng BPMQueue fir Messagen an een BPMTopic Thema fir Signaler (Evenementer). All Messagen duerch eng Schlaang ze setzen ass selwer e Kompromiss. Um Business Logik Niveau kënnt Dir elo esou vill nei Messagentypen aféieren wéi Dir wëllt ouni Ännerungen an der Systemstruktur ze maachen. Dëst ass eng bedeitend Vereinfachung, awer et bréngt gewësse Risiken, déi eis am Kader vun eisen typeschen Aufgaben net esou bedeitend ausgesinn hunn.

BPM Stil Integratioun

Wéi och ëmmer, et gëtt eng Subtilitéit hei: all Applikatioun filtert "seng" Messagen aus der Schlaang bei der Entrée, mam Numm vu senger Domain. D'Domain kann och a Signaler spezifizéiert ginn, wann Dir den "Visibilitéitsraum" vum Signal op eng eenzeg Applikatioun limitéiere musst. Dëst soll de Bus-Duerchschnëtt erhéijen, awer d'Geschäftslogik muss elo mat Domainnamen operéieren: fir d'Adresse vu Messagen - obligatoresch, fir Signaler - wënschenswäert.

Garantéieren Integratioun Bus Zouverlässegkeet

Zouverlässegkeet besteet aus e puer Punkten:

  • De gewielte Message Broker ass e kritesche Bestanddeel vun der Architektur an en eenzege Punkt vum Echec: et muss genuch Feelertolerant sinn. Dir sollt nëmmen zäitgetest Implementatioune benotzen, mat gudder Ënnerstëtzung an enger grousser Gemeinschaft;
  • et ass néideg fir eng héich Disponibilitéit vum Messagebroker ze garantéieren, fir deen et kierperlech vun den integréierten Uwendungen getrennt muss ginn (héich Disponibilitéit vun Uwendungen mat ugewandter Geschäftslogik ass vill méi schwéier an deier ze garantéieren);
  • de Broker ass verpflicht "op d'mannst eemol" Liwwerungsgarantien ze bidden. Dëst ass eng obligatoresch Noutwendegkeete fir zouverlässeg Operatioun vun der Integratioun Bus. Et gëtt kee Besoin fir "genau eemol" Niveau Garantien: Affär Prozesser, an der Regel, sinn net sensibel fir déi widderholl Arrivée vun Messagen oder Evenementer, a speziell Aufgaben, wou dat wichteg ass, ass et méi einfach, zousätzlech Kontrollen un d'Geschäft ze addéieren. Logik wéi dauernd zimlech "deier" " Garantien ze benotzen;
  • Messagen a Signaler schécken muss an enger Gesamttransaktioun mat Ännerungen am Zoustand vun de Geschäftsprozesser an Domaindaten involvéiert sinn. Déi léifste Optioun wier e Muster ze benotzen Transaktiounen Outbox, awer et wäert eng zousätzlech Tabell an der Datebank an e Repeater erfuerderen. An JEE Uwendungen kann dëst vereinfacht ginn andeems Dir e lokalen JTA Manager benotzt, awer d'Verbindung zum gewielte Broker muss fäeg sinn ze schaffen XA;
  • Handler vun erakommen Messagen an Eventer mussen och mat enger Transaktioun schaffen, déi den Zoustand vun engem Geschäftsprozess ännert: wann esou eng Transaktioun zréckgerullt gëtt, da muss de Empfang vun der Noriicht annuléiert ginn;
  • Messagen déi wéinst Feeler net geliwwert kënne ginn, mussen an enger separater Späichere gespäichert ginn D.L.Q. (Dead Letter Queue). Fir dësen Zweck hu mir eng separat Plattform Mikroservice erstallt, déi sou Messagen a senger Späichere späichert, se duerch Attributer indexéiert (fir séier Gruppéierung a Sich), an en API aussetzt fir ze kucken, op d'Destinatiounsadress nei ze schécken an d'Botschaften ze läschen. System Administrateuren kënne mat dësem Service duerch hir Web Interface schaffen;
  • an de Broker-Astellungen, musst Dir d'Zuel vun de Liwwerungsverbesserungen a Verspéidungen tëscht Liwwerungen upassen fir d'Wahrscheinlechkeet vu Messagen an DLQ ze reduzéieren (et ass bal onméiglech déi optimal Parameteren ze berechnen, awer Dir kënnt empiresch handelen an se während der Operatioun upassen );
  • Den DLQ Store muss kontinuéierlech iwwerwaacht ginn, an d'Iwwerwaachungssystem muss d'Systemadministratoren alarméieren, sou datt wann net geliwwert Messagen optrieden, se sou séier wéi méiglech äntweren. Dëst wäert der "betraff Beräich" vun engem Echec oder Affär Logik Feeler reduzéieren;
  • den Integratiounsbus muss onsensibel sinn fir d'temporäre Fehlen vun Uwendungen: Abonnementer op en Thema muss haltbar sinn, an den Domain Numm vun der Applikatioun muss eenzegaarteg sinn, sou datt wann d'Applikatioun fehlt, een aneren net probéiert seng Messagen aus der Schlaang.

Garantéieren thread Sécherheet vun Affär Logik

Déiselwecht Instanz vun engem Geschäftsprozess kann e puer Messagen an Eventer gläichzäiteg kréien, d'Veraarbechtung vun deenen parallel ufänkt. Zur selwechter Zäit, fir en Applikatiounsentwéckler, sollt alles einfach a thread-sécher sinn.

D'Geschäftslogik vun engem Prozess veraarbecht all extern Event dat dee Geschäftsprozess individuell beaflosst. Esou Eventer kéinten sinn:

  • lancéiert eng Affär Prozess Instanz;
  • Benotzeraktioun am Zesummenhang mat Aktivitéit bannent engem Geschäftsprozess;
  • Empfang vun engem Message oder Signal op déi eng Affär Prozess Instanz abonnéiert ass;
  • Ausléisung vun engem Timer, deen vun enger Geschäftsprozessinstanz festgeluecht gouf;
  • Kontroll Aktioun via API (Zum Beispill, Prozess Ënnerbriechung).

All esou Event kann den Zoustand vun enger Geschäftsprozessinstanz änneren: e puer Aktivitéite kënnen ophalen an anerer kënnen ufänken, an d'Wäerter vu persistent Eegeschafte kënnen änneren. All Aktivitéit zoumaachen kann zu der Aktivatioun vun enger oder méi vun de folgenden Aktivitéiten féieren. Déi, am Tour, kënnen ophalen op aner Eventer ze waarden oder, wa se keng zousätzlech Donnéeën brauchen, kënnen an der selwechter Transaktioun ofgeschloss ginn. Ier Dir d'Transaktioun ofschléisst, gëtt den neie Staat vum Geschäftsprozess an der Datebank gespäichert, wou et op déi nächst extern Event wart.

Persistent Geschäftsprozessdaten, déi an enger relationaler Datebank gespäichert sinn, ass e ganz praktesche Punkt fir d'Veraarbechtung ze synchroniséieren wann Dir SELECT FIR UPDATE benotzt. Wann eng Transaktioun et fäerdeg bruecht huet den Zoustand vun engem Geschäftsprozess aus der Basis ze kréien fir se z'änneren, da wäert keng aner Transaktioun parallel fäeg sinn dee selwechte Staat fir eng aner Ännerung ze kréien, an no der Ofschloss vun der éischter Transaktioun ass déi zweet garantéiert de scho geännert Staat ze kréien.

Mat pessimistesche Spären op der DBMS Säit erfëllen mir all déi néideg Ufuerderungen sauerem, an och d'Fäegkeet behalen fir d'Applikatioun mat Geschäftslogik ze skaléieren andeems d'Zuel vun de lafenden Instanzen eropgeet.

Wéi och ëmmer, pessimistesch Spären bedrohen eis mat Deadlocks, dat heescht datt SELECT FIR UPDATE nach ëmmer op e vernünfteg Timeout begrenzt sollt sinn, am Fall wou Deadlocks op e puer schrecklech Fäll an der Geschäftslogik optrieden.

En anere Problem ass d'Synchroniséierung vum Start vun engem Geschäftsprozess. Och wann et keng Instanz vun engem Geschäftsprozess gëtt, gëtt et kee Staat an der Datebank, sou datt déi beschriwwe Method net funktionnéiert. Wann Dir d'Eenzegaartegkeet vun enger Geschäftsprozessinstanz an engem spezifeschen Ëmfang muss garantéieren, da braucht Dir eng Aart vu Synchroniséierungsobjekt verbonne mat der Prozessklass an dem entspriechende Ëmfang. Fir dëse Problem ze léisen, benotze mir en anere Sperrmechanismus, deen eis erlaabt e Spär op eng arbiträr Ressource ze huelen, déi duerch e Schlëssel am URI-Format duerch en externen Service spezifizéiert ass.

An eise Beispiller enthält den InitialPlayer Geschäftsprozess eng Deklaratioun

uniqueConstraint = UniqueConstraints.singleton

Dofir enthält de Logbuch Messagen iwwer d'Ofhuelen an d'Verëffentlechung vun der Spär vum entspriechende Schlëssel. Et gi keng esou Messagen fir aner Geschäftsprozesser: uniqueConstraint ass net gesat.

Problemer vun Affär Prozesser mat bestänneg Staat

Heiansdo hëlleft e persistent Zoustand net nëmmen, awer och wierklech d'Entwécklung.
Probleemer fänken un wann Ännerungen un der Geschäftslogik an / oder Geschäftsprozessmodell musse gemaach ginn. Net all esou Ännerung ass kompatibel mam alen Zoustand vun de Geschäftsprozesser. Wann et vill Live Instanzen an der Datebank sinn, da kann inkompatibel Ännerunge vill Probleemer verursaachen, déi mir dacks begéint hunn wann Dir jBPM benotzt.

Ofhängeg vun der Tiefe vun den Ännerungen, kënnt Dir op zwou Weeër handelen:

  1. en neie Geschäftsprozesstyp erstellen fir net inkompatibel Ännerungen op déi al ze maachen, a benotzt se amplaz vum alen wann Dir nei Instanzen lancéiert. Al Exemplare wäerte weider "wéi virdrun" schaffen;
  2. de persistent Zoustand vu Geschäftsprozesser migréieren wann Dir d'Geschäftslogik aktualiséiert.

Den éischte Wee ass méi einfach, awer huet seng Aschränkungen an Nodeeler, zum Beispill:

  • Duplikatioun vu Geschäftslogik a ville Geschäftsprozessmodeller, erhéicht de Volume vun der Geschäftslogik;
  • Oft ass en direkten Iwwergank op eng nei Geschäftslogik erfuerderlech (a punkto Integratiounsaufgaben - bal ëmmer);
  • den Entwéckler weess net op wéi engem Punkt verouderte Modeller kënne geläscht ginn.

An der Praxis benotze mir béid Approche, awer hunn eng Rei Entscheedunge getraff fir eist Liewen méi einfach ze maachen:

  • An der Datebank gëtt de persistent Zoustand vun engem Geschäftsprozess an enger liicht liesbarer a liicht veraarbechter Form gelagert: an engem JSON Format String. Dëst erlaabt Migratiounen souwuel bannent der Applikatioun an extern auszeféieren. Als leschten Auswee kënnt Dir et manuell korrigéieren (besonnesch nëtzlech an der Entwécklung während der Debugging);
  • d'Integratiounsgeschäftslogik benotzt d'Nimm vu Geschäftsprozesser net, sou datt et zu all Moment méiglech ass d'Ëmsetzung vun engem vun de deelhuelende Prozesser duerch en neien mat engem neien Numm ze ersetzen (zum Beispill "InitialPlayerV2"). D'Verbindung geschitt duerch Message an Signal Nimm;
  • de Prozessmodell huet eng Versiounsnummer, déi mir inkrementéieren wa mir inkompatibel Ännerungen un dësem Modell maachen, an dës Zuel gëtt zesumme mam Zoustand vun der Prozessinstanz gespäichert;
  • de bestännegen Zoustand vum Prozess gëtt aus der Datebank fir d'éischt an e prakteschen Objektmodell gelies, mat deem d'Migratiounsprozedur funktionéiere kann, wann d'Modellversiounsnummer geännert huet;
  • d'Migratiounsprozedur gëtt nieft der Geschäftslogik plazéiert a gëtt "faul" fir all Instanz vum Geschäftsprozess zum Zäitpunkt vu senger Restauratioun aus der Datebank genannt;
  • wann Dir musst den Zoustand vun all Prozess Instanzen séier a synchron migréieren, méi klassesch Datebank Migratioun Léisungen benotzt ginn, mä Dir musst mat JSON schaffen.

Braucht Dir en anere Kader fir Geschäftsprozesser?

D'Léisungen, déi am Artikel beschriwwe ginn, hunn eis erlaabt eist Liewen wesentlech ze vereinfachen, d'Gamme vu Probleemer auszebauen, déi um Applikatiounsentwécklungsniveau geléist ginn, an d'Iddi fir d'Geschäftslogik an d'Mikroservicer méi attraktiv ze trennen. Fir dëst z'erreechen, gouf vill Aarbecht gemaach, e ganz "liichte" Kader fir Geschäftsprozesser erstallt, souwéi Servicekomponenten fir déi identifizéiert Problemer am Kontext vun enger breeder Palette vun Applikatiounsproblemer ze léisen. Mir hunn e Wonsch dës Resultater ze deelen an d'Entwécklung vu gemeinsame Komponenten oppen Zougang ënner enger gratis Lizenz ze maachen. Dëst wäert e puer Effort an Zäit verlaangen. D'Nofro fir esou Léisungen ze verstoen kéint en zousätzlechen Ureiz fir eis sinn. Am proposéierten Artikel gëtt ganz wéineg Opmierksamkeet op d'Fähigkeiten vum Kader selwer bezuelt, awer e puer vun hinnen si siichtbar aus de presentéiert Beispiller. Wa mir eise Kader verëffentlechen, gëtt en getrennten Artikel dofir gewidmet. An der Tëschenzäit wiere mir dankbar wann Dir e klenge Feedback hannerloosst andeems Dir d'Fro beäntwert:

Nëmme registréiert Benotzer kënnen un der Ëmfro deelhuelen. Umellen, wann ech glift.

Braucht Dir en anere Kader fir Geschäftsprozesser?

  • 18,8%Jo, ech sichen scho laang no sou eppes

  • 12,5%Ech sinn interesséiert méi iwwer Är Ëmsetzung ze léieren, et kéint nëtzlech sinn2

  • 6,2%Mir benotzen ee vun de bestehend Kaderen, awer denken drun ze ersetzen1

  • 18,8%Mir benotzen ee vun de existente Kaderen, alles ass gutt3

  • 18,8%mir geréieren ouni Kader3

  • 25,0%schreiwen Är 4

16 Benotzer hunn gestëmmt. 7 Benotzer hu sech enthalen.

Source: will.com

Setzt e Commentaire