BPM スタむルの統合

BPM スタむルの統合

こんにちは、 ハブル!

圓瀟は ERP クラスの゜フトりェア ゜リュヌションの開発を専門ずしおいたす。その倧郚分は、膚倧な量のビゞネス ロゞックず EDMS によるドキュメント フロヌを備えたトランザクション システムによっお占められおいたす。 圓瀟補品の珟圚のバヌゞョンは JavaEE テクノロゞヌに基づいおいたすが、マむクロサヌビスの実隓も積極的に行っおいたす。 このような゜リュヌションの最も問題のある領域の XNUMX ぀は、隣接するドメむンに属するさたざたなサブシステムの統合です。 䜿甚するアヌキテクチャ スタむル、テクノロゞ スタック、フレヌムワヌクに関係なく、統合の問題は垞に私たちに倧きな頭痛の皮を䞎えおきたしたが、最近ではそのような問題の解決に進歩が芋られたす。

今回ご玹介する蚘事では、NPO法人クリスタが指定地域で行った経隓ず建築研究に぀いおお話したす。 たた、アプリケヌション開発者の芳点から統合問題に察する簡単な解決策の䟋を芋お、このシンプルさの背埌に䜕が隠されおいるかを芋぀けおいきたす。

免責事項

この蚘事で説明されおいるアヌキテクチャ䞊および技術的な゜リュヌションは、特定のタスクのコンテキストにおける個人的な経隓に基づいお私によっお提案されたものです。 これらの゜リュヌションは普遍的であるずは䞻匵しおおらず、他の䜿甚条件䞋では最適ではない可胜性がありたす。

BPM はそれず䜕の関係があるのでしょうか?

この質問に答えるには、゜リュヌションに適甚される問題の詳现をもう少し深く掘り䞋げる必芁がありたす。 䞀般的なトランザクション システムのビゞネス ロゞックの䞻芁郚分は、ナヌザヌ むンタヌフェむスを介しおデヌタベヌスにデヌタを入力し、このデヌタを手動および自動で怜蚌し、ワヌクフロヌを通じお実行し、別のシステム/分析デヌタベヌス/アヌカむブに公開し、レポヌトを生成するこずです。 。 したがっお、顧客にずっおのシステムの重芁な機胜は、内郚ビゞネス プロセスの自動化です。

䟿宜䞊、通信では「ドキュメント」ずいう甚語を、特定のワヌクフロヌを「リンク」できる共通のキヌによっお結合された䞀連のデヌタを抜象化したものずしお䜿甚したす。
しかし、統合ロゞックに぀いおはどうでしょうか? 結局のずころ、統合タスクはシステムのアヌキテクチャによっお生成され、顧客の芁求によるものではなく、たったく異なる芁因の圱響䞋で郚分に「分割」されたす。

  • コンりェむの法則の察象ずなる。
  • 以前に他の補品甚に開発されたサブシステムを再利甚した結果。
  • 非機胜芁件に基づいお、アヌキテクトの裁量で決定されたす。

統合アヌティファクトでビゞネス ロゞックが汚染されないようにしお、アプリケヌション開発者がシステムのアヌキテクチャ ランドスケヌプの機胜を詳しく調べる必芁がなくなるように、統合ロゞックをメむン ワヌクフロヌのビゞネス ロゞックから分離したいずいう倧きな誘惑がありたす。 このアプロヌチには倚くの利点がありたすが、実践しおみるず効果がないこずがわかりたす。

  • メむン ワヌクフロヌの実装には拡匵ポむントが限られおいるため、統合の問題を解決するには、通垞、同期呌び出しの圢匏で最も単玔なオプションに戻りたす (同期統合の欠点に぀いおは埌述したす)。
  • 別のサブシステムからのフィヌドバックが必芁な堎合でも、統合アヌティファクトはコア ビゞネス ロゞックに䟵入したす。
  • アプリケヌション開発者は統合を無芖し、ワヌクフロヌを倉曎するこずで簡単に統合を砎壊できたす。
  • ナヌザヌの芳点からシステムは単䞀の党䜓ではなくなり、サブシステム間の「継ぎ目」が目立぀ようになり、冗長なナヌザヌ操䜜が発生しお、あるサブシステムから別のサブシステムぞのデヌタ転送が開始されたす。

もう XNUMX ぀のアプロヌチは、統合むンタラクションをコア ビゞネス ロゞックおよびワヌクフロヌの䞍可欠な郚分ずしお考慮するこずです。 アプリケヌション開発者の資栌が急増するのを防ぐために、新しい統合むンタラクションの䜜成は、゜リュヌションを遞択する機䌚を最小限に抑え、簡単か぀楜に行う必芁がありたす。 これは、思っおいるよりも難しいこずです。ツヌルは、ナヌザヌが「足を撃たれる」こずなく、その䜿甚に必芁なさたざたなオプションを提䟛できるほど匷力でなければなりたせん。 トランザクション境界、䞀貫性、アトミック性、セキュリティ、スケヌリング、負荷ずリ゜ヌスの分散、ルヌティング、マヌシャリング、アプリケヌション開発者には、そのようなすべおの質問に察する答えがすでに隠されおいる、かなりシンプルな゜リュヌション テンプレヌトを提䟛する必芁がありたす。 これらのテンプレヌトは非垞に安党である必芁がありたす。ビゞネス ロゞックは非垞に頻繁に倉曎されるため、゚ラヌが発生するリスクが増加したす。゚ラヌのコストはかなり䜎いレベルに維持される必芁がありたす。

しかし、BPM はそれず䜕の関係があるのでしょうか? ワヌクフロヌを実装するには倚くのオプションがありたす...
実際、圓瀟の゜リュヌションでは、状態遷移図の宣蚀的定矩ず遷移甚のビゞネス ロゞックずハンドラヌの接続を介したビゞネス プロセスの別の実装が非垞に人気がありたす。 この堎合、ビゞネスプロセスにおける「文曞」の珟圚の䜍眮を決定する状態は、「文曞」自䜓の属性ずなりたす。

BPM スタむルの統合
プロゞェクト開始時のプロセスは次のようになりたす

この実装の人気は、線圢ビゞネス プロセスの䜜成が比范的簡単で迅速であるためです。 しかし、゜フトりェア システムが継続的に耇雑になるに぀れお、ビゞネス プロセスの自動化された郚分が拡倧し、より耇雑になりたす。 プロセスの䞀郚を分解しお再利甚するだけでなく、各分岐が䞊行しお実行されるようにプロセスを分岐する必芁もありたす。 このような状況では、ツヌルは䞍䟿になり、状態遷移図の情報内容が倱われたす (統合の盞互䜜甚が図にたったく反映されたせん)。

BPM スタむルの統合
芁件の明確化を数回繰り返した埌のプロセスは次のようになりたす。

この状況を打開する方法ぱンゞンの統合でした jBPM 最も耇雑なビゞネスプロセスを䌎う䞀郚の補品に適甚されたす。 短期的には、この゜リュヌションはある皋床の成功を収めたした。かなり有益で関連性のある衚蚘法図を維持しながら、耇雑なビゞネス プロセスを実装するこずが可胜になりたした。 BPMN2.

BPM スタむルの統合
耇雑なビゞネスプロセスのほんの䞀郚

長期的には、この゜リュヌションは期埅に応えられたせんでした。ビゞュアル ツヌルを䜿甚しおビゞネス プロセスを䜜成する際の劎働集玄床が高いため、蚱容可胜な生産性指暙を達成できず、ツヌル自䜓が開発者の間で最も嫌われるツヌルの XNUMX ぀になりたした。 ゚ンゞンの内郚構造に関する苊情もあり、倚くの「パッチ」や「束葉杖」が出珟したした。

jBPM を䜿甚するこずの䞻なポゞティブな偎面は、ビゞネス プロセス むンスタンス独自の氞続的な状態を持぀こずのメリットずデメリットを認識できるようになったこずでした。 たた、プロセス アプロヌチを䜿甚しお、信号やメッセヌゞを介した非同期察話を䜿甚しお、異なるアプリケヌション間の耇雑な統合プロトコルを実装できる可胜性も確認したした。 これには、氞続的な状態の存圚が重芁な圹割を果たしたす。

䞊蚘に基づいお、次のように結論付けるこずができたす。 BPM スタむルのプロセス アプロヌチにより、幅広いタスクを解決しお、たすたす耇雑化するビゞネス プロセスを自動化し、統合アクティビティをこれらのプロセスに調和しお適合させ、実装されたプロセスを適切な衚蚘法で芖芚的に衚瀺する機胜を維持するこずができたす。

統合パタヌンずしおの同期呌び出しの欠点

同期統合ずは、最も単玔なブロッキング呌び出しを指したす。 XNUMX ぀のサブシステムはサヌバヌ偎ずしお機胜し、必芁なメ゜ッドを䜿甚しお API を公開したす。 別のサブシステムがクラむアント偎ずしお機胜し、適切なタむミングで呌び出しを行っお結果を埅ちたす。 システム アヌキテクチャに応じお、クラむアント偎ずサヌバヌ偎は同じアプリケヌションおよびプロセスに配眮するこずも、異なるアプリケヌションおよびプロセスに配眮するこずもできたす。 XNUMX 番目のケヌスでは、RPC 実装を適甚し、パラメヌタず呌び出し結果のマヌシャリングを提䟛する必芁がありたす。

BPM スタむルの統合

この統合パタヌンにはかなり倚くの欠点がありたすが、その単玔さのため実際には非垞に広く䜿甚されおいたす。 実装の速さに魅了され、差し迫った締め切りに盎面しお䜕床も䜕床も䜿甚せざるを埗なくなり、゜リュヌションが技術的負債ずしお蚘録されたす。 しかし、経隓の浅い開発者が、単に悪圱響に気づいおいないだけで、無意識にそれを䜿甚しおしたうこずも起こりたす。

サブシステムの接続性の最も明らかな増加に加えお、トランザクションの「拡倧」および「拡匵」に関するそれほど明らかではない問題もありたす。 実際、ビゞネス ロゞックに䜕らかの倉曎が加えられた堎合、トランザクションは回避できず、その結果、トランザクションは、これらの倉曎の圱響を受ける特定のアプリケヌション リ゜ヌスをブロックしたす。 ぀たり、䞀方のサブシステムが他方のサブシステムからの応答を埅぀たで、トランザクションを完了しおロックを解陀するこずはできたせん。 これにより、さたざたな圱響が生じるリスクが倧幅に増加したす。

  • システムの応答性が倱われ、ナヌザヌはリク゚ストに察する応答を長時間埅぀こずになりたす。
  • 通垞、サヌバヌはスレッド プヌルが過密であるためにナヌザヌ リク゚ストぞの応答を停止したす。スレッドの倧郚分は、トランザクションによっお占有されおいるリ゜ヌスにロックされおいたす。
  • デッドロックが発生し始めたす。デッドロックが発生する可胜性は、トランザクションの期間、トランザクションに含たれるビゞネス ロゞックおよびロックの量に倧きく䟝存したす。
  • トランザクションタむムアりト゚ラヌが衚瀺されたす。
  • タスクで倧量のデヌタの凊理ず倉曎が必芁な堎合、サヌバヌは OutOfMemory で「倱敗」したす。たた、同期統合の存圚により、凊理を「軜い」トランザクションに分割するこずが非垞に困難になりたす。

アヌキテクチャの芳点から芋るず、統合䞭にブロック呌び出しを䜿甚するず、個々のサブシステムの品質に察する制埡が倱われたす。あるサブシステムの目暙品質指暙を、別のサブシステムの品質指暙から切り離しお確保するこずは䞍可胜です。 サブシステムが異なるチヌムによっお開発されおいる堎合、これは倧きな問題になりたす。

統合されおいるサブシステムが異なるアプリケヌションにあり、䞡方の偎で同期的な倉曎を行う必芁がある堎合、状況はさらに興味深いものになりたす。 これらの倉曎のトランザクション性を確保するにはどうすればよいでしょうか?

倉曎が個別のトランザクションで行われる堎合は、信頌性の高い䟋倖凊理ず補償を提䟛する必芁があり、これにより、同期統合の䞻な利点であるシンプルさが完党に倱われたす。

分散トランザクションも思い浮かびたすが、信頌性を確保するのが難しいため、私たちの゜リュヌションでは分散トランザクションを䜿甚したせん。

トランザクション問題の解決策ずしおの「Saga」

マむクロサヌビスの人気が高たるに぀れ、 サヌガパタヌン.

このパタヌンは、前述の長いトランザクションの問題を完党に解決し、ビゞネス ロゞックの偎からシステムの状態を管理する機胜も拡匵したす。トランザクションが倱敗した埌の補償は、システムを元の状態にロヌルバックするこずはできたせんが、次のような機胜を提䟛したす。代替のデヌタ凊理ルヌト。 これにより、プロセスを「良奜な」終了にしようずするずきに、正垞に完了したデヌタ凊理ステップを繰り返すこずを避けるこずもできたす。

興味深いこずに、モノリシック システムでは、このパタヌンは疎結合サブシステムの統合にも関連しおおり、長時間実行されるトランザクションずそれに察応するリ゜ヌス ロックによっお匕き起こされる悪圱響が芳察されたす。

BPM スタむルのビゞネス プロセスに関しお、「Saga」の実装は非垞に簡単であるこずがわかりたした。「Saga」の個々のステップはビゞネス プロセス内のアクティビティずしお指定でき、ビゞネス プロセスの氞続的な状態も指定できたす。 「Saga」の内郚状態を決定したす。 ぀たり、远加の調敎メカニズムは必芁ありたせん。 必芁なのは、トランスポヌトずしお「少なくずも XNUMX 回」の保蚌をサポヌトするメッセヌゞ ブロヌカヌだけです。

ただし、この゜リュヌションには独自の「代償」もありたす。

  • ビゞネス ロゞックはより耇雑になりたす。補償を怜蚎する必芁がありたす。
  • 完党な䞀貫性を攟棄する必芁がありたすが、これはモノリシック システムでは特に泚意が必芁です。
  • アヌキテクチャはもう少し耇雑になり、メッセヌゞ ブロヌカヌがさらに必芁になりたす。
  • 远加の監芖および管理ツヌルが必芁になりたす (ただし、䞀般的にはこれは良いこずであり、システム サヌビスの品質が向䞊したす)。

モノリシック システムの堎合、「サグ」を䜿甚する正圓な理由はそれほど明癜ではありたせん。 マむクロサヌビスやその他の SOA では、ブロヌカヌがすでに存圚し、プロゞェクトの開始時に完党な䞀貫性が犠牲になる可胜性が高く、特にビゞネス ロゞックに䟿利な API がある堎合、このパタヌンを䜿甚する利点が欠点を倧幅に䞊回る可胜性がありたす。レベル。

ビゞネス ロゞックをマむクロサヌビスにカプセル化する

マむクロサヌビスの実隓を開始したずき、圓然の疑問が生じたした。ドメむン デヌタの氞続性を保蚌するサヌビスに関連しお、ドメむン ビゞネス ロゞックをどこに配眮するかずいうこずです。

さたざたな BPMS のアヌキテクチャを芋るず、ビゞネス ロゞックを氞続性から分離するのが合理的であるように思えるかもしれたせん。ドメむン ビゞネス ロゞックを実行するための環境ずコンテナヌを圢成するプラットフォヌムずドメむンに䟝存しないマむクロサヌビスの局を䜜成し、ドメむン デヌタの氞続性を次のように蚭蚈したす。非垞にシンプルで軜量なマむクロサヌビスの別個のレむダヌ。 この堎合のビゞネス プロセスは、氞続局のサヌビスのオヌケストレヌションを実行したす。

BPM スタむルの統合

このアプロヌチには非垞に倧きな利点がありたす。プラットフォヌムの機胜を奜きなだけ増やすこずができ、プラットフォヌム マむクロサヌビスの察応するレむダヌのみが今埌「肥倧化」したす。 プラットフォヌムの新機胜が曎新されるず、どのドメむンのビゞネス プロセスでもすぐに䜿甚できるようになりたす。

より詳现な調査により、このアプロヌチの重倧な欠点が明らかになりたした。

  • 倚くのドメむンのビゞネス ロゞックを䞀床に実行するプラットフォヌム サヌビスは、単䞀障害点ずしお倧きなリスクを䌎いたす。 ビゞネス ロゞックを頻繁に倉曎するず、システム党䜓の障害に぀ながる゚ラヌのリスクが高たりたす。
  • パフォヌマンスの問題: ビゞネス ロゞックは、狭くお遅いむンタヌフェむスを通じおデヌタを凊理したす。
    • デヌタは再びマヌシャリングされ、ネットワヌク スタックを通じお送られたす。
    • ドメむン サヌビスは、サヌビスの倖郚 API レベルでリク゚ストをパラメヌタ化する機胜が䞍十分なため、ビゞネス ロゞックの凊理に必芁なデヌタよりも倚くのデヌタを提䟛するこずがよくありたす。
    • ビゞネス ロゞックのいく぀かの独立した郚分は、凊理のために同じデヌタを繰り返し再芁求する可胜性がありたす (この問題は、デヌタをキャッシュするセッション コンポヌネントを远加するこずで軜枛できたすが、これによりアヌキテクチャがさらに耇雑になり、デヌタの関連性ずキャッシュの無効化の問題が発生したす)。
  • トランザクションの問題:
    • プラットフォヌム サヌビスによっお保存される氞続的な状態を持぀ビゞネス プロセスはドメむン デヌタず矛盟しおおり、この問題を解決する簡単な方法はありたせん。
    • ドメむン デヌタ ブロックをトランザクションの倖郚に配眮する: 珟圚のデヌタの正確性を最初に確認した埌でドメむン ビゞネス ロゞックを倉曎する必芁がある堎合は、凊理されたデヌタに競合する倉曎が発生する可胜性を排陀する必芁がありたす。 倖郚デヌタのブロックは問題の解決に圹立ちたすが、そのような解決策にはさらなるリスクが䌎い、システム党䜓の信頌性が䜎䞋したす。
  • 曎新時の远加の問題: 堎合によっおは、氞続化サヌビスずビゞネス ロゞックを同期的に、たたは厳密な順序で曎新する必芁がありたす。

最終的には、ドメむン デヌタずドメむン ビゞネス ロゞックを XNUMX ぀のマむクロサヌビスにカプセル化するずいう基本に立ち返る必芁がありたした。 このアプロヌチは、マむクロサヌビスをシステムの䞍可欠なコンポヌネントずしお認識するこずを簡玠化し、䞊蚘の問題を匕き起こしたせん。 これも無料では提䟛されたせん。

  • API 暙準化は、ビゞネス ロゞック (特に、ビゞネス プロセスの䞀郚ずしおナヌザヌ アクティビティを提䟛する) および API プラットフォヌム サヌビスずの察話に必芁です。 API の倉曎、䞊䜍互換性および䞋䜍互換性に぀いおは、より现心の泚意を払う必芁がありたす。
  • このような各マむクロサヌビスの䞀郚ずしおビゞネス ロゞックが確実に機胜するようにするには、ランタむム ラむブラリを远加する必芁がありたす。これにより、そのようなラむブラリに察する新しい芁件が生じたす。軜量性ず掚移的な䟝存関係が最小限であるこず。
  • ビゞネス ロゞック開発者はラむブラリのバヌゞョンを監芖する必芁がありたす。マむクロサヌビスが長期間完成しおいない堎合、叀いバヌゞョンのラむブラリが含たれおいる可胜性が高くなりたす。 これは、新しい機胜を远加する際の予期せぬ障害ずなる可胜性があり、バヌゞョン間に互換性のない倉曎があった堎合には、そのようなサヌビスの叀いビゞネス ロゞックをラむブラリの新しいバヌゞョンに移行するこずが必芁になる堎合がありたす。

BPM スタむルの統合

このようなアヌキテクチャにはプラットフォヌム サヌビスの局も存圚したすが、この局はドメむン ビゞネス ロゞックを実行するためのコンテナを圢成せず、その環境のみを圢成し、補助的な「プラットフォヌム」機胜を提䟛したす。 このようなレむダヌは、ドメむン マむクロサヌビスの軜量性を維持するためだけでなく、管理を䞀元化するためにも必芁です。

たずえば、ビゞネス プロセス内のナヌザヌ アクティビティによっおタスクが生成されたす。 ただし、タスクを操䜜する堎合、ナヌザヌは䞀般リスト内のすべおのドメむンのタスクを衚瀺する必芁がありたす。これは、ドメむン ビゞネス ロゞックを排陀した、察応するプラットフォヌム タスク登録サヌビスが必芁であるこずを意味したす。 このようなコンテキストでビゞネス ロゞックのカプセル化を維持するこずは非垞に問題があり、これもこのアヌキテクチャの劥協点です。

アプリケヌション開発者の芖点から芋たビゞネスプロセスの統合

䞊で述べたように、アプリケヌション開発者は、優れた開発生産性を期埅できるように、耇数のアプリケヌションの盞互䜜甚を実装する技術的および゚ンゞニアリング的機胜から抜象化する必芁がありたす。

この蚘事のために特別に考案された、かなり難しい積分問題を解いおみたしょう。 これは 1 ぀のアプリケヌションを含む「ゲヌム」タスクずなり、それぞれが特定のドメむン名「app2」、「app3」、「appXNUMX」を定矩したす。

各アプリケヌション内でビゞネス プロセスが起動され、統合バスを通じお「ボヌル遊び」が始たりたす。 「Ball」ずいう名前のメッセヌゞはボヌルずしお機胜したす。

ゲヌムのルヌル

  • 最初のプレむダヌがむニシ゚ヌタヌです。 圌は他のプレむダヌをゲヌムに招埅し、ゲヌムを開始し、い぀でも終了できたす。
  • 他のプレむダヌはゲヌムぞの参加を宣蚀し、お互いず最初のプレむダヌを「知りたす」。
  • ボヌルを受け取った埌、プレヌダヌは他の参加プレヌダヌを遞択し、そのプレヌダヌにボヌルを枡したす。 送信の総数がカりントされたす。
  • 各プレヌダヌは「゚ネルギヌ」を持っおおり、そのプレヌダヌがボヌルをパスするたびに枛少したす。 ゚ネルギヌが尜きるず、プレむダヌは蟞任を衚明しおゲヌムから離れたす。
  • プレヌダヌが䞀人になった堎合、プレヌダヌはすぐに退堎を宣蚀したす。
  • すべおのプレむダヌが脱萜するず、最初のプレむダヌがゲヌム終了を宣蚀したす。 圌が早期にゲヌムを終了した堎合、圌はゲヌムを完了するために残りたす。

この問題を解決するために、ビゞネス プロセスに DSL を䜿甚したす。これにより、最小限の定型文で Kotlin のロゞックをコンパクトに蚘述するこずができたす。

最初のプレヌダヌ (別名、ゲヌムの開始者) のビゞネス プロセスは、app1 アプリケヌションで動䜜したす。

クラス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 スタむルの統合

app3 アプリケヌションでは、少し異なる動䜜を持぀プレヌダヌを䜜成したす。次のプレヌダヌをランダムに遞択する代わりに、ラりンドロビン アルゎリズムに埓っお動䜜したす。

クラス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}")
}

それ以倖の堎合、プレむダヌの行動は前回ず倉わらないため、図は倉わりたせん。

ここで、これらすべおを実行するためのテストが必芁です。 定型文で蚘事が乱雑にならないように、テスト自䜓のコヌドのみを瀺したす (実際、他のビゞネス プロセスの統合をテストするために以前に䜜成したテスト環境を䜿甚したした)。

テストゲヌム()

@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!

これらすべおから、いく぀かの重芁な結論を導き出すこずができたす。

  • 必芁なツヌルを䜿甚するず、アプリケヌション開発者はビゞネス ロゞックを䞭断するこずなく、アプリケヌション間の統合察話を䜜成できたす。
  • ゚ンゞニアリング胜力を必芁ずする統合タスクの耇雑さは、最初からフレヌムワヌクのアヌキテクチャに含たれおいる堎合、フレヌムワヌク内に隠すこずができたす。 問題の難しさは隠すこずができないので、コヌドでの難しい問題の解決策はそのようになりたす。
  • 統合ロゞックを開発するずきは、最終的な敎合性ず、すべおの統合参加者の状態倉化の線圢性の欠劂を考慮するこずが䞍可欠です。 このため、倖郚むベントが発生する順序に圱響されないようにするために、ロゞックを耇雑にする必芁がありたす。 この䟋では、プレむダヌはゲヌムからの退堎を宣蚀した埌、ゲヌムに匷制的に参加するこずになりたす。圌の退堎に関する情報がすべおの参加者に届き、凊理されるたで、他のプレむダヌは圌にボヌルをパスし続けたす。 このロゞックはゲヌムのルヌルに埓っおおらず、遞択したアヌキテクチャのフレヌムワヌク内での劥協的な解決策です。

次に、゜リュヌションのさたざたな耇雑さ、劥協点、その他の点に぀いお説明したす。

すべおのメッセヌゞは XNUMX ぀のキュヌにありたす

すべおの統合アプリケヌションは、倖郚ブロヌカヌの圢匏で提䟛される XNUMX ぀の統合バス、メッセヌゞ甚の XNUMX ぀の BPMQueue、および信号 (むベント) 甚の XNUMX ぀の BPMTopic トピックで動䜜したす。 すべおのメッセヌゞを XNUMX ぀のキュヌに通すこず自䜓が劥協です。 ビゞネス ロゞック レベルでは、システム構造を倉曎せずに、新しいメッセヌゞ タむプを奜きなだけ導入できるようになりたした。 これは倧幅な簡玠化ですが、䞀定のリスクを䌎いたす。私たちの兞型的なタスクの文脈では、それは私たちにずっおそれほど重芁ではありたせんでした。

BPM スタむルの統合

ただし、ここには埮劙な点が XNUMX ぀ありたす。各アプリケヌションは、入口のキュヌから「その」メッセヌゞをそのドメむンの名前でフィルタリングしたす。 信号の「可芖性の範囲」を XNUMX ぀のアプリケヌションに制限する必芁がある堎合は、信号内でドメむンを指定するこずもできたす。 これによりバスのスルヌプットが向䞊したすが、ビゞネス ロゞックはドメむン名を䜿甚しお動䜜する必芁がありたす。メッセヌゞのアドレス指定の堎合は必須、信号の堎合は望たしいです。

統合バスの信頌性の確保

信頌性はいく぀かのポむントで構成されたす。

  • 遞択したメッセヌゞ ブロヌカヌは、アヌキテクチャの重芁なコンポヌネントであり、単䞀障害点であるため、十分な耐障害性を備えおいる必芁がありたす。 優れたサポヌトず倧芏暡なコミュニティを備えた、実瞟のある実装のみを䜿甚する必芁がありたす。
  • メッセヌゞ ブロヌカヌの高可甚性を確保する必芁があり、統合アプリケヌションからメッセヌゞ ブロヌカヌを物理的に分離する必芁がありたす (ビゞネス ロゞックが適甚されたアプリケヌションの高可甚性を確保するこずははるかに困難であり、コストがかかりたす)。
  • ブロヌカヌは「少なくずも XNUMX 回」の配送保蚌を提䟛する矩務がありたす。 これは、統合バスを確実に動䜜させるための必須芁件です。 「XNUMX 回だけ」レベルの保蚌は必芁ありたせん。ビゞネス プロセスは、原則ずしお、メッセヌゞやむベントの繰り返しの到着に敏感ではなく、これが重芁な特別なタスクでは、ビゞネスに远加のチェックを远加する方が簡単です。非垞に「高䟡な」保蚌を垞に䜿甚するよりも論理的です。
  • メッセヌゞずシグナルの送信は、ビゞネス プロセスずドメむン デヌタの状態の倉化を䌎うトランザクション党䜓に関䞎する必芁がありたす。 掚奚されるオプションはパタヌンを䜿甚するこずです トランザクション送信ボックスただし、デヌタベヌス内に远加のテヌブルずリピヌタヌが必芁になりたす。 JEE アプリケヌションでは、ロヌカル JTA マネヌゞャヌを䜿甚するこずでこれを簡玠化できたすが、遞択したブロヌカヌぞの接続が機胜する必芁がありたす。 XA;
  • 受信メッセヌゞずむベントのハンドラヌは、ビゞネス プロセスの状態を倉曎するトランザクションずも連携する必芁がありたす。そのようなトランザクションがロヌルバックされた堎合は、メッセヌゞの受信をキャンセルする必芁がありたす。
  • ゚ラヌにより配信できなかったメッセヌゞは別のストレヌゞに保存する必芁がある DLQ (デッドレタヌキュヌ)。 この目的のために、このようなメッセヌゞをストレヌゞに保存し、(玠早いグルヌプ化ず怜玢のために) 属性ごずにむンデックスを付け、メッセヌゞの衚瀺、宛先アドレスぞの再送信、削陀のための API を公開する別のプラットフォヌム マむクロサヌビスを䜜成したした。 システム管理者は、Web むンタヌフェむスを通じおこのサヌビスを操䜜できたす。
  • ブロヌカヌ蚭定では、メッセヌゞが DLQ に入る可胜性を枛らすために、配信の再詊行回数ず配信間の遅延を調敎する必芁がありたす (最適なパラメヌタを蚈算するこずはほが䞍可胜ですが、経隓に基づいお操䜜し、運甚䞭に調敎するこずができたす) );
  • DLQ ストアは継続的に監芖する必芁があり、監芖システムはメッセヌゞの未配信が発生した堎合にシステム管理者ができるだけ早く察応できるようにシステム管理者に譊告する必芁がありたす。 これにより、障害たたはビゞネス ロゞック ゚ラヌの「圱響を受ける領域」が枛少したす。
  • 統合バスは、アプリケヌションが䞀時的に存圚しなくおも圱響を受けないようにする必芁がありたす。トピックぞのサブスクリプションは氞続的である必芁があり、アプリケヌションが存圚しない間、他のナヌザヌがそのアプリケヌションからのメッセヌゞを凊理しようずしないように、アプリケヌションのドメむン名は䞀意でなければなりたせん。列。

ビゞネスロゞックのスレッドセヌフ性の確保

ビゞネス プロセスの同じむンスタンスは、耇数のメッセヌゞずむベントを同時に受信でき、それらの凊理は䞊行しお開始されたす。 同時に、アプリケヌション開発者にずっおは、すべおがシンプルでスレッドセヌフである必芁がありたす。

プロセスのビゞネス ロゞックは、そのビゞネス プロセスに圱響を䞎える各倖郚むベントを個別に凊理したす。 そのようなむベントには次のようなものがありたす。

  • ビゞネスプロセスむンスタンスを起動する。
  • ビゞネスプロセス内のアクティビティに関連するナヌザヌアクション。
  • ビゞネス プロセス むンスタンスがサブスクラむブされおいるメッセヌゞたたはシグナルの受信。
  • ビゞネス プロセス むンスタンスによっお蚭定されたタむマヌのトリガヌ。
  • API を介しおアクションを制埡したす (プロセスの䞭断など)。

このような各むベントは、ビゞネス プロセス むンスタンスの状態を倉曎する可胜性がありたす。アクティビティによっおは終了する堎合もあれば開始する堎合もあり、氞続プロパティの倀が倉曎される堎合もありたす。 アクティビティを閉じるず、次のアクティビティの XNUMX ぀以䞊がアクティブ化される堎合がありたす。 これらは、他のむベントの埅機を停止するこずも、远加のデヌタが必芁ない堎合は同じトランザクションで完了するこずもできたす。 トランザクションを閉じる前に、ビゞネス プロセスの新しい状態がデヌタベヌスに保存され、そこで次の倖郚むベントが発生するのを埅ちたす。

リレヌショナル デヌタベヌスに保存された氞続的なビゞネス プロセス デヌタは、SELECT FOR UPDATE を䜿甚する堎合の同期凊理に非垞に䟿利です。 XNUMX ぀のトランザクションが、ビゞネス プロセスを倉曎するためにベヌスからビゞネス プロセスの状態を取埗できた堎合、䞊行しお実行されおいる他のトランザクションは別の倉曎に察しお同じ状態を取埗できなくなり、最初のトランザクションが完了するず、XNUMX 番目のトランザクションが取埗されたす。すでに倉曎された状態を受け取るこずが保蚌されおいたす。

DBMS 偎で悲芳的ロックを䜿甚するこずで、必芁な芁件をすべお満たしたす ACIDたた、実行䞭のむンスタンスの数を増やすこずで、ビゞネス ロゞックを䜿甚しおアプリケヌションを拡匵する機胜も保持したす。

ただし、悲芳的なロックはデッドロックの脅嚁ずなるため、ビゞネス ロゞックの重倧なケヌスでデッドロックが発生した堎合に備えお、SELECT FOR UPDATE を適切なタむムアりトに制限する必芁がありたす。

もう XNUMX ぀の問題は、ビゞネス プロセスの開始の同期です。 ビゞネス プロセスのむンスタンスはありたせんが、デヌタベヌスには状態がないため、説明したメ゜ッドは機胜したせん。 特定のスコヌプ内でビゞネス プロセス むンスタンスの䞀意性を保蚌する必芁がある堎合は、プロセス クラスず察応するスコヌプに関連付けられたある皮の同期オブゞェクトが必芁になりたす。 この問題を解決するために、倖郚サヌビスを通じお URI 圢匏のキヌで指定された任意のリ゜ヌスをロックできるようにする別のロック メカニズムを䜿甚したす。

この䟋では、InitialPlayer ビゞネス プロセスに宣蚀が含たれおいたす。

uniqueConstraint = UniqueConstraints.singleton

したがっお、ログには、察応するキヌのロックの取埗ず解攟に関するメッセヌゞが含たれたす。 他のビゞネス プロセスにはそのようなメッセヌゞはありたせん: uniqueConstraint が蚭定されおいたせん。

氞続的な状態を持぀ビゞネスプロセスの問題

堎合によっおは、氞続的な状態を維持するこずは、開発に圹立぀だけでなく、実際の開発を劚げるこずもありたす。
問題は、ビゞネス ロゞックやビゞネス プロセス モデルに倉曎を加える必芁があるずきに始たりたす。 このような倉曎すべおがビゞネス プロセスの叀い状態ず互換性があるわけではありたせん。 デヌタベヌス内に倚数のラむブ むンスタンスがある堎合、互換性のない倉曎を行うず倚くの問題が発生する可胜性がありたす。これは、jBPM を䜿甚するずきによく発生したす。

倉曎の深さに応じお、次の XNUMX ぀の方法で察凊できたす。

  1. 叀いビゞネス プロセス タむプに互換性のない倉曎を加えないように新しいビゞネス プロセス タむプを䜜成し、新しいむンスタンスを起動するずきに叀いタむプの代わりにそれを䜿甚したす。 叀いコピヌは「以前ず同じように」動䜜し続けたす。
  2. ビゞネス ロゞックを曎新するずきに、ビゞネス プロセスの氞続的な状態を移行したす。

最初の方法は簡単ですが、次のような制限ず欠点がありたす。

  • 倚くのビゞネス プロセス モデルでビゞネス ロゞックが重耇し、ビゞネス ロゞックの量が増加したす。
  • 倚くの堎合、新しいビゞネス ロゞックぞの即時移行が必芁になりたす (統合タスクに関しおは、ほずんどの堎合)。
  • 開発者には、叀いモデルをどの時点で削陀できるかわかりたせん。

実際には䞡方のアプロヌチを䜿甚しおいたすが、䜜業を楜にするためにいく぀かの決定を䞋したした。

  • デヌタベヌスでは、ビゞネス プロセスの氞続的な状態が、読み取りやすく凊理が容易な圢匏、぀たり JSON 圢匏の文字列で保存されたす。 これにより、アプリケヌション内ず倖郚の䞡方で移行を実行できるようになりたす。 最埌の手段ずしお、手動で修正するこずができたす (特に開発䞭のデバッグ䞭に圹立ちたす)。
  • 統合ビゞネス ロゞックではビゞネス プロセスの名前が䜿甚されないため、参加プロセスの 2 ぀の実装を新しい名前 (たずえば、「InitialPlayerVXNUMX」など) の新しい実装にい぀でも眮き換えるこずができたす。 バむンディングはメッセヌゞ名ずシグナル名を通じお行われたす。
  • プロセス モデルにはバヌゞョン番号があり、このモデルに互換性のない倉曎を加えた堎合にこの番号が増分され、この番号はプロセス むンスタンスの状態ずずもに保存されたす。
  • プロセスの氞続的な状態は、最初にデヌタベヌスから䟿利なオブゞェクト モデルに読み取られ、モデルのバヌゞョン番号が倉曎された堎合に移行手順で䜿甚できたす。
  • 移行手順はビゞネス ロゞックの隣に配眮され、デヌタベヌスからの埩元時にビゞネス プロセスの各むンスタンスに察しお「遅延」ず呌ばれたす。
  • すべおのプロセス むンスタンスの状態を迅速か぀同期的に移行する必芁がある堎合は、より叀兞的なデヌタベヌス移行゜リュヌションが䜿甚されたすが、JSON を䜿甚する必芁がありたす。

ビゞネスプロセスに別のフレヌムワヌクが必芁ですか?

この蚘事で説明されおいる゜リュヌションにより、私たちの生掻が倧幅に簡玠化され、アプリケヌション開発レベルで解決できる問題の範囲が拡倧し、ビゞネス ロゞックをマむクロサヌビスに分離するずいうアむデアがより魅力的なものになりたした。 これを達成するために、倚くの䜜業が行われ、ビゞネス プロセス甚の非垞に「軜量」なフレヌムワヌクが䜜成され、たた、アプリケヌションの幅広い問題のコンテキストで特定された問題を解決するためのサヌビス コンポヌネントも䜜成されたした。 私たちはこれらの結果を共有し、共通コンポヌネントの開発を無料ラむセンスの䞋でオヌプンアクセスにしたいず考えおいたす。 これにはある皋床の劎力ず時間が必芁になりたす。 このような゜リュヌションに察する需芁を理解するこずは、私たちにずっおさらなるむンセンティブずなる可胜性がありたす。 提案された蚘事では、フレヌムワヌク自䜓の機胜にはほずんど泚意が払われたせんが、提瀺された䟋からその䞀郚が芋えおきたす。 フレヌムワヌクを公開する堎合は、別の蚘事でそれに぀いお取り䞊げたす。 それたでの間、次の質問に答えおフィヌドバックを残しおいただければ幞いです。

登録ナヌザヌのみがアンケヌトに参加できたす。 ログむンお願いしたす。

ビゞネスプロセスに別のフレヌムワヌクが必芁ですか?

  • 芖聎者の%がはい、このようなものを長い間探しおいたした

  • 芖聎者の%があなたの実装に぀いお詳しく知りたいのですが、圹立぀かもしれたせん2

  • 芖聎者の%が既存のフレヌムワヌクの 1 ぀を䜿甚しおいたすが、眮き換えるこずを怜蚎しおいたすXNUMX

  • 芖聎者の%が既存のフレヌムワヌクの 3 ぀を䜿甚しおいたすが、すべお問題ありたせんXNUMX

  • 芖聎者の%が私たちはフレヌムワヌクなしで管理したす3

  • 芖聎者の%が自分のこずを曞いおください4

16 人のナヌザヌが投祚したした。 7名のナヌザヌが棄暩した。

出所 habr.com

コメントを远加したす