BPM загварын интеграци

BPM загварын интеграци

Сайн уу, Хабр!

Манай компани нь ERP ангиллын програм хангамжийн шийдлүүдийг боловсруулах чиглэлээр мэргэшсэн бөгөөд үүний арслангийн хувийг асар их хэмжээний бизнесийн логик, ажлын урсгал a la EDMS бүхий гүйлгээний систем эзэлдэг. Манай бүтээгдэхүүний орчин үеийн хувилбарууд нь JavaEE технологи дээр суурилдаг ч бид микро үйлчилгээг идэвхтэй туршиж байна. Ийм шийдлүүдийн хамгийн асуудалтай хэсгүүдийн нэг бол зэргэлдээх домэйнуудтай холбоотой янз бүрийн дэд системүүдийг нэгтгэх явдал юм. Бидний ашигладаг архитектурын хэв маяг, технологийн стек, хүрээ зэргээс үл хамааран интеграцийн даалгавар бидэнд үргэлж асар их толгойны өвчин болж ирсэн боловч сүүлийн үед эдгээр асуудлыг шийдвэрлэхэд ахиц дэвшил гарч байна.

Таны анхаарлыг татсан нийтлэлд би тусгай зориулалтын газар Кристагийн туршлага, архитектурын судалгааны талаар ярих болно. Мөн бид програм хөгжүүлэгчийн үүднээс интеграцийн асуудлыг шийдэх энгийн шийдлийн жишээг авч үзэх бөгөөд энэхүү энгийн байдлын цаана юу нуугдаж байгааг олж мэдэх болно.

Хариуцлага

Өгүүлэлд дурдсан архитектурын болон техникийн шийдлүүдийг би тодорхой ажлуудын хүрээнд хувийн туршлага дээр үндэслэн санал болгож байна. Эдгээр шийдлүүд нь бүх нийтийнх биш бөгөөд ашиглалтын бусад нөхцөлд оновчтой биш байж магадгүй юм.

BPM үүнд ямар хамаатай вэ?

Энэ асуултад хариулахын тулд бид өөрсдийн шийдлүүдийн хэрэглээний асуудлын онцлогийг бага зэрэг судлах хэрэгтэй. Манай ердийн гүйлгээний систем дэх бизнесийн логикийн гол хэсэг нь хэрэглэгчийн интерфейсээр дамжуулан мэдээллийн санд өгөгдөл оруулах, энэ өгөгдлийг гараар болон автоматаар баталгаажуулах, зарим ажлын урсгалаар дамжуулж, өөр систем / аналитик мэдээллийн сан / архивт нийтлэх, тайлан гаргах явдал юм. Тиймээс үйлчлүүлэгчдэд зориулсан системийн гол үүрэг бол тэдний дотоод бизнесийн үйл явцыг автоматжуулах явдал юм.

Тохиромжтой болгох үүднээс бид харилцаа холбоонд "баримт бичиг" гэсэн нэр томъёог нийтлэг түлхүүрээр нэгтгэсэн өгөгдлийн багцын зарим хийсвэрлэл болгон ашигладаг бөгөөд үүнд тодорхой ажлын урсгалыг "хавсгаж" байна.
Гэхдээ интеграцийн логикийн талаар юу хэлэх вэ? Эцсийн эцэст, нэгтгэх ажлыг хэрэглэгчийн хүсэлтээр биш, харин огт өөр хүчин зүйлийн нөлөөн дор хэсэг болгон "хөрөөддөг" системийн архитектураар бий болгодог.

  • Конвейн хуулийн нөлөөн дор;
  • бусад бүтээгдэхүүнд зориулж өмнө нь боловсруулсан дэд системийг дахин ашигласны үр дүнд;
  • үйл ажиллагааны бус шаардлагад үндэслэн архитекторын шийдвэрээр.

Бизнесийн логикийг интеграцийн олдворуудаар бохирдуулахгүйн тулд интеграцийн логикийг үндсэн ажлын урсгалын бизнесийн логикоос салгах, програм хөгжүүлэгчийг системийн архитектурын ландшафтын онцлог шинж чанарыг судлахаас аврах асар их сорилт байдаг. Энэ арга нь хэд хэдэн давуу талтай боловч практик нь түүний үр ашиггүй байдлыг харуулж байна:

  • үндсэн ажлын урсгалыг хэрэгжүүлэхэд хязгаарлагдмал өргөтгөлийн цэгүүдээс шалтгаалан интеграцийн асуудлыг шийдвэрлэх нь ихэвчлэн синхрон дуудлагын хэлбэрээр хамгийн энгийн сонголтууд руу шилждэг (доорх синхрон интеграцийн дутагдлын талаар илүү дэлгэрэнгүй);
  • интеграцийн олдворууд нь өөр дэд системээс санал хүсэлт гаргах шаардлагатай үед бизнесийн үндсэн логикт нэвтэрсэн хэвээр байна;
  • програм хөгжүүлэгч нь интеграцийг үл тоомсорлож, ажлын урсгалыг өөрчилснөөр амархан эвдэж болно;
  • Систем нь хэрэглэгчийн үүднээс нэг бүхэл байхаа больж, дэд системүүдийн хоорондох "давхарга" нь мэдэгдэхүйц болж, нэг дэд системээс нөгөөд өгөгдөл дамжуулах ажлыг эхлүүлдэг хэрэглэгчийн илүүдэл үйлдлүүд гарч ирдэг.

Өөр нэг арга бол интеграцийн харилцан үйлчлэлийг бизнесийн үндсэн логик, ажлын урсгалын салшгүй хэсэг гэж үзэх явдал юм. Хэрэглээний хөгжүүлэгчдийн ур чадварын шаардлагуудыг огцом өсгөхгүйн тулд шинэ интеграцийн харилцан үйлчлэлийг бий болгох нь шийдлийг сонгоход хамгийн бага сонголттой хялбар бөгөөд байгалийн жамаар хийгдэх ёстой. Энэ нь харагдахаас хамаагүй хэцүү юм: хэрэгсэл нь хэрэглэгчийг ашиглахад шаардлагатай олон төрлийн сонголтоор хангахуйц хүчирхэг байх ёстой бөгөөд нэгэн зэрэг хөл рүү нь буудахыг зөвшөөрөхгүй байх ёстой. Интеграцчлалын даалгаврын хүрээнд инженерийн хариулах ёстой олон асуулт байдаг ч програм хөгжүүлэгч өдөр тутмын ажилдаа бодох ёсгүй: гүйлгээний хил хязгаар, тууштай байдал, атом, аюулгүй байдал, масштаб, ачаалал, нөөцийн хуваарилалт, чиглүүлэлт, чиглүүлэлт, дэлгэрүүлэх, сэлгэх контекст гэх мэт. Ийм бүх асуултын хариулт аль хэдийн нуугдаж байгаа маш энгийн шийдвэрийн загваруудыг програм хөгжүүлэгчдэд санал болгох шаардлагатай байна. Эдгээр загварууд нь хангалттай найдвартай байх ёстой: бизнесийн логик нь маш олон удаа өөрчлөгддөг бөгөөд энэ нь алдаа гаргах эрсдэлийг нэмэгдүүлдэг тул алдааны өртөг нь нэлээд бага түвшинд байх ёстой.

Гэсэн хэдий ч BPM үүнтэй ямар холбоотой вэ? Ажлын урсгалыг хэрэгжүүлэх олон сонголт байдаг ...
Үнэн хэрэгтээ бизнесийн үйл явцын өөр нэг хэрэгжилт нь манай шийдлүүдэд маш их алдартай байдаг - төрийн шилжилтийн диаграммыг тунхаглах тохиргоо, бизнес логиктой зохицуулагчдыг шилжилттэй холбох замаар. Үүний зэрэгцээ бизнесийн үйл явц дахь "баримт бичиг" -ийн одоогийн байр суурийг тодорхойлдог муж нь "баримт бичиг" -ийн шинж чанар юм.

BPM загварын интеграци
Төслийн эхэнд үйл явц иймэрхүү харагдаж байна

Ийм хэрэгжилтийн түгээмэл байдал нь шугаман бизнесийн үйл явцыг бий болгох харьцангуй энгийн, хурдтай холбоотой юм. Гэсэн хэдий ч програм хангамжийн системүүд илүү төвөгтэй болохын хэрээр бизнесийн үйл явцын автоматжуулсан хэсэг нь өсч, илүү төвөгтэй болдог. Салбар бүрийг зэрэгцүүлэн гүйцэтгэхийн тулд задлах, үйл явцын хэсгүүдийг дахин ашиглах, салаалах процессууд шаардлагатай байдаг. Ийм нөхцөлд хэрэгсэл нь тохиромжгүй болж, төлөвийн шилжилтийн диаграм нь мэдээллийн агуулгаа алддаг (интеграцийн харилцан үйлчлэл нь диаграммд огт тусгаагүй болно).

BPM загварын интеграци
Шаардлагуудыг тодорхой болгосны дараа хэд хэдэн давталт хийсний дараа процесс иймэрхүү харагдаж байна

Энэ байдлаас гарах арга зам бол хөдөлгүүрийг нэгтгэх явдал байв jBPM хамгийн төвөгтэй бизнесийн үйл явц бүхий зарим бүтээгдэхүүн болгон . Богино хугацаанд энэхүү шийдэл нь амжилтанд хүрсэн: тэмдэглэгээнд нэлээд мэдээлэл сайтай, хамгийн сүүлийн үеийн диаграммыг хадгалахын зэрэгцээ бизнесийн нарийн төвөгтэй үйл явцыг хэрэгжүүлэх боломжтой болсон. BPMN2.

BPM загварын интеграци
Бизнесийн нарийн төвөгтэй үйл явцын жижиг хэсэг

Удаан хугацааны туршид шийдэл нь хүлээлтийг хангаагүй: харааны хэрэгслээр дамжуулан бизнесийн үйл явцыг бий болгох өндөр хөдөлмөрийн эрчимжилт нь хүлээн зөвшөөрөгдсөн бүтээмжийн үзүүлэлтэд хүрэх боломжийг олгодоггүй бөгөөд уг хэрэгсэл өөрөө хөгжүүлэгчдийн дунд хамгийн дургүй зүйлсийн нэг болжээ. Хөдөлгүүрийн дотоод бүтцийн талаар гомдол ирсэн бөгөөд энэ нь олон "нөхөөс", "таяг" гарч ирэхэд хүргэсэн.

JBPM-ийг ашиглах гол эерэг тал нь бизнесийн үйл явцын хувьд өөрийн гэсэн байнгын төлөвтэй байхын ашиг тус, хор хөнөөлийг ойлгох явдал байв. Бид дохио, мессежээр дамжуулан асинхрон харилцан үйлчлэлийг ашиглан өөр өөр програмуудын хооронд нарийн төвөгтэй интеграцийн протоколуудыг хэрэгжүүлэх процессын аргыг ашиглах боломжийг олж харсан. Тогтвортой улс байх нь үүнд чухал үүрэг гүйцэтгэдэг.

Дээр дурдсан зүйлс дээр үндэслэн бид дараахь дүгнэлтийг хийж болно. BPM хэв маягийн үйл явцын арга нь улам бүр төвөгтэй бизнесийн үйл явцыг автоматжуулах өргөн хүрээний даалгавруудыг шийдвэрлэх, интеграцийн үйл ажиллагааг эдгээр процессуудад нийцүүлэн тохируулах, хэрэгжсэн үйл явцыг зохих тэмдэглэгээгээр нүдээр харуулах чадварыг хадгалах боломжийг олгодог.

Интеграцийн загвар болох синхрон дуудлагын сул тал

Синхрон интеграци нь хамгийн энгийн хаах дуудлага гэж ойлгогддог. Нэг дэд систем нь серверийн үүрэг гүйцэтгэдэг бөгөөд API-г хүссэн аргаар ил гаргадаг. Өөр нэг дэд систем нь үйлчлүүлэгчийн үүрэг гүйцэтгэдэг бөгөөд зөв цагт, үр дүнг хүлээсэн дуудлага хийдэг. Системийн архитектураас хамааран үйлчлүүлэгч болон серверийн талыг нэг програм, процесст эсвэл өөр өөр хэлбэрээр байрлуулж болно. Хоёр дахь тохиолдолд та RPC-ийн зарим хэрэгжилтийг ашиглаж, параметрүүд болон дуудлагын үр дүнг нэгтгэх хэрэгтэй.

BPM загварын интеграци

Ийм интеграцийн загвар нь нэлээд олон тооны сул талуудтай боловч энгийн байдлаас шалтгаалан практикт маш өргөн хэрэглэгддэг. Хэрэгжилтийн хурд нь сэтгэлийг хөдөлгөж, шийдлийг техникийн өрөнд бичиж, эцсийн хугацаа нь "шатаах" нөхцөлд дахин дахин хэрэглэхэд хүргэдэг. Гэхдээ туршлагагүй хөгжүүлэгчид сөрөг үр дагаврыг ухамсарлахгүйгээр үүнийг ухамсаргүйгээр ашигладаг.

Дэд системүүдийн холболтын хамгийн тод өсөлтөөс гадна гүйлгээг "тархах", "сунгах" зэрэгтэй холбоотой асуудал бага байдаг. Үнэн хэрэгтээ, хэрэв бизнесийн логик ямар нэгэн өөрчлөлт хийвэл гүйлгээ нь зайлшгүй шаардлагатай бөгөөд гүйлгээ нь эргээд эдгээр өөрчлөлтөд өртсөн хэрэглээний тодорхой нөөцийг түгжих болно. Өөрөөр хэлбэл, нэг дэд систем нөгөөгөөсөө хариу хүлээх хүртэл гүйлгээг хийж, түгжээг гаргах боломжгүй болно. Энэ нь янз бүрийн үр нөлөөний эрсдлийг ихээхэн нэмэгдүүлдэг:

  • системийн хариу үйлдэл алдагдаж, хэрэглэгчид хүсэлтийн хариултыг удаан хүлээдэг;
  • сервер хэт их урсгалын улмаас хэрэглэгчийн хүсэлтэд хариу өгөхөө больдог: ихэнх хэлхээ нь гүйлгээний эзэлдэг нөөцийн цоож дээр "зогсдог";
  • түгжрэл гарч эхэлдэг: тэдгээрийн үүсэх магадлал нь гүйлгээний үргэлжлэх хугацаа, бизнесийн логикийн хэмжээ, гүйлгээнд хамаарах түгжээ зэргээс ихээхэн хамаардаг;
  • гүйлгээний хугацаа дуусахад алдаа гарсан;
  • Хэрэв даалгавар нь их хэмжээний өгөгдлийг боловсруулж, өөрчлөх шаардлагатай бол сервер OutOfMemory дээр "унадаг" бөгөөд синхрон интеграцчлал байгаа нь боловсруулалтыг "хөнгөн" гүйлгээ болгон хуваахад маш хэцүү болгодог.

Архитектурын үүднээс авч үзвэл, нэгтгэх явцад дуудлагыг хаах нь бие даасан дэд системүүдийн чанарын хяналт алдагдахад хүргэдэг: нэг дэд системийн чанарын зорилтыг нөгөө дэд системийн чанарын зорилтоос тусгаарлах боломжгүй юм. Хэрэв дэд системүүдийг өөр өөр багууд хөгжүүлдэг бол энэ нь том асуудал юм.

Хэрэв нэгтгэж буй дэд системүүд өөр өөр хэрэглээнд байгаа бөгөөд хоёр талдаа синхрон өөрчлөлт хийх шаардлагатай бол бүх зүйл илүү сонирхолтой болно. Эдгээр өөрчлөлтийг хэрхэн гүйлгээ хийх вэ?

Хэрэв тусдаа гүйлгээнд өөрчлөлт оруулсан бол онцгой тохиолдлуудыг шийдвэрлэх, нөхөн олговор олгох шаардлагатай бөгөөд энэ нь синхрон интеграцийн гол давуу тал болох энгийн байдлыг бүрэн арилгах болно.

Тархсан гүйлгээ нь бас санаанд орж ирдэг, гэхдээ бид тэдгээрийг шийдэлдээ ашигладаггүй: найдвартай байдлыг хангахад хэцүү байдаг.

Гүйлгээний асуудлын шийдэл болох "Сага"

Микро үйлчилгээний нэр хүнд өсөхийн хэрээр эрэлт хэрэгцээ нэмэгдэж байна Сага загвар.

Энэхүү загвар нь урт хугацааны гүйлгээний дээрх асуудлуудыг төгс шийдэж, системийн төлөв байдлыг бизнесийн логик талаас нь удирдах боломжийг өргөжүүлдэг: амжилтгүй гүйлгээний дараа нөхөн олговор нь системийг анхны байдалд нь буцааж өгөхгүй, харин өөр хувилбараар хангаж өгдөг. өгөгдөл боловсруулах зам. Энэ нь мөн процессыг "сайн" төгсгөлд хүргэхийг оролдох үед амжилттай дууссан өгөгдөл боловсруулах алхмуудыг давтахгүй байх боломжийг олгодог.

Сонирхолтой нь цул системд энэ загвар нь сул холболттой дэд системүүдийг нэгтгэх үед хамааралтай бөгөөд урт хугацааны гүйлгээ, холбогдох нөөцийн түгжээнээс үүдэлтэй сөрөг нөлөөлөл байдаг.

BPM хэв маягийн бизнесийн үйл явцын хувьд Сагасыг хэрэгжүүлэхэд маш хялбар байдаг: Сагагийн бие даасан алхмуудыг бизнесийн үйл явцын үйл ажиллагаа болгон тохируулж болох бөгөөд бизнесийн үйл явцын тогтвортой байдал нь дараахь зүйлийг тодорхойлдог. бусад зүйлс, Сагадын дотоод байдал. Өөрөөр хэлбэл, зохицуулалтын нэмэлт механизм хэрэггүй. Танд хэрэгтэй зүйл бол "дор хаяж нэг удаа" баталгаатай мессеж брокер юм.

Гэхдээ энэ шийдэл нь бас өөрийн гэсэн "үнэтэй":

  • бизнесийн логик илүү төвөгтэй болж байна: та нөхөн олговрыг боловсруулах хэрэгтэй;
  • цул системд онцгой мэдрэмтгий байж болох бүрэн тууштай байдлаас татгалзах шаардлагатай болно;
  • архитектур нь арай илүү төвөгтэй болж, мессеж зуучлагчийн нэмэлт хэрэгцээ бий болно;
  • нэмэлт хяналт, удирдлагын хэрэгсэл шаардлагатай болно (хэдийгээр ерөнхийдөө энэ нь бүр сайн: системийн үйлчилгээний чанар нэмэгдэх болно).

Цул системүүдийн хувьд "Sags" ашиглах үндэслэл нь тийм ч тодорхой биш юм. Төслийн эхэнд аль хэдийн брокер байгаа бөгөөд бүрэн тууштай байдлыг золиосолсон микро үйлчилгээ болон бусад SOA-ийн хувьд энэ загварыг ашиглахын давуу тал нь сул талуудаас хамаагүй илүү байж болно, ялангуяа тохиромжтой API байгаа бол. бизнесийн логик түвшин.

Бичил үйлчилгээнд бизнесийн логикийг багтаах

Бид бичил үйлчилгээтэй туршилт хийж эхлэх үед боломжийн асуулт гарч ирэв: домэйн мэдээллийн тогтвортой байдлыг хангадаг үйлчилгээтэй холбоотой домэйны бизнесийн логикийг хаана байрлуулах вэ?

Төрөл бүрийн BPMS-ийн архитектурыг харахад бизнесийн логикийг тууштай байдлаас салгах нь үндэслэлтэй мэт санагдаж магадгүй юм: домэйн бизнесийн логикийг хэрэгжүүлэх орчин, савыг бүрдүүлдэг платформ болон домэйноос хамааралгүй микро үйлчилгээний давхаргыг бий болгож, домэйн өгөгдлийн тогтвортой байдлыг тусад нь зохион байгуул. маш энгийн бөгөөд хөнгөн микро үйлчилгээний давхарга. Энэ тохиолдолд бизнесийн үйл явц нь тогтвортой давхаргын үйлчилгээг зохион байгуулдаг.

BPM загварын интеграци

Энэ арга нь маш том давуу талтай: та платформын үйл ажиллагааг хүссэн хэмжээгээрээ нэмэгдүүлэх боломжтой бөгөөд зөвхөн платформын микро үйлчилгээний холбогдох давхарга үүнээс "таргалах" болно. Аливаа домэйны бизнесийн процессууд шинэчлэгдсэн даруйд платформын шинэ функцийг ашиглах боломжийг шууд олж авдаг.

Илүү нарийвчилсан судалгаагаар энэ аргын томоохон дутагдал илэрсэн:

  • Олон домэйны бизнесийн логикийг нэгэн зэрэг гүйцэтгэдэг платформ үйлчилгээ нь нэг л бүтэлгүйтлийн цэг болох том эрсдэлийг дагуулдаг. Бизнесийн логикийг байнга өөрчлөх нь системийн хэмжээнд алдаа гарахад хүргэдэг алдааны эрсдлийг нэмэгдүүлдэг;
  • Гүйцэтгэлийн асуудлууд: Бизнесийн логик нь нарийн бөгөөд удаан интерфейсээр дамжуулан өгөгдөлтэйгээ ажилладаг.
    • өгөгдлийг дахин нэгтгэж, сүлжээний стекээр дамжуулан шахах болно;
    • домэйн үйлчилгээ нь үйлчилгээний гадаад API-ийн түвшинд асуулгын параметрүүдийг тодорхойлох чадвар хангалтгүйн улмаас бизнесийн логик боловсруулахад шаардагдахаас илүү их өгөгдлийг буцаана;
    • Бизнесийн логикийн хэд хэдэн бие даасан хэсгүүд нь ижил өгөгдлийг боловсруулахын тулд дахин дахин хүсэлт гаргах боломжтой (та өгөгдлийг кэш болгодог сессийн шошгуудыг нэмж оруулснаар энэ асуудлыг багасгаж болно, гэхдээ энэ нь архитектурыг улам хүндрүүлж, мэдээллийн шинэлэг байдал, кэшийг хүчингүй болгох асуудал үүсгэдэг);
  • гүйлгээний асуудал:
    • платформын үйлчилгээнд хадгалагдаж буй байнгын төлөвтэй бизнесийн үйл явц нь домайн өгөгдөлтэй нийцэхгүй байгаа бөгөөд энэ асуудлыг шийдвэрлэх хялбар арга байхгүй;
    • Домэйн мэдээллийн түгжээг гүйлгээнээс гаргах: хэрэв домэйн бизнесийн логик өөрчлөлт хийх шаардлагатай бол эхлээд бодит өгөгдлийн зөв эсэхийг шалгасны дараа боловсруулсан өгөгдөлд өрсөлдөхүйц өөрчлөлт хийх боломжийг хасах шаардлагатай. Мэдээллийг гаднаас хаах нь асуудлыг шийдвэрлэхэд тусална, гэхдээ ийм шийдэл нь нэмэлт эрсдэл дагуулж, системийн ерөнхий найдвартай байдлыг бууруулдаг;
  • Шинэчлэх үед гарах нэмэлт хүндрэлүүд: зарим тохиолдолд та тууштай үйлчилгээ болон бизнесийн логикийг синхрон эсвэл хатуу дарааллаар шинэчлэх хэрэгтэй.

Эцэст нь би үндсэн зүйл рүү буцах хэрэгтэй болсон: домэйн өгөгдөл болон домэйн бизнесийн логикийг нэг микро үйлчилгээнд багтаах. Энэхүү арга нь микро үйлчилгээг системийн салшгүй бүрэлдэхүүн хэсэг гэж ойлгоход хялбар болгож, дээрх асуудлуудыг үүсгэдэггүй. Энэ нь бас үнэ төлбөргүй биш юм:

  • API стандартчилал нь бизнесийн логик (ялангуяа бизнесийн үйл явцын нэг хэсэг болгон хэрэглэгчийн үйл ажиллагааг хангах) болон API платформын үйлчилгээтэй харилцахад шаардлагатай; API-ийн өөрчлөлтөд илүү анхааралтай хандах, урагш болон хойшоо нийцтэй байх шаардлагатай;
  • Ийм бичил үйлчилгээ бүрийн нэг хэсэг болох бизнесийн логикийн үйл ажиллагааг хангахын тулд нэмэлт ажиллах цагийн номын сангуудыг нэмэх шаардлагатай бөгөөд энэ нь эдгээр номын санд тавигдах шинэ шаардлагыг бий болгож байна: хөнгөн байдал, хамгийн бага шилжилтийн хамаарал;
  • Бизнесийн логик хөгжүүлэгчид номын сангийн хувилбаруудыг хянаж байх хэрэгтэй: хэрэв микро үйлчилгээ удаан хугацаанд шийдэгдээгүй бол энэ нь номын сангийн хуучирсан хувилбарыг агуулсан байх магадлалтай. Энэ нь шинэ функц нэмэхэд гэнэтийн саад тотгор учруулж болзошгүй бөгөөд хэрэв хувилбаруудын хооронд үл нийцэх өөрчлөлт гарсан бол ийм үйлчилгээний хуучин бизнесийн логикийг номын сангийн шинэ хувилбарууд руу шилжүүлэхийг шаардаж болно.

BPM загварын интеграци

Ийм архитектурт платформын үйлчилгээний давхарга байдаг боловч энэ давхарга нь домэйн бизнесийн логикийг гүйцэтгэх сав байхаа больсон, зөвхөн түүний орчинг бүрдүүлж, туслах "платформ" функцийг хангадаг. Ийм давхарга нь зөвхөн домэйн микро үйлчилгээний хөнгөн байдлыг хадгалахаас гадна менежментийг төвлөрүүлэхэд хэрэгтэй.

Жишээлбэл, бизнесийн үйл явц дахь хэрэглэгчийн үйл ажиллагаа нь даалгавар үүсгэдэг. Гэсэн хэдий ч, даалгавартай ажиллахдаа хэрэглэгч ерөнхий жагсаалтад байгаа бүх домэйны даалгавруудыг харах ёстой бөгөөд энэ нь домэйны бизнесийн логикоос ангид, тохирох даалгаврын бүртгэлийн платформын үйлчилгээ байх ёстой гэсэн үг юм. Энэ хүрээнд бизнесийн логикийг багтаах нь нэлээд асуудалтай бөгөөд энэ нь энэхүү архитектурын өөр нэг буулт юм.

Хэрэглээний хөгжүүлэгчийн нүдээр бизнесийн үйл явцыг нэгтгэх

Дээр дурьдсанчлан, програм хөгжүүлэгч нь хөгжлийн сайн бүтээмжид найдаж болохын тулд хэд хэдэн програмын харилцан үйлчлэлийн хэрэгжилтийн техникийн болон инженерийн онцлогоос салсан байх ёстой.

Нийтлэлд зориулж тусгайлан зохион бүтээсэн нэлээд төвөгтэй интеграцийн асуудлыг шийдэхийг хичээцгээе. Энэ нь тус бүр нь "app1", "app2", "app3" гэсэн домэйн нэрийг тодорхойлдог гурван програмыг хамарсан "тоглоомын" даалгавар байх болно.

Аппликейшн бүрийн дотор интеграцийн автобусаар "бөмбөг тоглож" эхэлдэг бизнесийн процессууд нээгддэг. "Бөмбөлөг" нэртэй мессежүүд бөмбөгний үүрэг гүйцэтгэнэ.

Тоглоомын дүрэм:

  • Эхний тоглогч бол санаачлагч юм. Тэрээр бусад тоглогчдыг тоглоомонд урьж, тоглоомыг эхлүүлж, хүссэн үедээ дуусгаж болно;
  • бусад тоглогчид тоглолтонд оролцож байгаагаа зарлаж, бие биетэйгээ болон эхний тоглогчтой "танилцах";
  • Бөмбөгийг хүлээн авсны дараа тоглогч өөр оролцогч тоглогчийг сонгож, бөмбөгийг түүнд дамжуулна. Нийт дамжуулалтын тоог тоолно;
  • тоглогч бүр "эрч хүч"-тэй байдаг бөгөөд энэ нь тухайн тоглогчийн бөмбөг дамжуулах бүрт буурдаг. Эрчим хүч дуусахад тоглогч зодог тайлж байгаагаа зарлаж, тоглоомоос хасагдана;
  • хэрэв тоглогч ганцаараа үлдсэн бол тэр даруй явахаа мэдэгдэнэ;
  • Бүх тоглогчид хасагдах үед эхний тоглогч тоглоом дууссаныг мэдэгдэнэ. Хэрэв тэр тоглоомыг эрт орхисон бол дуусгахын тулд тоглоомыг дагаж мөрдөх шаардлагатай.

Энэ асуудлыг шийдэхийн тулд би DSL-ийг бизнесийн процессуудад ашиглах болно, энэ нь танд Котлин дахь логикийг хамгийн багадаа багтаамжтай авсаархан дүрслэх боломжийг олгодог.

Апп1 програм дээр эхний тоглогчийн (тэр бас тоглоомыг санаачлагч) бизнесийн үйл явц ажиллах болно.

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

Дээрх код нь бизнесийн логикийг хэрэгжүүлэхээс гадна бизнесийн үйл явцын объект загварыг диаграм хэлбэрээр дүрслэн харуулах боломжтой. Бид хараахан хараахан дүрслэгчийг хэрэгжүүлээгүй байгаа тул зурахад хэсэг хугацаа зарцуулах шаардлагатай болсон (энд би диаграмын дээрх кодтой нийцтэй байдлыг сайжруулахын тулд хаалга ашиглахтай холбоотой BPMN тэмдэглэгээг бага зэрэг хялбаршуулсан):

BPM загварын интеграци

app2 нь өөр тоглогчийн бизнесийн үйл явцыг агуулна:

анги 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}")
}

Диаграм:

BPM загварын интеграци

Апп3 програм дээр бид тоглогчийг арай өөр зан авиртай болгох болно: дараагийн тоглогчийг санамсаргүй байдлаар сонгохын оронд тэрээр тойргийн алгоритмын дагуу ажиллах болно.

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

Үгүй бол тоглогчийн зан байдал өмнөхөөсөө ялгаатай биш тул диаграмм өөрчлөгдөхгүй.

Одоо бид бүгдийг ажиллуулах тест хэрэгтэй байна. Өгүүллийг бөглөрүүлэхгүйн тулд би зөвхөн тестийн кодыг өгөх болно (үнэндээ би бусад бизнесийн үйл явцыг нэгтгэхийг шалгахын тулд өмнө нь бий болгосон туршилтын орчныг ашигласан):

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

Туршилтыг ажиллуулж, бүртгэлийг харна уу:

консолийн гаралт

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

Энэ бүхнээс хэд хэдэн чухал дүгнэлт хийж болно:

  • хэрэв шаардлагатай хэрэгслүүд байгаа бол програм хөгжүүлэгчид бизнесийн логикоос салалгүйгээр програмуудын хооронд интеграцийн харилцан үйлчлэлийг бий болгож чадна;
  • Инженерийн ур чадвар шаардсан интеграцийн даалгаврын нарийн төвөгтэй байдал (нарийн төвөгтэй байдал) нь уг бүтцийн архитектурт анхлан тусгагдсан бол хүрээ дотор нуугдаж болно. Даалгаврын хүндрэлийг (хэцүү байдал) нуух боломжгүй тул код дахь хэцүү даалгаврын шийдэл нь зохих ёсоор харагдах болно;
  • Интеграцийн логикийг боловсруулахдаа интеграцийн бүх оролцогчдын төлөв байдлын өөрчлөлтийн тууштай байдал, шугаманчлалгүй байдлыг харгалзан үзэх шаардлагатай. Энэ нь биднийг гадны үйл явдлын дараалалд мэдрэмжгүй болгохын тулд логикийг төвөгтэй болгоход хүргэдэг. Бидний жишээн дээр тоглогч тоглоомоос гарснаа мэдэгдсэний дараа тоглоомд оролцохыг албаддаг: бусад тоглогчид түүний гарах тухай мэдээлэл хүрч, бүх оролцогчид боловсруулагдах хүртэл бөмбөгийг түүнд дамжуулсаар байх болно. Энэ логик нь тоглоомын дүрмээс үл хамаарах бөгөөд сонгосон архитектурын хүрээнд буулт хийх шийдэл юм.

Дараа нь бидний шийдэл, буулт болон бусад зүйлсийн янз бүрийн нарийн ширийн зүйлсийн талаар ярилцъя.

Бүх мессежүүд нэг дараалалд байна

Бүх нэгдсэн програмууд нь гадаад брокер, мессежийн нэг BPMQueue, дохионы (үйл явдлын) нэг BPMTopic сэдвээр танилцуулагдсан нэг интеграцийн автобустай ажилладаг. Бүх мессежийг нэг дарааллаар дамжуулах нь өөрөө буулт хийх явдал юм. Бизнесийн логик түвшинд та системийн бүтцэд өөрчлөлт оруулахгүйгээр хүссэнээрээ олон шинэ төрлийн мессежийг нэвтрүүлэх боломжтой. Энэ нь ихээхэн хялбаршуулсан боловч бидний ердийн даалгаврын хүрээнд бидэнд тийм ч чухал биш мэт санагдсан тодорхой эрсдэлүүдийг дагуулдаг.

BPM загварын интеграци

Гэсэн хэдий ч энд нэг нарийн зүйл бий: програм бүр хаалган дээрх дарааллаас "өөрийн" мессежийг өөрийн домэйны нэрээр шүүдэг. Мөн дохионы "хамрах хүрээг" нэг програмаар хязгаарлах шаардлагатай бол домэйныг дохионд зааж өгч болно. Энэ нь автобусны зурвасын өргөнийг нэмэгдүүлэх ёстой, гэхдээ бизнесийн логик нь одоо домэйн нэртэй ажиллах ёстой: мессеж хаяглахад заавал байх ёстой, дохионы хувьд зүйтэй.

Интеграцийн автобусны найдвартай байдлыг хангах

Найдвартай байдал нь хэд хэдэн зүйлээс бүрдэнэ.

  • Сонгосон мессежийн брокер нь архитектурын чухал бүрэлдэхүүн хэсэг бөгөөд алдааны нэг цэг юм: энэ нь хангалттай гэмтэлтэй байх ёстой. Та зөвхөн сайн дэмжлэг, өргөн хүрээний нийгэмлэг бүхий цаг хугацаагаар туршсан хэрэгжилтийг ашиглах ёстой;
  • мессежийн брокерын өндөр хүртээмжийг хангах шаардлагатай бөгөөд үүний тулд үүнийг нэгдсэн програмуудаас биечлэн тусгаарлах шаардлагатай (бизнесийн логик бүхий програмуудын өндөр хүртээмжийг хангах нь илүү хэцүү бөгөөд үнэтэй байдаг);
  • брокер нь "дор хаяж нэг удаа" хүргэх баталгаа өгөх үүрэгтэй. Энэ нь интеграцийн автобусыг найдвартай ажиллуулах зайлшгүй шаардлага юм. "Яг нэг удаа" түвшний баталгаа шаардлагагүй: бизнесийн үйл явц нь ихэвчлэн мессеж, үйл явдал давтагдахад мэдрэмтгий байдаггүй бөгөөд энэ нь чухал байдаг тусгай ажлуудад байнга ашиглахаас илүү бизнесийн логикт нэмэлт шалгалт оруулах нь илүү хялбар байдаг. харин "үнэтэй" " баталгаа;
  • мессеж, дохиог илгээх нь бизнесийн үйл явц, домэйны өгөгдөлд өөрчлөлт оруулах нийтлэг гүйлгээнд оролцох ёстой. Хамгийн тохиромжтой сонголт бол хэв маягийг ашиглах явдал юм Гүйлгээний Outbox, гэхдээ энэ нь мэдээллийн санд нэмэлт хүснэгт болон реле шаардах болно. JEE програмуудад үүнийг локал JTA менежер ашиглан хялбарчилж болох боловч сонгосон брокерын холболт нь горимд ажиллах боломжтой байх ёстой. XA;
  • Ирж буй мессеж, үйл явдлын зохицуулагчид бизнесийн үйл явцын төлөв байдлыг өөрчлөх гүйлгээтэй ажиллах ёстой: хэрэв ийм гүйлгээг буцаах юм бол мессежийн хүлээн авалтыг мөн цуцлах ёстой;
  • Алдаанаас болж хүргэх боломжгүй мессежүүдийг тусдаа дэлгүүрт хадгалах ёстой D.L.Q. (Үхсэн захидлын дараалал). Үүнийг хийхийн тулд бид ийм мессежийг хадгалах сандаа хадгалж, шинж чанаруудаар нь индексжүүлж (хурдан бүлэглэх, хайх), API-г үзэх, очих хаяг руу дахин илгээх, мессежийг устгахад зориулагдсан тусдаа платформ микро үйлчилгээг үүсгэсэн. Системийн администраторууд вэб интерфэйсээр дамжуулан энэ үйлчилгээтэй ажиллах боломжтой;
  • брокерын тохиргоонд та DLQ-д мессеж орох магадлалыг бууруулахын тулд хүргэх дахин оролдлого, хүргэлтийн хоорондох саатлын тоог тохируулах хэрэгтэй (оновчтой параметрүүдийг тооцоолох нь бараг боломжгүй, гэхдээ та эмпирик байдлаар ажиллаж, тэдгээрийг тохируулах боломжтой. үйл ажиллагаа);
  • DLQ дэлгүүрийг тасралтгүй хянаж байх ёстой бөгөөд хяналтын систем нь системийн администраторуудад мэдэгдэх ёстой бөгөөд ингэснээр тэд хүргэгдээгүй мессеж гарсан тохиолдолд аль болох хурдан хариу өгөх боломжтой болно. Энэ нь бүтэлгүйтэл эсвэл бизнесийн логик алдааны "хохирлын бүсийг" багасгах болно;
  • Интеграцийн автобус нь програмууд түр зуур байхгүй байх ёстой: сэдвийн захиалга нь удаан эдэлгээтэй байх ёстой бөгөөд програмын домэйн нэр нь өвөрмөц байх ёстой бөгөөд ингэснээр өөр хэн нэгэн програм байхгүй үед дарааллаас мессежийг боловсруулахыг оролдохгүй байх ёстой.

Бизнесийн логикийн урсгалын аюулгүй байдлыг хангах

Бизнесийн үйл явцын ижил тохиолдол нь хэд хэдэн мессеж, үйл явдлыг нэгэн зэрэг хүлээн авах боломжтой бөгөөд боловсруулалт нь зэрэгцээ эхлэх болно. Үүний зэрэгцээ, програм хөгжүүлэгчийн хувьд бүх зүйл энгийн бөгөөд урсгалтай байх ёстой.

Үйл явцын бизнесийн логик нь энэхүү бизнесийн үйл явцад нөлөөлж буй гадаад үйл явдал бүрийг тус тусад нь боловсруулдаг. Эдгээр үйл явдал байж болно:

  • бизнесийн үйл явцын жишээг эхлүүлэх;
  • бизнесийн үйл явц дахь үйл ажиллагаатай холбоотой хэрэглэгчийн үйлдэл;
  • бизнесийн үйл явцын жишээг захиалсан мессеж эсвэл дохиог хүлээн авах;
  • бизнесийн үйл явцын жишээнээс тогтоосон таймерын хугацаа дуусах;
  • API-ээр дамжуулан үйлдлийг хянах (жишээ нь: процессыг зогсоох).

Ийм үйл явдал бүр бизнесийн үйл явцын төлөвийг өөрчилж болно: зарим үйл ажиллагаа дуусч, бусад нь эхэлж, байнгын шинж чанаруудын үнэ цэнэ өөрчлөгдөж болно. Аливаа үйл ажиллагааг хаах нь дараах үйл ажиллагааны нэг буюу хэд хэдэн үйл ажиллагааг идэвхжүүлэхэд хүргэж болзошгүй. Эдгээр нь эргээд бусад үйл явдлуудыг хүлээхээ больж, эсвэл нэмэлт мэдээлэл шаардлагагүй бол ижил гүйлгээг хийж болно. Гүйлгээг хаахаас өмнө бизнесийн үйл явцын шинэ төлөв нь мэдээллийн санд хадгалагдаж, дараагийн гадаад үйл явдлыг хүлээх болно.

Харилцааны мэдээллийн санд хадгалагдсан бизнесийн үйл явцын байнгын өгөгдөл нь SELECT FOR UPDATE-г ашиглах үед боловсруулахад маш тохиромжтой синхрончлолын цэг юм. Хэрэв нэг гүйлгээ өгөгдлийн сангаас бизнесийн үйл явцын төлөвийг авч чадсан бол түүнийг өөрчлөхийн тулд өөр ямар ч гүйлгээ нь өөр өөрчлөлтөд ижил төлөвийг авах боломжгүй бөгөөд эхний гүйлгээ дууссаны дараа хоёр дахь нь аль хэдийн өөрчлөгдсөн төлөвийг хүлээн авах баталгаатай.

DBMS тал дээр гутранги түгжээг ашигласнаар бид шаардлагатай бүх шаардлагыг биелүүлдэг ХҮЧИЛ, мөн ажиллаж байгаа тохиолдлын тоог нэмэгдүүлэх замаар програмыг бизнесийн логикоор масштаблах чадварыг хадгална.

Гэсэн хэдий ч гутранги түгжээ нь биднийг мухардалд заналхийлж байгаа бөгөөд энэ нь бизнесийн логикийн зарим ноцтой тохиолдлуудад гацсан тохиолдолд SELECT FOR UPDATE нь боломжийн хугацаатай байх ёстой гэсэн үг юм.

Өөр нэг асуудал бол бизнесийн үйл явцын эхлэлийг синхрончлох явдал юм. Бизнесийн процессын жишээ байхгүй ч мэдээллийн санд төлөв байхгүй тул тайлбарласан арга ажиллахгүй. Хэрэв та бизнесийн үйл явцын жишээний өвөрмөц байдлыг тодорхой хүрээнд баталгаажуулахыг хүсвэл процессын ангилал болон харгалзах хамрах хүрээтэй холбоотой синхрончлолын объект хэрэгтэй болно. Энэ асуудлыг шийдэхийн тулд бид гадны үйлчилгээгээр дамжуулан URI форматын түлхүүрээр тодорхойлсон дурын нөөцийн түгжээг авах боломжийг олгодог өөр түгжих механизмыг ашигладаг.

Бидний жишээн дээр InitialPlayer бизнесийн үйл явц нь тунхаглал агуулсан байдаг

uniqueConstraint = UniqueConstraints.singleton

Тиймээс бүртгэл нь холбогдох түлхүүрийн түгжээг авч, тайлах тухай мессежүүдийг агуулдаг. Бусад бизнесийн процессуудад ийм мессеж байхгүй байна: uniqueConstraint тохируулагдаагүй байна.

Байнгын төлөвтэй бизнесийн үйл явцын асуудлууд

Заримдаа байнгын төлөв байдал нь зөвхөн тусалдаг төдийгүй хөгжилд үнэхээр саад болдог.
Бизнесийн логик ба / эсвэл бизнесийн үйл явцын загварт өөрчлөлт оруулах шаардлагатай үед асуудал эхэлдэг. Ийм өөрчлөлт нь бизнесийн үйл явцын хуучин байдалтай нийцэхгүй байна. Хэрэв өгөгдлийн санд олон "амьд" тохиолдлууд байгаа бол үл нийцэх өөрчлөлтүүд нь jBPM-ийг ашиглах үед бидэнд ихэвчлэн тулгардаг маш их асуудал үүсгэдэг.

Өөрчлөлтийн гүнээс хамааран та хоёр аргаар ажиллаж болно.

  1. хуучндаа үл нийцэх өөрчлөлт хийхгүйн тулд шинэ бизнесийн үйл явцын төрлийг үүсгэж, шинэ тохиолдлуудыг эхлүүлэхдээ хуучин нэгний оронд үүнийг ашиглана уу. Хуучин тохиолдлууд "хуучин арга замаар" үргэлжлүүлэн ажиллах болно;
  2. бизнесийн логикийг шинэчлэх үед бизнесийн үйл явцын байнгын төлөвийг шилжүүлэх.

Эхний арга нь илүү хялбар боловч хязгаарлалт, сул талуудтай, жишээлбэл:

  • бизнесийн үйл явцын олон загварт бизнесийн логикийн давхардал, бизнесийн логикийн хэмжээ нэмэгдэх;
  • ихэвчлэн бизнесийн шинэ логик руу нэн даруй шилжих шаардлагатай байдаг (бараг үргэлж нэгтгэх ажлуудын хувьд);
  • Хөгжүүлэгч нь хуучирсан загваруудыг ямар үед устгах боломжтойг мэдэхгүй байна.

Практикт бид хоёр аргыг хоёуланг нь ашигладаг боловч амьдралаа хялбарчлах хэд хэдэн шийдвэр гаргасан:

  • өгөгдлийн санд бизнесийн үйл явцын байнгын төлөвийг уншихад хялбар, боловсруулахад хялбар хэлбэрээр хадгалдаг: JSON форматын мөрөнд. Энэ нь програмын дотор болон гадна талд шилжих хөдөлгөөнийг хийх боломжийг танд олгоно. Онцгой тохиолдолд та үүнийг бариулаар тохируулж болно (ялангуяа дибаг хийх явцад хөгжүүлэхэд хэрэгтэй);
  • Бизнесийн интеграцийн логик нь бизнесийн үйл явцын нэрийг ашигладаггүй тул оролцогчдын аль нэгийг хэрэгжүүлэх ажлыг шинэ нэрээр (жишээлбэл, "InitialPlayerV2") солих боломжтой. Холболт нь мессеж, дохионы нэрээр дамждаг;
  • процессын загвар нь хувилбарын дугаартай бөгөөд хэрэв бид энэ загварт нийцэхгүй өөрчлөлт хийвэл энэ дугаарыг нэмэгдүүлэх бөгөөд энэ дугаар нь процессын жишээний төлөвтэй хамт хадгалагдана;
  • үйл явцын байнгын төлөвийг үндсэн загвараас эхлээд тухайн загварын хувилбарын дугаар өөрчлөгдсөн тохиолдолд шилжүүлэх процедур ажиллах боломжтой тохиромжтой объектын загвар болгон уншдаг;
  • шилжих журмыг бизнесийн логикийн хажууд байрлуулсан бөгөөд мэдээллийн сангаас сэргээх үед бизнесийн үйл явцын тохиолдол бүрийн хувьд "залхуу" гэж нэрлэгддэг;
  • Хэрэв та бүх процессын тохиолдлуудын төлөвийг хурдан бөгөөд синхроноор шилжүүлэх шаардлагатай бол мэдээллийн баазын шилжилтийн илүү сонгодог шийдлүүдийг ашигладаг, гэхдээ та тэнд JSON-тэй ажиллах хэрэгтэй.

Надад бизнесийн үйл явцын өөр хүрээ хэрэгтэй юу?

Нийтлэлд тайлбарласан шийдлүүд нь бидний амьдралыг ихээхэн хялбаршуулж, програм боловсруулах түвшинд шийдэгдсэн асуудлын хүрээг өргөжүүлж, бизнесийн логикийг микро үйлчилгээнд хуваах санааг илүү сонирхолтой болгох боломжийг бидэнд олгосон. Үүний тулд маш их ажил хийгдэж, бизнесийн үйл явцын маш "хөнгөн" хүрээ, өргөн хүрээний хэрэглээний даалгаврын хүрээнд тодорхойлсон асуудлуудыг шийдвэрлэх үйлчилгээний бүрэлдэхүүн хэсгүүдийг бий болгосон. Бид эдгээр үр дүнг хуваалцаж, нийтлэг бүрэлдэхүүн хэсгүүдийн хөгжлийг үнэгүй лицензийн дагуу нээлттэй хандалтад оруулах хүсэлтэй байна. Үүнд бага зэрэг хүчин чармайлт, цаг хугацаа шаардагдана. Ийм шийдлүүдийн эрэлт хэрэгцээг ойлгох нь бидний хувьд нэмэлт урамшуулал болж чадна. Санал болгож буй нийтлэлд хүрээний чадавхид маш бага анхаарал хандуулсан боловч тэдгээрийн зарим нь танилцуулсан жишээнүүдээс харагдаж байна. Хэрэв бид өөрсдийн хүрээг нийтлэх юм бол тусдаа нийтлэлийг түүнд зориулах болно. Энэ хооронд та асуултанд хариулж бага зэрэг санал хүсэлтээ үлдээвэл бид талархах болно.

Зөвхөн бүртгэлтэй хэрэглэгчид санал асуулгад оролцох боломжтой. Нэвтрэх, гуйя.

Надад бизнесийн үйл явцын өөр хүрээ хэрэгтэй юу?

  • 18,8%Тийм ээ, би ийм зүйлийг удаан хугацаанд хайж байсан.

  • 12,5%Таны хэрэгжилтийн талаар илүү ихийг мэдэх нь сонирхолтой байна, энэ нь ашигтай байж магадгүй2

  • 6,2%Бид одоо байгаа хүрээнүүдийн аль нэгийг ашиглаж байгаа ч үүнийг солих талаар бодож байна1

  • 18,8%Бид одоо байгаа хүрээнүүдийн аль нэгийг ашигладаг, бүх зүйл тохирсон3

  • 18,8%хүрээгүйгээр даван туулах3

  • 25,0%өөрөө бичих4

16 хэрэглэгч санал өгсөн. 7 хэрэглэгч түдгэлзсэн.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх