BPM üslubunun inteqrasiyası

BPM üslubunun inteqrasiyası

Salam, Habr!

Şirkətimiz ERP səviyyəli proqram həllərinin işlənib hazırlanmasında ixtisaslaşıb, burada aslan payı böyük həcmdə biznes məntiqi və iş axını a la EDMS ilə əməliyyat sistemləri tərəfindən tutulur. Məhsullarımızın müasir versiyaları JavaEE texnologiyalarına əsaslanır, lakin biz mikroservislərlə də fəal şəkildə təcrübə aparırıq. Belə həllərin ən problemli sahələrindən biri bitişik domenlərlə əlaqəli müxtəlif alt sistemlərin inteqrasiyasıdır. İstifadə etdiyimiz memarlıq üslublarından, texnologiya yığınlarından və çərçivələrdən asılı olmayaraq, inteqrasiya vəzifələri həmişə bizə böyük bir başağrısı verdi, lakin son vaxtlar bu cür problemlərin həllində irəliləyiş var.

Diqqətinizə çatdırılan məqalədə NPO Kristanın təyin olunmuş ərazidəki təcrübəsi və memarlıq araşdırmalarından danışacağam. Biz həmçinin proqram tərtibatçısı nöqteyi-nəzərindən inteqrasiya probleminin sadə həlli nümunəsini nəzərdən keçirəcəyik və bu sadəliyin arxasında nəyin gizləndiyini öyrənəcəyik.

İmtina

Məqalədə təsvir olunan memarlıq və texniki həllər mənim tərəfimdən xüsusi tapşırıqlar kontekstində şəxsi təcrübə əsasında təklif olunur. Bu həllər universal olduğunu iddia etmir və digər istifadə şərtlərində optimal olmaya bilər.

BPM-nin bununla nə əlaqəsi var?

Bu suala cavab vermək üçün həll yollarımızın tətbiqi problemlərinin xüsusiyyətlərini bir az araşdırmalıyıq. Tipik tranzaksiya sistemimizdə biznes məntiqinin əsas hissəsi istifadəçi interfeysləri vasitəsilə verilənlər bazasına verilənlərin daxil edilməsi, bu məlumatların əl ilə və avtomatik yoxlanılması, hansısa iş prosesindən keçirilməsi, başqa sistemə/analitik verilənlər bazasına/arxivə dərc edilməsi, hesabatların yaradılmasıdır. Beləliklə, müştərilər üçün sistemin əsas funksiyası onların daxili biznes proseslərinin avtomatlaşdırılmasıdır.

Rahatlıq üçün biz kommunikasiyada "sənəd" terminini ümumi açarla birləşdirilən, müəyyən bir iş axınının "əlavə edilə" biləcəyi məlumat dəstinin bəzi abstraksiyaları kimi istifadə edirik.
Bəs inteqrasiya məntiqi haqqında nə demək olar? Axı, inteqrasiya vəzifəsi müştərinin istəyi ilə DEYİL, tamamilə fərqli amillərin təsiri altında hissələrə "mişarlanmış" sistemin arxitekturası tərəfindən yaradılır:

  • Konvey qanununun təsiri altında;
  • əvvəllər digər məhsullar üçün hazırlanmış alt sistemlərin təkrar istifadəsi nəticəsində;
  • qeyri-funksional tələblərə əsaslanaraq memar tərəfindən qərar verildiyi kimi.

Biznes məntiqini inteqrasiya artefaktları ilə çirkləndirməmək və proqram tərtibatçısını sistemin memarlıq landşaftının xüsusiyyətlərini araşdırmaq məcburiyyətindən xilas etmək üçün inteqrasiya məntiqini əsas iş axınının biznes məntiqindən ayırmaq üçün böyük bir cazibə var. Bu yanaşmanın bir sıra üstünlükləri var, lakin təcrübə onun səmərəsizliyini göstərir:

  • inteqrasiya problemlərinin həlli adətən əsas iş axınının həyata keçirilməsində məhdud uzatma nöqtələri səbəbindən sinxron zənglər şəklində ən sadə variantlara doğru sürüşür (aşağıda sinxron inteqrasiyanın çatışmazlıqları haqqında daha çox məlumat);
  • inteqrasiya artefaktları başqa alt sistemdən rəy tələb olunduqda hələ də əsas biznes məntiqinə nüfuz edir;
  • proqram tərtibatçısı inteqrasiyaya məhəl qoymur və iş prosesini dəyişdirərək asanlıqla onu poza bilər;
  • sistem istifadəçi nöqteyi-nəzərindən vahid bütöv olmaqdan çıxır, alt sistemlər arasında "tikişlər" nəzərə çarpır, məlumatların bir alt sistemdən digərinə ötürülməsini başlatan lazımsız istifadəçi əməliyyatları meydana çıxır.

Başqa bir yanaşma inteqrasiya qarşılıqlı əlaqələrini əsas biznes məntiqinin və iş axınının tərkib hissəsi kimi nəzərdən keçirməkdir. Tətbiq tərtibatçılarının bacarıq tələblərinin sürətlə artmasının qarşısını almaq üçün yeni inteqrasiya qarşılıqlı əlaqələrinin yaradılması həllin seçilməsi üçün minimal seçimlərlə asanlıqla və təbii şəkildə həyata keçirilməlidir. Bu göründüyündən daha çətindir: alət istifadəçiyə onun istifadəsi üçün lazımi müxtəlif variantları təqdim etmək və eyni zamanda ayağından vurulmasına imkan verməmək üçün kifayət qədər güclü olmalıdır. Mühəndisin inteqrasiya tapşırıqları kontekstində cavab verməli olduğu, lakin proqram tərtibatçısının gündəlik işində düşünməməli olduğu bir çox suallar var: əməliyyat sərhədləri, ardıcıllıq, atomiklik, təhlükəsizlik, miqyaslama, yük və resurs bölgüsü, marşrutlaşdırma, sıralama, yayılması və kommutasiya kontekstləri və s. Tətbiq tərtibatçılarına kifayət qədər sadə qərar şablonları təklif etmək lazımdır ki, burada bütün bu kimi sualların cavabları artıq gizlədilir. Bu nümunələr kifayət qədər təhlükəsiz olmalıdır: biznes məntiqi çox tez-tez dəyişir, bu da səhvlərin tətbiqi riskini artırır, səhvlərin dəyəri kifayət qədər aşağı səviyyədə qalmalıdır.

Ancaq yenə də BPM-nin bununla nə əlaqəsi var? İş prosesini həyata keçirmək üçün bir çox variant var ...
Həqiqətən də, biznes proseslərinin başqa bir tətbiqi həllərimizdə çox populyardır - dövlət keçid diaqramının deklarativ qurulması və işləyiciləri biznes məntiqi ilə keçidlərə birləşdirməklə. Eyni zamanda biznes prosesində “sənədin” cari mövqeyini müəyyən edən dövlət “sənədin” özünün atributudur.

BPM üslubunun inteqrasiyası
Layihənin başlanğıcında proses belə görünür

Belə bir tətbiqin populyarlığı xətti iş proseslərinin yaradılmasının nisbi sadəliyi və sürəti ilə bağlıdır. Bununla belə, proqram sistemləri mürəkkəbləşdikcə, biznes prosesinin avtomatlaşdırılmış hissəsi böyüyür və mürəkkəbləşir. Parçalanmaya, proseslərin hissələrinin təkrar istifadəsinə, həmçinin çəngəl proseslərinə ehtiyac var ki, hər bir budaq paralel olaraq yerinə yetirilsin. Belə şəraitdə alət əlverişsiz olur və vəziyyətə keçid diaqramı informasiya məzmununu itirir (inteqrasiya qarşılıqlı əlaqəsi diaqramda ümumiyyətlə əks olunmur).

BPM üslubunun inteqrasiyası
Tələblərin aydınlaşdırılmasının bir neçə təkrarlanmasından sonra proses belə görünür

Bu vəziyyətdən çıxış yolu mühərrikin inteqrasiyası idi jBPM ən mürəkkəb iş proseslərinə malik bəzi məhsullara. Qısa müddətdə bu həll bir qədər uğur qazandı: qeyddə kifayət qədər məlumatlandırıcı və aktual diaqram saxlamaqla mürəkkəb iş proseslərini həyata keçirmək mümkün oldu. BPMN2.

BPM üslubunun inteqrasiyası
Mürəkkəb bir iş prosesinin kiçik bir hissəsi

Uzunmüddətli perspektivdə həll gözləntiləri doğrultmadı: vizual alətlər vasitəsilə biznes proseslərinin yaradılmasının yüksək əmək intensivliyi məqbul məhsuldarlıq göstəricilərinə nail olmağa imkan vermədi və alətin özü tərtibatçılar arasında ən çox bəyənilməyənlərdən birinə çevrildi. Mühərrikin daxili quruluşu ilə bağlı şikayətlər də var idi, bu da bir çox "yamaqların" və "köpəklərin" görünməsinə səbəb oldu.

jBPM-dən istifadənin əsas müsbət cəhəti biznes prosesi nümunəsi üçün öz davamlı vəziyyətinə malik olmağın fayda və zərərlərinin həyata keçirilməsi idi. Siqnallar və mesajlar vasitəsilə asinxron qarşılıqlı əlaqədən istifadə edərək müxtəlif tətbiqlər arasında mürəkkəb inteqrasiya protokollarını həyata keçirmək üçün proses yanaşmasından istifadə etmək imkanını da gördük. Bunda davamlı dövlətin olması həlledici rol oynayır.

Yuxarıda göstərilənlərə əsaslanaraq belə nəticəyə gələ bilərik: BPM üslubunda proses yanaşması bizə getdikcə daha mürəkkəb iş proseslərinin avtomatlaşdırılması üçün geniş spektrli vəzifələri həll etməyə, inteqrasiya fəaliyyətlərini bu proseslərə ahəngdar şəkildə uyğunlaşdırmağa və həyata keçirilən prosesi uyğun notada vizual olaraq göstərmək qabiliyyətini saxlamağa imkan verir.

Sinxron zənglərin inteqrasiya nümunəsi kimi çatışmazlıqları

Sinxron inteqrasiya ən sadə bloklama çağırışına aiddir. Bir alt sistem server tərəfi kimi çıxış edir və API-ni istədiyiniz metodla ifşa edir. Başqa bir alt sistem müştəri tərəfi kimi çıxış edir və lazımi anda nəticə gözləməsi ilə zəng edir. Sistemin arxitekturasından asılı olaraq, müştəri və server tərəfləri ya eyni proqram və prosesdə, ya da fərqli olaraq yerləşdirilə bilər. İkinci halda, RPC-nin bəzi tətbiqlərini tətbiq etməli və parametrlərin və çağırışın nəticəsinin sıralanmasını təmin etməlisiniz.

BPM üslubunun inteqrasiyası

Belə bir inteqrasiya nümunəsi kifayət qədər böyük çatışmazlıqlara malikdir, lakin sadəliyinə görə praktikada çox geniş istifadə olunur. Həyata keçirmə sürəti sizi valeh edir və həllini texniki borclara yazaraq "yanan" son tarixlərdə təkrar-təkrar tətbiq etməyə məcbur edir. Amma elə olur ki, təcrübəsiz tərtibatçılar ondan şüursuz şəkildə istifadə edirlər, sadəcə olaraq mənfi nəticələri dərk etmirlər.

Alt sistemlərin əlaqəsinin ən bariz artması ilə yanaşı, əməliyyatların "yayılması" və "uzanması" ilə bağlı daha az aşkar problemlər var. Həqiqətən, əgər biznes məntiqi hər hansı dəyişiklik edərsə, əməliyyatlar əvəzolunmazdır və əməliyyatlar da öz növbəsində bu dəyişikliklərin təsirinə məruz qalan müəyyən proqram resurslarını bloklayır. Yəni, bir alt sistem digərindən cavab gözləməyənə qədər tranzaksiyanı başa çatdıra və kilidləri buraxa bilməyəcək. Bu, müxtəlif təsirlərin riskini əhəmiyyətli dərəcədə artırır:

  • sistemin cavab vermə qabiliyyəti itir, istifadəçilər sorğuların cavablarını uzun müddət gözləyirlər;
  • server, ümumiyyətlə, daşqın bir mövzu hovuzuna görə istifadəçi sorğularına cavab verməyi dayandırır: mövzuların çoxu əməliyyat tərəfindən tutulan resursun kilidində "durur";
  • çıxılmaz vəziyyətlər görünməyə başlayır: onların baş vermə ehtimalı əməliyyatların müddətindən, biznes məntiqinin miqdarından və əməliyyatda iştirak edən kilidlərdən çox asılıdır;
  • əməliyyatın müddətinin bitməsi xətaları görünür;
  • Əgər tapşırıq böyük həcmdə məlumatların işlənməsini və dəyişdirilməsini tələb edirsə, server OutOfMemory-ə “düşür” və sinxron inteqrasiyaların olması emalın “daha ​​yüngül” əməliyyatlara bölünməsini çox çətinləşdirir.

Memarlıq nöqteyi-nəzərindən inteqrasiya zamanı bloklama çağırışlarından istifadə ayrı-ayrı altsistemlərin keyfiyyətə nəzarətinin itirilməsinə gətirib çıxarır: digər altsistem üçün keyfiyyət göstəricilərindən təcrid olunmuş halda bir altsistem üzrə keyfiyyət hədəflərinə nail olmaq mümkün deyil. Əgər alt sistemlər müxtəlif komandalar tərəfindən hazırlanırsa, bu, böyük problemdir.

İnteqrasiya edilən alt sistemlər müxtəlif tətbiqlərdə olarsa və hər iki tərəfdən sinxron dəyişikliklər edilməlidirsə, işlər daha da maraqlı olur. Bu dəyişiklikləri necə əməliyyat etmək olar?

Dəyişikliklər ayrı-ayrı əməliyyatlarda edilirsə, o zaman möhkəm istisnaların idarə edilməsi və kompensasiya təmin edilməlidir və bu, sinxron inteqrasiyaların əsas üstünlüyünü - sadəliyi tamamilə aradan qaldırır.

Paylanmış əməliyyatlar da ağlımıza gəlir, lakin biz həllərimizdə onlardan istifadə etmirik: etibarlılığı təmin etmək çətindir.

"Dastan" əməliyyatlar probleminin həlli kimi

Mikroservislərin populyarlığının artması ilə bu xidmətlərə tələbat artır Saga Pattern.

Bu nümunə yuxarıda göstərilən uzun əməliyyatların problemlərini mükəmməl həll edir, həmçinin sistemin vəziyyətini biznes məntiqi baxımından idarə etmək imkanlarını genişləndirir: uğursuz əməliyyatdan sonra kompensasiya sistemi orijinal vəziyyətinə qaytarmaya bilər, əksinə alternativ təmin edir. məlumatların emalı marşrutu. O, həmçinin prosesi "yaxşı" sona çatdırmağa çalışdığınız zaman müvəffəqiyyətlə tamamlanmış məlumatların işlənməsi addımlarını təkrarlamamağa imkan verir.

Maraqlıdır ki, monolit sistemlərdə bu nümunə, boş birləşmiş alt sistemlərin inteqrasiyasına gəldikdə də aktualdır və uzun əməliyyatların və müvafiq resurs kilidlərinin səbəb olduğu mənfi təsirlər var.

BPM üslubunda iş proseslərimizə gəldikdə, Saqaları həyata keçirmək çox asandır: Saqaların fərdi addımları biznes prosesi çərçivəsində fəaliyyətlər kimi təyin edilə bilər və biznes prosesinin davamlı vəziyyəti arasında müəyyən edilir. başqa şeylər, dastanların daxili vəziyyəti. Yəni bizim əlavə koordinasiya mexanizminə ehtiyacımız yoxdur. Sizə lazım olan tək şey nəqliyyat kimi "ən azı bir dəfə" zəmanətləri dəstəkləyən mesaj brokeridir.

Ancaq belə bir həllin də öz "qiyməti" var:

  • iş məntiqi daha mürəkkəbləşir: kompensasiya işləyib hazırlamaq lazımdır;
  • monolit sistemlər üçün xüsusilə həssas ola biləcək tam tutarlılıqdan imtina etmək lazımdır;
  • arxitektura bir az daha mürəkkəbləşir, mesaj brokerinə əlavə ehtiyac yaranır;
  • əlavə monitorinq və idarəetmə vasitələri tələb olunacaq (baxmayaraq ki, ümumilikdə bu, hətta yaxşıdır: sistem xidmətinin keyfiyyəti yüksələcək).

Monolit sistemlər üçün "Sags" istifadə üçün əsaslandırma o qədər də aydın deyil. Çox güman ki, artıq bir brokerin olduğu və layihənin başlanğıcında tam ardıcıllığın qurban verildiyi mikroservislər və digər SOA-lar üçün, bu nümunədən istifadənin faydaları çatışmazlıqlardan əhəmiyyətli dərəcədə üstün ola bilər, xüsusən də operatorda rahat bir API varsa. biznes məntiqi səviyyəsi.

Mikroservislərdə biznes məntiqinin inkapsulyasiyası

Mikroservislərlə təcrübə aparmağa başlayanda ağlabatan sual yarandı: domen məlumatlarının davamlılığını təmin edən xidmətlə bağlı domen biznes məntiqini hara qoymaq lazımdır?

Müxtəlif BPMS-lərin arxitekturasına nəzər saldıqda, biznes məntiqini davamlılıqdan ayırmaq məqsədəuyğun görünə bilər: domen biznes məntiqini yerinə yetirmək üçün mühiti və konteyneri təşkil edən platforma və domendən asılı olmayan mikroservislər təbəqəsi yaradın və domen məlumatlarının davamlılığını ayrıca olaraq təşkil edin. çox sadə və yüngül mikroservislər təbəqəsi. Bu halda biznes prosesləri davamlılıq qatının xidmətlərinin orkestrləşdirilməsini həyata keçirir.

BPM üslubunun inteqrasiyası

Bu yanaşmanın çox böyük bir üstünlüyü var: platformanın funksionallığını istədiyiniz qədər artıra bilərsiniz və yalnız platforma mikroservislərinin müvafiq təbəqəsi bundan “yağlı olacaq”. İstənilən domendən olan biznes proseslər platformanın yeni funksionallığından dərhal istifadə etmək imkanı əldə edir.

Daha ətraflı araşdırma bu yanaşmanın əhəmiyyətli çatışmazlıqlarını aşkar etdi:

  • bir çox domenlərin biznes məntiqini eyni anda yerinə yetirən platforma xidməti tək uğursuzluq nöqtəsi kimi böyük risklər daşıyır. Biznes məntiqində tez-tez dəyişikliklər sistem miqyasında uğursuzluqlara səbəb olan səhvlərin riskini artırır;
  • performans problemləri: biznes məntiqi öz məlumatları ilə dar və yavaş interfeys vasitəsilə işləyir:
    • məlumatlar yenidən yığılacaq və şəbəkə yığını vasitəsilə nəql ediləcək;
    • domen xidməti tez-tez xidmətin xarici API səviyyəsində sorğu parametrlərinin qeyri-kafi olması səbəbindən iş məntiqinin emal üçün tələb etdiyindən daha çox məlumat qaytaracaq;
    • bir neçə müstəqil biznes məntiqi eyni verilənləri emal üçün təkrar tələb edə bilər (siz verilənləri keşləyən sessiya lobyaları əlavə etməklə bu problemi azalda bilərsiniz, lakin bu, arxitekturanı daha da çətinləşdirir və məlumatların təzəliyi və keşin etibarsızlığı problemləri yaradır);
  • əməliyyat məsələləri:
    • platforma xidməti tərəfindən saxlanılan davamlı vəziyyətə malik biznes prosesləri domen məlumatlarına uyğun gəlmir və bu problemi həll etməyin asan yolları yoxdur;
    • domen məlumatlarının kilidinin tranzaksiyadan kənara çıxarılması: əgər domen biznes məntiqinə dəyişiklik etmək lazımdırsa, ilk növbədə faktiki məlumatların düzgünlüyünü yoxladıqdan sonra işlənmiş məlumatlarda rəqabətli dəyişiklik ehtimalını istisna etmək lazımdır. Məlumatların xarici bloklanması problemin həllinə kömək edə bilər, lakin belə bir həll əlavə risklər daşıyır və sistemin ümumi etibarlılığını azaldır;
  • yeniləmə zamanı əlavə çətinliklər: bəzi hallarda davamlılıq xidmətini və biznes məntiqini sinxron və ya ciddi ardıcıllıqla yeniləməlisiniz.

Sonda mən əsaslara qayıtmalı oldum: domen məlumatlarını və domen biznes məntiqini bir mikroservisə daxil edin. Bu yanaşma mikroservisin sistemdə ayrılmaz komponent kimi qavranılmasını sadələşdirir və yuxarıda göstərilən problemlərə səbəb olmur. Bu da pulsuz deyil:

  • API standartlaşdırılması biznes məntiqi (xüsusilə, biznes proseslərinin bir hissəsi kimi istifadəçi fəaliyyətlərini təmin etmək üçün) və API platforması xidmətləri ilə qarşılıqlı əlaqə üçün tələb olunur; API dəyişikliklərinə daha diqqətli diqqət, irəli və geri uyğunluq tələb olunur;
  • hər bir belə mikroxidmətin bir hissəsi kimi biznes məntiqinin işləməsini təmin etmək üçün əlavə iş vaxtı kitabxanalarının əlavə edilməsi tələb olunur və bu, belə kitabxanalar üçün yeni tələblərin yaranmasına səbəb olur: yüngüllük və minimum keçid asılılıqları;
  • biznes məntiqi tərtibatçıları kitabxana versiyalarını izləməlidirlər: əgər mikroservis uzun müddətdir ki, yekunlaşdırılmayıbsa, onda çox güman ki, kitabxanaların köhnəlmiş versiyası olacaq. Bu, yeni funksiya əlavə etmək üçün gözlənilməz maneə ola bilər və versiyalar arasında uyğun olmayan dəyişikliklər olarsa, belə bir xidmətin köhnə biznes məntiqinin kitabxanaların yeni versiyalarına köçürülməsini tələb edə bilər.

BPM üslubunun inteqrasiyası

Belə bir arxitekturada platforma xidmətlərinin təbəqəsi də mövcuddur, lakin bu təbəqə artıq domen biznes məntiqini yerinə yetirmək üçün konteyner deyil, yalnız köməkçi “platforma” funksiyalarını təmin edən onun mühitini təşkil edir. Belə bir təbəqə yalnız domen mikroservislərinin yüngüllüyünü qorumaq üçün deyil, həm də idarəetmənin mərkəzləşdirilməsi üçün lazımdır.

Məsələn, biznes proseslərində istifadəçi fəaliyyətləri tapşırıqlar yaradır. Bununla belə, tapşırıqlarla işləyərkən istifadəçi ümumi siyahıda bütün domenlərdən olan tapşırıqları görməlidir, bu o deməkdir ki, domen biznes məntiqindən təmizlənmiş müvafiq tapşırıq qeydiyyatı platforması xidməti olmalıdır. Bu kontekstdə biznes məntiqinin inkapsulyasiyasını saxlamaq olduqca problemlidir və bu, bu arxitekturanın başqa bir güzəştidir.

Proqram tərtibatçısının gözü ilə biznes proseslərinin inteqrasiyası

Artıq yuxarıda qeyd edildiyi kimi, proqram tərtibatçısı yaxşı inkişaf məhsuldarlığına arxalana bilmək üçün bir neçə tətbiqin qarşılıqlı əlaqəsinin həyata keçirilməsinin texniki və mühəndis xüsusiyyətlərindən mücərrəd olmalıdır.

Məqalə üçün xüsusi olaraq icad edilmiş olduqca çətin inteqrasiya problemini həll etməyə çalışaq. Bu, üç tətbiqi əhatə edən "oyun" tapşırığı olacaq, burada onların hər biri bəzi domen adını təyin edir: "app1", "app2", "app3".

Hər bir tətbiqin daxilində inteqrasiya avtobusu vasitəsilə "top oynamağa" başlayan biznes prosesləri işə salınır. "Top" adlı mesajlar top rolunu oynayacaq.

Oyunun qaydaları:

  • ilk oyunçu təşəbbüskardır. O, başqa oyunçuları oyuna dəvət edir, oyunu başlayır və istənilən vaxt bitirə bilər;
  • digər oyunçular oyunda iştirak etdiklərini bəyan edirlər, bir-biri ilə və birinci oyunçu ilə "tanış olurlar";
  • topu qəbul etdikdən sonra oyunçu başqa bir iştirakçı oyunçu seçir və topu ona ötürür. Keçidlərin ümumi sayı hesablanır;
  • hər bir oyunçunun "enerji" var ki, o oyunçu tərəfindən topun hər ötürməsi ilə azalır. Enerji tükəndikdə, oyunçu təqaüdə çıxdığını elan edərək oyundan kənarlaşdırılır;
  • oyunçu tək qalsa, dərhal getdiyini elan edir;
  • bütün oyunçular kənarlaşdırıldıqda, birinci oyunçu oyunun bitdiyini elan edir. Əgər o, oyunu daha əvvəl tərk edibsə, onu başa çatdırmaq üçün oyunu izləmək qalır.

Bu problemi həll etmək üçün, Kotlin-də məntiqi minimum səviyyədə kompakt şəkildə təsvir etməyə imkan verən biznes prosesləri üçün DSL-dən istifadə edəcəyəm.

App1 tətbiqində ilk oyunçunun (o, həm də oyunun təşəbbüskarıdır) iş prosesi işləyəcək:

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

İş məntiqini yerinə yetirməklə yanaşı, yuxarıdakı kod diaqram kimi vizuallaşdırıla bilən biznes prosesinin obyekt modelini yarada bilər. Vizualizatoru hələ tətbiq etməmişik, ona görə də rəsm çəkməyə bir qədər vaxt sərf etməli olduq (burada diaqramın yuxarıdakı kodla uyğunluğunu yaxşılaşdırmaq üçün qapıların istifadəsi ilə bağlı BPMN notasiyasını bir qədər sadələşdirdim):

BPM üslubunun inteqrasiyası

app2 başqa oyunçunun iş prosesini əhatə edəcək:

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

Diaqram:

BPM üslubunun inteqrasiyası

App3 tətbiqində oyunçunu bir az fərqli davranışa sahib edəcəyik: təsadüfi olaraq növbəti oyunçunu seçmək əvəzinə, o, dairəvi sistem alqoritminə uyğun hərəkət edəcək:

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

Əks halda, oyunçunun davranışı əvvəlkindən fərqlənmir, ona görə də diaqram dəyişmir.

İndi bütün bunları həyata keçirmək üçün bizə bir test lazımdır. Məqaləni boşqab ilə qarışdırmamaq üçün yalnız testin kodunu verəcəyəm (əslində digər iş proseslərinin inteqrasiyasını yoxlamaq üçün əvvəllər yaradılmış test mühitindən istifadə etdim):

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

Testi işə salın, jurnala baxın:

konsol çıxışı

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

Bütün bunlardan bir neçə mühüm nəticə çıxarmaq olar:

  • zəruri alətlər mövcud olduqda, proqram tərtibatçıları biznes məntiqindən qopmadan proqramlar arasında inteqrasiya qarşılıqlı əlaqə yarada bilərlər;
  • mühəndislik səriştələri tələb edən inteqrasiya tapşırığının mürəkkəbliyi (mürəkkəbliyi) ilkin olaraq çərçivənin arxitekturasında qeyd olunarsa, çərçivə daxilində gizlənə bilər. Tapşırığın çətinliyi (çətinlik) gizlənə bilməz, ona görə də kodda çətin tapşırığın həlli buna uyğun görünəcək;
  • inteqrasiya məntiqini inkişaf etdirərkən, nəticə etibarilə bütün inteqrasiya iştirakçılarının dövlət dəyişikliyinin ardıcıllığını və xətti olmaması nəzərə alınmalıdır. Bu, bizi xarici hadisələrin baş vermə ardıcıllığına həssas olmayan etmək üçün məntiqi mürəkkəbləşdirməyə məcbur edir. Bizim nümunəmizdə, oyunçu oyundan çıxdığını elan etdikdən sonra oyunda iştirak etməyə məcbur edilir: onun çıxışı haqqında məlumat çatana və bütün iştirakçılar tərəfindən emal olunana qədər digər oyunçular topu ona ötürməyə davam edəcəklər. Bu məntiq oyun qaydalarından irəli gəlmir və seçilmiş arxitektura çərçivəsində kompromis həll yoludur.

Sonra, həllimizin müxtəlif incəlikləri, kompromislər və digər məqamlardan danışaq.

Bütün mesajlar bir növbədə

Bütün inteqrasiya olunmuş proqramlar xarici broker kimi təqdim olunan bir inteqrasiya avtobusu, mesajlar üçün bir BPMQueue və siqnallar (hadisələr) üçün bir BPMTopic mövzusu ilə işləyir. Bütün mesajların bir növbə ilə ötürülməsi özlüyündə kompromisdir. Biznes məntiqi səviyyəsində indi sistem strukturunda dəyişiklik etmədən istədiyiniz qədər yeni tipli mesajlar təqdim edə bilərsiniz. Bu, əhəmiyyətli bir sadələşdirmədir, lakin bizim tipik vəzifələrimiz kontekstində bizə o qədər də əhəmiyyətli görünməyən müəyyən risklər daşıyır.

BPM üslubunun inteqrasiyası

Bununla belə, burada bir incəlik var: hər bir proqram girişdəki növbədən “öz” mesajlarını öz domeninin adı ilə süzür. Həmçinin, siqnalın "əhatə dairəsini" tək bir tətbiqlə məhdudlaşdırmaq lazımdırsa, domen siqnallarda göstərilə bilər. Bu, avtobusun bant genişliyini artırmalıdır, lakin biznes məntiqi indi domen adları ilə işləməlidir: mesajların ünvanlanması üçün məcburi, siqnallar üçün arzu olunandır.

İnteqrasiya avtobusunun etibarlılığının təmin edilməsi

Etibarlılıq bir neçə şeydən ibarətdir:

  • Seçilmiş mesaj brokeri arxitekturanın kritik komponenti və tək uğursuzluq nöqtəsidir: o, kifayət qədər nasazlığa dözümlü olmalıdır. Siz yaxşı dəstək və geniş icma ilə yalnız zamanla sınaqdan keçirilmiş tətbiqlərdən istifadə etməlisiniz;
  • mesaj brokerinin yüksək əlçatanlığını təmin etmək lazımdır, bunun üçün o, inteqrasiya olunmuş proqramlardan fiziki olaraq ayrılmalıdır (tətbiq olunan biznes məntiqi ilə proqramların yüksək əlçatanlığını təmin etmək daha çətin və bahalıdır);
  • broker “ən azı bir dəfə” çatdırılma zəmanəti verməyə borcludur. Bu inteqrasiya avtobusunun etibarlı işləməsi üçün məcburi tələbdir. “Tam bir dəfə” səviyyəli zəmanətlərə ehtiyac yoxdur: iş prosesləri adətən mesajların və ya hadisələrin təkrar gəlməsinə həssas deyildir və bunun vacib olduğu xüsusi tapşırıqlarda daimi istifadə etməkdənsə, biznes məntiqinə əlavə yoxlamalar əlavə etmək daha asandır. daha çox "bahalı" " zəmanətlər;
  • mesajların və siqnalların göndərilməsi biznes proseslərinin vəziyyətində və domen məlumatlarında dəyişiklik ilə ümumi əməliyyatda iştirak etməlidir. Ən yaxşı seçim naxışdan istifadə etmək olardı Transaksiya Giden qutusu, lakin bunun üçün verilənlər bazasında əlavə cədvəl və relay tələb olunacaq. JEE proqramlarında bu, yerli JTA menecerindən istifadə etməklə sadələşdirilə bilər, lakin seçilmiş brokerlə əlaqə rejimdə işləyə bilməlidir. XA;
  • daxil olan mesajların və hadisələrin işləyiciləri biznes prosesinin vəziyyətinin dəyişdirilməsi əməliyyatı ilə də işləməlidirlər: əgər belə bir əməliyyat geri qaytarılırsa, mesajın qəbulu da ləğv edilməlidir;
  • xətalara görə çatdırıla bilməyən mesajlar ayrıca mağazada saxlanmalıdır D.L.Q. (Ölü məktub növbəsi). Bunun üçün biz bu cür mesajları öz yaddaşında saxlayan, onları atributlar üzrə indeksləşdirən (sürətli qruplaşdırma və axtarış üçün) və API-ni baxmaq, təyinat ünvanına yenidən göndərmək və mesajları silmək üçün təqdim edən ayrıca platforma mikroservisi yaratdıq. Sistem administratorları öz veb interfeysi vasitəsilə bu xidmətlə işləyə bilərlər;
  • broker parametrlərində mesajların DLQ-yə daxil olma ehtimalını azaltmaq üçün çatdırılma cəhdlərinin və çatdırılmalar arasında gecikmələrin sayını tənzimləməlisiniz (optimal parametrləri hesablamaq demək olar ki, mümkün deyil, lakin siz empirik olaraq hərəkət edə və onları tənzimləyə bilərsiniz. əməliyyat);
  • DLQ mağazasına davamlı olaraq nəzarət edilməlidir və monitorinq sistemi sistem inzibatçılarını xəbərdar etməlidir ki, çatdırılmamış mesajlar baş verdikdə onlar mümkün qədər tez cavab verə bilsinlər. Bu, uğursuzluğun və ya biznes məntiqi səhvinin “zərər zonasını” azaldacaq;
  • inteqrasiya avtobusu tətbiqlərin müvəqqəti olmamasına qarşı həssas olmalıdır: mövzu abunələri davamlı olmalıdır və tətbiqin domen adı unikal olmalıdır ki, başqası proqram olmadığı müddətdə növbədən onun mesajını emal etməyə çalışmasın.

Biznes məntiqinin mövzu təhlükəsizliyini təmin etmək

Biznes prosesinin eyni nümunəsi eyni anda bir neçə mesaj və hadisə qəbul edə bilər, onların işlənməsi paralel olaraq başlayacaq. Eyni zamanda, bir proqram tərtibatçısı üçün hər şey sadə və mövzu üçün təhlükəsiz olmalıdır.

Prosesin biznes məntiqi bu iş prosesinə təsir edən hər bir xarici hadisəni fərdi şəkildə emal edir. Bu hadisələr ola bilər:

  • biznes prosesi nümunəsinin işə salınması;
  • iş prosesində fəaliyyətlə bağlı istifadəçi hərəkəti;
  • biznes prosesi instansiyasının abunə olduğu mesaj və ya siqnalın qəbulu;
  • biznes prosesi instansiyası tərəfindən təyin edilmiş taymerin bitməsi;
  • API vasitəsilə fəaliyyətə nəzarət (məsələn, prosesin dayandırılması).

Hər bir belə hadisə biznes prosesi nümunəsinin vəziyyətini dəyişə bilər: bəzi fəaliyyətlər bitə bilər, digərləri başlaya bilər, davamlı xassələrin dəyərləri dəyişə bilər. Hər hansı fəaliyyətin bağlanması aşağıdakı fəaliyyətlərdən birinin və ya bir neçəsinin aktivləşməsi ilə nəticələnə bilər. Onlar, öz növbəsində, digər hadisələri gözləməyi dayandıra bilər və ya əlavə məlumatlara ehtiyac duymazlarsa, eyni əməliyyatda tamamlaya bilərlər. Əməliyyatı bağlamazdan əvvəl, biznes prosesinin yeni vəziyyəti verilənlər bazasında saxlanılır, burada növbəti xarici hadisəni gözləyəcəkdir.

Əlaqəli verilənlər bazasında saxlanılan davamlı iş prosesi məlumatları SELECT FOR UPDATE istifadə edərkən çox rahat emal sinxronizasiya nöqtəsidir. Əgər bir tranzaksiya onu dəyişdirmək üçün verilənlər bazasından biznes prosesinin vəziyyətini əldə etməyi bacarıbsa, paralel olaraq heç bir əməliyyat başqa bir dəyişiklik üçün eyni vəziyyəti ala bilməyəcək və birinci əməliyyat başa çatdıqdan sonra ikincisi artıq dəyişdirilmiş vəziyyəti almağa zəmanət verilir.

DBMS tərəfində pessimist kilidlərdən istifadə edərək, biz bütün lazımi tələbləri yerinə yetiririk ACID, həmçinin işləyən nümunələrin sayını artırmaqla tətbiqi biznes məntiqi ilə miqyaslandırmaq qabiliyyətini qoruyun.

Bununla belə, pessimist qıfıllar bizi çıxılmaz vəziyyətlərlə təhdid edir, bu o deməkdir ki, SELECT FOR UPDATE biznes məntiqində bəzi qeyri-adi hallarda dalana dirənməsi halında hələ də bəzi ağlabatan fasilə ilə məhdudlaşdırılmalıdır.

Başqa bir problem iş prosesinin başlanğıcının sinxronizasiyasıdır. Biznes prosesinin nümunəsi olmasa da, verilənlər bazasında da vəziyyət yoxdur, buna görə də təsvir olunan metod işləməyəcək. Müəyyən bir əhatə dairəsində biznes prosesi nümunəsinin unikallığını təmin etmək istəyirsinizsə, o zaman proses sinfi və müvafiq əhatə dairəsi ilə əlaqəli bir növ sinxronizasiya obyektinə ehtiyacınız var. Bu problemi həll etmək üçün biz xarici xidmət vasitəsilə URI formatında bir açarla müəyyən edilmiş ixtiyari resursda kilid götürməyə imkan verən fərqli kilidləmə mexanizmindən istifadə edirik.

Nümunələrimizdə InitialPlayer biznes prosesində bəyannamə var

uniqueConstraint = UniqueConstraints.singleton

Buna görə də, jurnalda müvafiq açarın kilidini götürmək və buraxmaq barədə mesajlar var. Digər biznes prosesləri üçün belə mesajlar yoxdur: unikal Məhdudiyyət təyin edilməyib.

Davamlı vəziyyətlə iş prosesində problemlər

Bəzən davamlı bir vəziyyətə sahib olmaq yalnız kömək etmir, həm də həqiqətən inkişafa mane olur.
Problemlər biznes məntiqinə və/və ya biznes prosesi modelinə dəyişiklik etmək lazım olduqda başlayır. Hər hansı belə dəyişikliyin biznes proseslərinin köhnə vəziyyətinə uyğun olmadığı aşkar edilmir. Verilənlər bazasında çoxlu "canlı" nümunələr varsa, uyğun olmayan dəyişikliklər etmək jBPM-dən istifadə edərkən tez-tez qarşılaşdığımız bir çox problemə səbəb ola bilər.

Dəyişikliyin dərinliyindən asılı olaraq, iki şəkildə hərəkət edə bilərsiniz:

  1. köhnəsinə uyğun olmayan dəyişikliklər etməmək üçün yeni iş prosesi növü yaradın və yeni nümunələrə başlayarkən köhnənin əvəzinə ondan istifadə edin. Köhnə nümunələr "köhnə üsulla" işləməyə davam edəcək;
  2. biznes məntiqini yeniləyərkən iş proseslərinin davamlı vəziyyətini köçürün.

Birinci yol daha sadədir, lakin onun məhdudiyyətləri və mənfi cəhətləri var, məsələn:

  • bir çox biznes prosesi modellərində biznes məntiqinin təkrarlanması, biznes məntiqinin həcminin artması;
  • tez-tez yeni bir iş məntiqinə ani keçid tələb olunur (demək olar ki, həmişə inteqrasiya vəzifələri baxımından);
  • tərtibatçı köhnəlmiş modelləri hansı anda silməyin mümkün olduğunu bilmir.

Praktikada biz hər iki yanaşmadan istifadə edirik, lakin həyatımızı sadələşdirmək üçün bir sıra qərarlar qəbul etmişik:

  • verilənlər bazasında iş prosesinin davamlı vəziyyəti asanlıqla oxuna bilən və asanlıqla işlənən formada saxlanılır: JSON format sətirində. Bu, həm proqram daxilində, həm də xaricdə miqrasiyaları həyata keçirməyə imkan verir. Həddindən artıq hallarda, onu tutacaqlarla da düzəldə bilərsiniz (xüsusilə sazlama zamanı inkişafda faydalıdır);
  • inteqrasiya biznes məntiqi biznes proseslərinin adlarından istifadə etmir ki, istənilən vaxt iştirakçı proseslərdən birinin həyata keçirilməsini yenisi ilə, yeni adla (məsələn, “InitialPlayerV2”) əvəz etmək mümkün olsun. Bağlama mesajların və siqnalların adları vasitəsilə baş verir;
  • proses modelinin versiya nömrəsi var, biz bu modelə uyğun olmayan dəyişikliklər etdikdə onu artırırıq və bu nömrə proses instansiyasının vəziyyəti ilə birlikdə saxlanılır;
  • prosesin davamlı vəziyyəti əvvəlcə bazadan əlverişli obyekt modelinə oxunur ki, modelin versiya nömrəsi dəyişdikdə miqrasiya proseduru işləyə bilər;
  • miqrasiya proseduru biznes məntiqinin yanında yerləşdirilir və verilənlər bazasından bərpa edilərkən biznes prosesinin hər bir nümunəsi üçün "tənbəl" adlanır;
  • bütün proses instansiyalarının vəziyyətini tez və sinxron şəkildə köçürməlisinizsə, daha klassik verilənlər bazası miqrasiya həllərindən istifadə olunur, lakin orada JSON ilə işləməlisiniz.

Biznes prosesləri üçün başqa çərçivəyə ehtiyacım varmı?

Məqalədə təsvir olunan həllər bizə həyatımızı əhəmiyyətli dərəcədə sadələşdirməyə, tətbiqlərin inkişafı səviyyəsində həll olunan məsələlərin çeşidini genişləndirməyə və biznes məntiqini mikroservislərə ayırmaq ideyasını daha cəlbedici etməyə imkan verdi. Bunun üçün çox iş görülüb, biznes prosesləri üçün çox “yüngül” çərçivə yaradılıb, eləcə də geniş tətbiqi tapşırıqlar kontekstində müəyyən edilmiş problemlərin həlli üçün xidmət komponentləri yaradılıb. Bu nəticələri bölüşmək, ümumi komponentlərin inkişafını pulsuz lisenziya altında açıq girişə gətirmək arzusu var. Bu, müəyyən səy və vaxt tələb edəcək. Bu cür həllərə olan tələbatı başa düşmək bizim üçün əlavə stimul ola bilər. Təklif olunan məqalədə çərçivənin özünün imkanlarına çox az diqqət yetirilir, lakin onlardan bəziləri təqdim olunan nümunələrdən görünür. Buna baxmayaraq, çərçivəmizi dərc etsək, ona ayrıca məqalə həsr olunacaq. Bu arada, sualı cavablandıraraq bir az rəy bildirsəniz, minnətdar olarıq:

Sorğuda yalnız qeydiyyatdan keçmiş istifadəçilər iştirak edə bilər. Daxil olunxahiş edirəm.

Biznes prosesləri üçün başqa çərçivəyə ehtiyacım varmı?

  • 18,8%Bəli, mən çoxdandır ki, belə bir şey axtarırdım.

  • 12,5%tətbiqiniz haqqında daha çox öyrənmək maraqlıdır, faydalı ola bilər2

  • 6,2%biz mövcud çərçivələrdən birini istifadə edirik, lakin onu əvəz etməyi düşünürük1

  • 18,8%biz mövcud çərçivələrdən birini istifadə edirik, hər şey uyğun gəlir3

  • 18,8%çərçivəsiz mübarizə3

  • 25,0%öz yazın 4

16 istifadəçi səs verib. 7 istifadəçi bitərəf qalıb.

Mənbə: www.habr.com

Добавить комментарий