Integrasi gaya BPM

Integrasi gaya BPM

Hi Habr!

Syarikat kami pakar dalam pembangunan penyelesaian perisian kelas ERP, bahagian terbesarnya diduduki oleh sistem transaksi dengan sejumlah besar logik perniagaan dan aliran dokumen ala EDMS. Versi semasa produk kami adalah berdasarkan teknologi JavaEE, tetapi kami juga sedang bereksperimen secara aktif dengan perkhidmatan mikro. Salah satu bidang yang paling bermasalah bagi penyelesaian tersebut ialah penyepaduan pelbagai subsistem yang dimiliki oleh domain bersebelahan. Masalah integrasi sentiasa memberi kami sakit kepala yang besar, tanpa mengira gaya seni bina, susunan teknologi dan rangka kerja yang kami gunakan, tetapi baru-baru ini terdapat kemajuan dalam menyelesaikan masalah sedemikian.

Dalam artikel yang saya bawa untuk perhatian anda, saya akan bercakap tentang pengalaman dan penyelidikan seni bina yang NPO Krista ada di kawasan yang ditetapkan. Kami juga akan melihat contoh penyelesaian mudah kepada masalah penyepaduan dari sudut pandangan pembangun aplikasi dan mengetahui perkara yang tersembunyi di sebalik kesederhanaan ini.

Penafian

Penyelesaian seni bina dan teknikal yang diterangkan dalam artikel itu dicadangkan oleh saya berdasarkan pengalaman peribadi dalam konteks tugas tertentu. Penyelesaian ini tidak mendakwa sebagai universal dan mungkin tidak optimum di bawah syarat penggunaan lain.

Apa kaitan BPM dengannya?

Untuk menjawab soalan ini, kita perlu menyelidiki sedikit lebih mendalam ke dalam spesifik masalah yang digunakan bagi penyelesaian kita. Bahagian utama logik perniagaan dalam sistem transaksi biasa kami ialah memasukkan data ke dalam pangkalan data melalui antara muka pengguna, pengesahan manual dan automatik data ini, menjalankannya melalui beberapa aliran kerja, menerbitkannya ke sistem lain / pangkalan data analitik / arkib, dan menjana laporan . Oleh itu, fungsi utama sistem untuk pelanggan ialah automasi proses perniagaan dalaman mereka.

Untuk kemudahan, kami menggunakan istilah "dokumen" dalam komunikasi sebagai beberapa abstraksi set data yang disatukan oleh kunci biasa yang mana aliran kerja tertentu boleh "dipautkan".
Tetapi bagaimana dengan logik integrasi? Lagipun, tugas integrasi dihasilkan oleh seni bina sistem, yang "dipotong" menjadi bahagian BUKAN atas permintaan pelanggan, tetapi di bawah pengaruh faktor yang sama sekali berbeza:

  • tertakluk kepada undang-undang Conway;
  • akibat penggunaan semula subsistem yang dibangunkan sebelum ini untuk produk lain;
  • mengikut budi bicara arkitek, berdasarkan keperluan tidak berfungsi.

Terdapat godaan hebat untuk memisahkan logik integrasi daripada logik perniagaan aliran kerja utama, supaya tidak mencemarkan logik perniagaan dengan artifak penyepaduan dan melegakan pembangun aplikasi daripada keperluan untuk menyelidiki spesifik landskap seni bina sistem. Pendekatan ini mempunyai beberapa kelebihan, tetapi amalan menunjukkan ketidakberkesannya:

  • menyelesaikan masalah penyepaduan biasanya kembali kepada pilihan paling mudah dalam bentuk panggilan segerak kerana titik sambungan terhad dalam pelaksanaan aliran kerja utama (kelemahan penyepaduan segerak dibincangkan di bawah);
  • artifak penyepaduan masih menembusi logik perniagaan teras apabila maklum balas daripada subsistem lain diperlukan;
  • pembangun aplikasi mengabaikan penyepaduan dan boleh memecahkannya dengan mudah dengan menukar aliran kerja;
  • sistem tidak lagi menjadi satu keseluruhan dari sudut pandangan pengguna, "jahitan" antara subsistem menjadi ketara, dan operasi pengguna yang berlebihan muncul, memulakan pemindahan data dari satu subsistem ke subsistem yang lain.

Pendekatan lain ialah mempertimbangkan interaksi penyepaduan sebagai bahagian penting dalam logik perniagaan teras dan aliran kerja. Untuk mengelakkan kelayakan pembangun aplikasi daripada melonjak naik, mencipta interaksi penyepaduan baharu haruslah mudah dan mudah, dengan peluang yang minimum untuk memilih penyelesaian. Ini lebih sukar untuk dilakukan daripada yang kelihatan: alat itu mestilah cukup berkuasa untuk menyediakan pengguna dengan pelbagai pilihan yang diperlukan untuk penggunaannya, tanpa membenarkan dia "menembak dirinya sendiri." Terdapat banyak soalan yang mesti dijawab oleh jurutera dalam konteks tugas penyepaduan, tetapi yang tidak sepatutnya difikirkan oleh pembangun aplikasi dalam kerja hariannya: sempadan transaksi, konsistensi, atomicity, keselamatan, penskalaan, pengagihan beban dan sumber, penghalaan, pemasungan, konteks pengedaran dan penukaran, dsb. Pembangun aplikasi perlu menawarkan templat penyelesaian yang agak mudah di mana jawapan kepada semua soalan sedemikian telah disembunyikan. Templat ini mestilah agak selamat: logik perniagaan berubah dengan kerap, yang meningkatkan risiko memperkenalkan ralat, kos ralat mesti kekal pada tahap yang agak rendah.

Tetapi apa kaitan BPM dengannya? Terdapat banyak pilihan untuk melaksanakan aliran kerja...
Malah, satu lagi pelaksanaan proses perniagaan sangat popular dalam penyelesaian kami - melalui takrifan pengisytiharan gambar rajah peralihan keadaan dan sambungan pengendali dengan logik perniagaan untuk peralihan. Dalam kes ini, keadaan yang menentukan kedudukan semasa "dokumen" dalam proses perniagaan ialah atribut "dokumen" itu sendiri.

Integrasi gaya BPM
Beginilah rupa prosesnya pada permulaan sesuatu projek

Populariti pelaksanaan ini adalah disebabkan oleh kesederhanaan dan kelajuan relatif untuk mencipta proses perniagaan linear. Walau bagaimanapun, apabila sistem perisian terus menjadi lebih kompleks, bahagian automatik proses perniagaan berkembang dan menjadi lebih kompleks. Terdapat keperluan untuk penguraian, penggunaan semula bahagian proses, serta proses percabangan supaya setiap cawangan dilaksanakan secara selari. Dalam keadaan sedemikian, alat menjadi menyusahkan, dan rajah peralihan keadaan kehilangan kandungan maklumatnya (interaksi penyepaduan tidak ditunjukkan dalam rajah sama sekali).

Integrasi gaya BPM
Beginilah rupa proses selepas beberapa lelaran penjelasan keperluan.

Jalan keluar dari situasi ini ialah penyepaduan enjin jBPM ke dalam beberapa produk dengan proses perniagaan yang paling kompleks. Dalam jangka pendek, penyelesaian ini mempunyai beberapa kejayaan: ia menjadi mungkin untuk melaksanakan proses perniagaan yang kompleks sambil mengekalkan rajah yang agak bermaklumat dan relevan dalam notasi BPMN2.

Integrasi gaya BPM
Sebahagian kecil daripada proses perniagaan yang kompleks

Dalam jangka panjang, penyelesaian itu tidak memenuhi jangkaan: intensiti buruh yang tinggi untuk mencipta proses perniagaan melalui alat visual tidak membenarkan pencapaian penunjuk produktiviti yang boleh diterima, dan alat itu sendiri menjadi salah satu yang paling tidak disukai dalam kalangan pembangun. Terdapat juga aduan mengenai struktur dalaman enjin, yang membawa kepada kemunculan banyak "tampalan" dan "tongkat".

Aspek positif utama menggunakan jBPM ialah kesedaran tentang faedah dan kemudaratan mempunyai keadaan berterusan contoh proses perniagaan sendiri. Kami juga melihat kemungkinan menggunakan pendekatan proses untuk melaksanakan protokol penyepaduan kompleks antara aplikasi yang berbeza menggunakan interaksi tak segerak melalui isyarat dan mesej. Kehadiran keadaan yang berterusan memainkan peranan penting dalam hal ini.

Berdasarkan perkara di atas, kita boleh membuat kesimpulan: Pendekatan proses dalam gaya BPM membolehkan kami menyelesaikan pelbagai tugas untuk mengautomasikan proses perniagaan yang semakin kompleks, menyelaraskan aktiviti penyepaduan secara harmoni ke dalam proses ini dan mengekalkan keupayaan untuk memaparkan proses yang dilaksanakan secara visual dalam tatatanda yang sesuai.

Kelemahan panggilan segerak sebagai corak penyepaduan

Penyepaduan segerak merujuk kepada panggilan menyekat yang paling mudah. Satu subsistem bertindak sebagai bahagian pelayan dan mendedahkan API dengan kaedah yang diperlukan. Subsistem lain bertindak sebagai pihak pelanggan dan pada masa yang tepat membuat panggilan dan menunggu hasilnya. Bergantung pada seni bina sistem, bahagian klien dan pelayan boleh ditempatkan sama ada dalam aplikasi dan proses yang sama, atau dalam aplikasi yang berbeza. Dalam kes kedua, anda perlu menggunakan beberapa pelaksanaan RPC dan menyediakan marshalling parameter dan hasil panggilan.

Integrasi gaya BPM

Corak integrasi ini mempunyai set kelemahan yang agak besar, tetapi ia digunakan secara meluas dalam amalan kerana kesederhanaannya. Kepantasan pelaksanaan memikat dan memaksa anda untuk menggunakannya berulang kali dalam menghadapi tarikh akhir yang mendesak, merekodkan penyelesaian sebagai hutang teknikal. Tetapi ia juga berlaku bahawa pemaju yang tidak berpengalaman menggunakannya secara tidak sedar, semata-mata tidak menyedari akibat negatif.

Sebagai tambahan kepada peningkatan yang paling jelas dalam sambungan subsistem, terdapat juga masalah yang kurang jelas dengan transaksi "berkembang" dan "meregangkan". Sesungguhnya, jika logik perniagaan membuat beberapa perubahan, maka urus niaga tidak boleh dielakkan, dan urus niaga, seterusnya, menyekat sumber aplikasi tertentu yang terjejas oleh perubahan ini. Iaitu, sehingga satu subsistem menunggu jawapan daripada yang lain, ia tidak akan dapat menyelesaikan transaksi dan mengalih keluar kunci. Ini dengan ketara meningkatkan risiko pelbagai kesan:

  • Responsif sistem hilang, pengguna menunggu lama untuk jawapan kepada pertanyaan;
  • pelayan biasanya berhenti bertindak balas kepada permintaan pengguna kerana kumpulan benang yang terlalu sesak: majoriti benang dikunci pada sumber yang diduduki oleh transaksi;
  • Kebuntuan mula muncul: kemungkinan berlakunya sangat bergantung pada tempoh transaksi, jumlah logik perniagaan dan kunci yang terlibat dalam transaksi;
  • ralat tamat masa transaksi muncul;
  • pelayan "gagal" dengan OutOfMemory jika tugas memerlukan pemprosesan dan menukar sejumlah besar data, dan kehadiran penyepaduan segerak menjadikannya sangat sukar untuk memisahkan pemprosesan kepada transaksi "lebih ringan".

Dari sudut pandangan seni bina, penggunaan panggilan menyekat semasa penyepaduan membawa kepada kehilangan kawalan ke atas kualiti subsistem individu: adalah mustahil untuk memastikan penunjuk kualiti sasaran satu subsistem secara berasingan daripada penunjuk kualiti subsistem lain. Jika subsistem dibangunkan oleh pasukan yang berbeza, ini adalah masalah besar.

Perkara menjadi lebih menarik jika subsistem yang disepadukan berada dalam aplikasi yang berbeza dan anda perlu membuat perubahan segerak pada kedua-dua belah pihak. Bagaimana untuk memastikan transaksi perubahan ini?

Jika perubahan dibuat dalam urus niaga berasingan, maka anda perlu menyediakan pengendalian dan pampasan pengecualian yang boleh dipercayai, dan ini menghapuskan sepenuhnya manfaat utama penyepaduan segerak - kesederhanaan.

Urus niaga yang diedarkan juga terlintas di fikiran, tetapi kami tidak menggunakannya dalam penyelesaian kami: sukar untuk memastikan kebolehpercayaan.

"Saga" sebagai penyelesaian kepada masalah transaksi

Dengan peningkatan populariti perkhidmatan mikro, permintaan untuk Corak Saga.

Corak ini menyelesaikan masalah urus niaga lama yang disebutkan di atas dengan sempurna, dan juga mengembangkan keupayaan mengurus keadaan sistem dari sisi logik perniagaan: pampasan selepas transaksi yang gagal mungkin tidak mengembalikan sistem kepada keadaan asalnya, tetapi memberikan laluan pemprosesan data alternatif. Ini juga membolehkan anda mengelak daripada mengulangi langkah pemprosesan data yang berjaya disiapkan apabila cuba membawa proses itu ke pengakhiran yang "baik".

Menariknya, dalam sistem monolitik corak ini juga relevan apabila melibatkan penyepaduan subsistem gandingan longgar dan kesan negatif yang disebabkan oleh urus niaga yang berjalan lama dan kunci sumber yang sepadan diperhatikan.

Berhubung dengan proses perniagaan kami dalam gaya BPM, ternyata sangat mudah untuk melaksanakan "Sagas": langkah individu "Saga" boleh ditentukan sebagai aktiviti dalam proses perniagaan, dan keadaan berterusan proses perniagaan juga menentukan keadaan dalaman "Saga". Maksudnya, kami tidak memerlukan sebarang mekanisme penyelarasan tambahan. Apa yang anda perlukan ialah broker mesej yang menyokong jaminan "sekurang-kurangnya sekali" sebagai pengangkutan.

Tetapi penyelesaian ini juga mempunyai "harga" sendiri:

  • logik perniagaan menjadi lebih kompleks: pampasan perlu diuruskan;
  • adalah perlu untuk meninggalkan konsistensi penuh, yang boleh menjadi sangat sensitif untuk sistem monolitik;
  • Seni bina menjadi sedikit lebih rumit, dan keperluan tambahan untuk broker mesej muncul;
  • alat pemantauan dan pentadbiran tambahan akan diperlukan (walaupun secara umum ini bagus: kualiti perkhidmatan sistem akan meningkat).

Untuk sistem monolitik, justifikasi untuk menggunakan "Sag" tidak begitu jelas. Untuk perkhidmatan mikro dan SOA lain, di mana kemungkinan besar sudah ada broker, dan konsistensi penuh dikorbankan pada permulaan projek, faedah menggunakan corak ini boleh mengatasi kelemahan dengan ketara, terutamanya jika terdapat API yang mudah pada logik perniagaan tahap.

Merangkumkan logik perniagaan dalam perkhidmatan mikro

Apabila kami mula bereksperimen dengan perkhidmatan mikro, persoalan yang munasabah timbul: di mana hendak meletakkan logik perniagaan domain berhubung dengan perkhidmatan yang memastikan kegigihan data domain?

Apabila melihat seni bina pelbagai BPMS, nampaknya munasabah untuk memisahkan logik perniagaan daripada kegigihan: mencipta lapisan platform dan perkhidmatan mikro bebas domain yang membentuk persekitaran dan wadah untuk melaksanakan logik perniagaan domain, dan mereka bentuk kegigihan data domain sebagai lapisan berasingan perkhidmatan mikro yang sangat ringkas dan ringan. Proses perniagaan dalam kes ini melaksanakan orkestrasi perkhidmatan lapisan kegigihan.

Integrasi gaya BPM

Pendekatan ini mempunyai kelebihan yang sangat besar: anda boleh meningkatkan kefungsian platform seberapa banyak yang anda suka, dan hanya lapisan perkhidmatan mikro platform yang sepadan akan menjadi "gemuk" daripada ini. Proses perniagaan dari mana-mana domain serta-merta boleh menggunakan fungsi baharu platform sebaik sahaja ia dikemas kini.

Kajian yang lebih terperinci mendedahkan kelemahan ketara pendekatan ini:

  • perkhidmatan platform yang melaksanakan logik perniagaan banyak domain sekaligus membawa risiko besar sebagai satu titik kegagalan. Perubahan yang kerap kepada logik perniagaan meningkatkan risiko ralat yang membawa kepada kegagalan seluruh sistem;
  • isu prestasi: logik perniagaan berfungsi dengan datanya melalui antara muka yang sempit dan perlahan:
    • data sekali lagi akan dikawal dan dipam melalui timbunan rangkaian;
    • perkhidmatan domain selalunya akan menyediakan lebih banyak data daripada yang diperlukan untuk diproses oleh logik perniagaan disebabkan oleh keupayaan yang tidak mencukupi untuk permintaan parameter pada tahap API luaran perkhidmatan;
    • beberapa bahagian logik perniagaan bebas boleh berulang kali meminta semula data yang sama untuk diproses (masalah ini boleh dikurangkan dengan menambahkan komponen sesi yang menyimpan data, tetapi ini merumitkan lagi seni bina dan mewujudkan masalah perkaitan data dan ketidaksahihan cache);
  • masalah transaksi:
    • proses perniagaan dengan keadaan berterusan, yang disimpan oleh perkhidmatan platform, tidak konsisten dengan data domain, dan tidak ada cara mudah untuk menyelesaikan masalah ini;
    • meletakkan penyekatan data domain di luar urus niaga: jika logik perniagaan domain perlu membuat perubahan selepas terlebih dahulu menyemak ketepatan data semasa, adalah perlu untuk mengecualikan kemungkinan perubahan kompetitif dalam data yang diproses. Penyekatan data luaran boleh membantu menyelesaikan masalah, tetapi penyelesaian sedemikian membawa risiko tambahan dan mengurangkan kebolehpercayaan keseluruhan sistem;
  • kesukaran tambahan apabila mengemas kini: dalam beberapa kes, perkhidmatan kegigihan dan logik perniagaan perlu dikemas kini secara serentak atau dalam urutan yang ketat.

Akhirnya, kami terpaksa kembali kepada asas: merangkum data domain dan logik perniagaan domain ke dalam satu perkhidmatan mikro. Pendekatan ini memudahkan persepsi perkhidmatan mikro sebagai komponen penting dalam sistem dan tidak menimbulkan masalah di atas. Ini juga tidak diberikan secara percuma:

  • Penyeragaman API diperlukan untuk interaksi dengan logik perniagaan (khususnya, untuk menyediakan aktiviti pengguna sebagai sebahagian daripada proses perniagaan) dan perkhidmatan platform API; memerlukan perhatian yang lebih teliti terhadap perubahan API, keserasian ke hadapan dan ke belakang;
  • adalah perlu untuk menambah perpustakaan masa jalan tambahan untuk memastikan fungsi logik perniagaan sebagai sebahagian daripada setiap perkhidmatan mikro tersebut, dan ini menimbulkan keperluan baharu untuk perpustakaan tersebut: ringan dan minimum kebergantungan transitif;
  • pembangun logik perniagaan perlu memantau versi perpustakaan: jika perkhidmatan mikro tidak dimuktamadkan untuk masa yang lama, maka kemungkinan besar ia akan mengandungi versi perpustakaan yang sudah lapuk. Ini boleh menjadi halangan yang tidak dijangka untuk menambah ciri baharu dan mungkin memerlukan pemindahan logik perniagaan lama perkhidmatan sedemikian kepada versi perpustakaan baharu jika terdapat perubahan yang tidak serasi antara versi.

Integrasi gaya BPM

Lapisan perkhidmatan platform juga terdapat dalam seni bina sedemikian, tetapi lapisan ini tidak lagi membentuk bekas untuk melaksanakan logik perniagaan domain, tetapi hanya persekitarannya, menyediakan fungsi "platform" tambahan. Lapisan sedemikian diperlukan bukan sahaja untuk mengekalkan sifat ringan perkhidmatan mikro domain, tetapi juga untuk memusatkan pengurusan.

Sebagai contoh, aktiviti pengguna dalam proses perniagaan menjana tugas. Walau bagaimanapun, apabila bekerja dengan tugasan, pengguna mesti melihat tugas daripada semua domain dalam senarai umum, yang bermaksud mesti ada perkhidmatan pendaftaran tugas platform yang sepadan, dibersihkan daripada logik perniagaan domain. Mengekalkan enkapsulasi logik perniagaan dalam konteks sedemikian agak bermasalah, dan ini merupakan satu lagi kompromi seni bina ini.

Integrasi proses perniagaan melalui mata pembangun aplikasi

Seperti yang dinyatakan di atas, pembangun aplikasi mesti disarikan daripada ciri teknikal dan kejuruteraan untuk melaksanakan interaksi beberapa aplikasi supaya seseorang boleh bergantung kepada produktiviti pembangunan yang baik.

Mari cuba selesaikan masalah penyepaduan yang agak sukar, yang dicipta khas untuk artikel itu. Ini akan menjadi tugas "permainan" yang melibatkan tiga aplikasi, di mana setiap satu daripadanya mentakrifkan nama domain tertentu: "app1", "app2", "app3".

Di dalam setiap aplikasi, proses perniagaan dilancarkan yang mula "bermain bola" melalui bas integrasi. Mesej dengan nama "Bola" akan bertindak sebagai bola.

Peraturan permainan:

  • pemain pertama ialah pemula. Dia menjemput pemain lain ke permainan, memulakan permainan dan boleh menamatkannya pada bila-bila masa;
  • pemain lain mengisytiharkan penyertaan mereka dalam permainan, "mengenal" satu sama lain dan pemain pertama;
  • selepas menerima bola, pemain memilih pemain lain yang mengambil bahagian dan menghantar bola kepadanya. Jumlah bilangan penghantaran dikira;
  • Setiap pemain mempunyai "tenaga" yang berkurangan dengan setiap hantaran bola oleh pemain tersebut. Apabila tenaga habis, pemain meninggalkan permainan, mengumumkan peletakan jawatannya;
  • jika pemain ditinggalkan bersendirian, dia segera mengumumkan pemergiannya;
  • Apabila semua pemain disingkirkan, pemain pertama mengisytiharkan permainan tamat. Jika dia meninggalkan permainan lebih awal, dia tetap mengikuti permainan untuk menyelesaikannya.

Untuk menyelesaikan masalah ini, saya akan menggunakan DSL kami untuk proses perniagaan, yang membolehkan kami menerangkan logik dalam Kotlin dengan padat, dengan minimum boilerplate.

Proses perniagaan pemain pertama (aka pemula permainan) akan berfungsi dalam aplikasi app1:

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

Selain melaksanakan logik perniagaan, kod di atas boleh menghasilkan model objek proses perniagaan, yang boleh divisualisasikan dalam bentuk rajah. Kami belum melaksanakan visualizer lagi, jadi kami terpaksa meluangkan sedikit masa melukis (di sini saya ringkaskan sedikit notasi BPMN berkenaan penggunaan get untuk meningkatkan ketekalan gambar rajah dengan kod di bawah):

Integrasi gaya BPM

app2 akan menyertakan proses perniagaan pemain lain:

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

rajah:

Integrasi gaya BPM

Dalam aplikasi app3 kami akan membuat pemain dengan tingkah laku yang sedikit berbeza: daripada memilih pemain seterusnya secara rawak, dia akan bertindak mengikut algoritma round-robin:

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

Jika tidak, tingkah laku pemain tidak berbeza daripada yang sebelumnya, jadi gambar rajah tidak berubah.

Sekarang kita memerlukan ujian untuk menjalankan semua ini. Saya hanya akan memberikan kod ujian itu sendiri, supaya tidak mengacaukan artikel dengan boilerplate (sebenarnya, saya menggunakan persekitaran ujian yang dibuat sebelum ini untuk menguji integrasi proses perniagaan lain):

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

Mari jalankan ujian dan lihat log:

keluaran konsol

Взята Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ° ΠΊΠ»ΡŽΡ‡Π° 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!

Dari semua ini kita boleh membuat beberapa kesimpulan penting:

  • dengan alatan yang diperlukan, pembangun aplikasi boleh mencipta interaksi penyepaduan antara aplikasi tanpa mengganggu logik perniagaan;
  • kerumitan tugas integrasi yang memerlukan kecekapan kejuruteraan boleh disembunyikan dalam rangka kerja jika ini pada mulanya dimasukkan dalam seni bina rangka kerja. Kesukaran masalah tidak boleh disembunyikan, jadi penyelesaian kepada masalah sukar dalam kod akan kelihatan seperti itu;
  • Apabila membangunkan logik penyepaduan, adalah penting untuk mengambil kira ketekalan akhirnya dan kekurangan kebolehlinearan perubahan dalam keadaan semua peserta penyepaduan. Ini memaksa kita untuk merumitkan logik untuk menjadikannya tidak sensitif kepada susunan peristiwa luaran berlaku. Dalam contoh kami, pemain terpaksa mengambil bahagian dalam permainan selepas dia mengisytiharkan keluar daripada permainan: pemain lain akan terus menghantar bola kepadanya sehingga maklumat tentang keluarnya sampai dan diproses oleh semua peserta. Logik ini tidak mengikut peraturan permainan dan merupakan penyelesaian kompromi dalam rangka kerja seni bina yang dipilih.

Seterusnya, kami akan bercakap tentang pelbagai kerumitan penyelesaian kami, kompromi dan perkara lain.

Semua mesej berada dalam satu baris gilir

Semua aplikasi bersepadu berfungsi dengan satu bas integrasi, yang dibentangkan dalam bentuk broker luaran, satu BPMQueue untuk mesej dan satu topik BPMTopic untuk isyarat (peristiwa). Meletakkan semua mesej melalui satu baris gilir itu sendiri merupakan satu kompromi. Pada peringkat logik perniagaan, anda kini boleh memperkenalkan seberapa banyak jenis mesej baharu yang anda suka tanpa membuat perubahan pada struktur sistem. Ini adalah penyederhanaan yang ketara, tetapi ia membawa risiko tertentu, yang dalam konteks tugas biasa kami nampaknya tidak begitu penting kepada kami.

Integrasi gaya BPM

Walau bagaimanapun, terdapat satu kehalusan di sini: setiap aplikasi menapis mesej "nya" daripada baris gilir di pintu masuk, dengan nama domainnya. Domain juga boleh ditentukan dalam isyarat jika anda perlu mengehadkan "skop keterlihatan" isyarat kepada satu aplikasi. Ini sepatutnya meningkatkan daya pengeluaran bas, tetapi logik perniagaan kini mesti beroperasi dengan nama domain: untuk menangani mesej - wajib, untuk isyarat - wajar.

Memastikan Kebolehpercayaan Bas Integrasi

Kebolehpercayaan terdiri daripada beberapa perkara:

  • Broker mesej yang dipilih ialah komponen kritikal seni bina dan satu titik kegagalan: ia mestilah cukup toleran terhadap kesalahan. Anda harus menggunakan hanya pelaksanaan yang diuji masa, dengan sokongan yang baik dan komuniti yang besar;
  • adalah perlu untuk memastikan ketersediaan tinggi broker mesej, yang mana ia mesti dipisahkan secara fizikal daripada aplikasi bersepadu (ketersediaan tinggi aplikasi dengan logik perniagaan terpakai adalah lebih sukar dan mahal untuk dipastikan);
  • broker bertanggungjawab untuk memberikan jaminan penghantaran "sekurang-kurangnya sekali". Ini adalah keperluan wajib untuk operasi bas integrasi yang boleh dipercayai. Tidak ada keperluan untuk jaminan tahap "tepat sekali": proses perniagaan, sebagai peraturan, tidak sensitif terhadap ketibaan berulang mesej atau acara, dan dalam tugas khas yang penting ini, lebih mudah untuk menambah semakan tambahan pada perniagaan logik daripada sentiasa menggunakan jaminan yang agak β€œmahal” ";
  • menghantar mesej dan isyarat mesti terlibat dalam transaksi keseluruhan dengan perubahan dalam keadaan proses perniagaan dan data domain. Pilihan pilihan adalah menggunakan corak Peti Keluar Transaksi, tetapi ia memerlukan jadual tambahan dalam pangkalan data dan pengulang. Dalam aplikasi JEE, ini boleh dipermudahkan dengan menggunakan pengurus JTA tempatan, tetapi sambungan kepada broker yang dipilih mesti boleh berfungsi dalam XA;
  • pengendali mesej dan acara masuk juga mesti bekerjasama dengan transaksi yang mengubah keadaan proses perniagaan: jika transaksi sedemikian ditarik balik, maka penerimaan mesej mesti dibatalkan;
  • mesej yang tidak dapat dihantar kerana ralat mesti disimpan dalam storan yang berasingan D.L.Q. (Barisan Surat Mati). Untuk tujuan ini, kami mencipta perkhidmatan mikro platform berasingan yang menyimpan mesej sedemikian dalam storannya, mengindeksnya mengikut atribut (untuk pengumpulan dan carian pantas) dan mendedahkan API untuk melihat, menghantar semula ke alamat destinasi dan memadamkan mesej. Pentadbir sistem boleh bekerja dengan perkhidmatan ini melalui antara muka web mereka;
  • dalam tetapan broker, anda perlu melaraskan bilangan percubaan semula penghantaran dan kelewatan antara penghantaran untuk mengurangkan kemungkinan mesej masuk ke DLQ (hampir mustahil untuk mengira parameter optimum, tetapi anda boleh bertindak secara empirik dan menyesuaikannya semasa operasi );
  • Stor DLQ mesti dipantau secara berterusan, dan sistem pemantauan mesti memberi amaran kepada pentadbir sistem supaya apabila mesej tidak dihantar berlaku, mereka boleh bertindak balas secepat mungkin. Ini akan mengurangkan "kawasan terjejas" kegagalan atau ralat logik perniagaan;
  • bas integrasi mestilah tidak sensitif terhadap ketiadaan sementara aplikasi: langganan kepada topik mestilah tahan lama, dan nama domain aplikasi mestilah unik supaya sementara aplikasi itu tiada, orang lain tidak akan cuba memproses mesejnya daripada beratur.

Memastikan keselamatan benang logik perniagaan

Contoh proses perniagaan yang sama boleh menerima beberapa mesej dan acara sekaligus, yang pemprosesannya akan bermula selari. Pada masa yang sama, untuk pembangun aplikasi, segala-galanya harus mudah dan selamat untuk benang.

Logik perniagaan proses memproses setiap peristiwa luaran yang mempengaruhi proses perniagaan tersebut secara individu. Peristiwa sedemikian boleh menjadi:

  • melancarkan contoh proses perniagaan;
  • tindakan pengguna yang berkaitan dengan aktiviti dalam proses perniagaan;
  • penerimaan mesej atau isyarat yang mana contoh proses perniagaan dilanggan;
  • mencetuskan pemasa yang ditetapkan oleh contoh proses perniagaan;
  • tindakan kawalan melalui API (contohnya, gangguan proses).

Setiap peristiwa sedemikian boleh mengubah keadaan contoh proses perniagaan: sesetengah aktiviti mungkin berakhir dan yang lain mungkin bermula, dan nilai sifat berterusan mungkin berubah. Menutup sebarang aktiviti boleh mengakibatkan pengaktifan satu atau lebih aktiviti berikut. Mereka, seterusnya, boleh berhenti menunggu acara lain atau, jika mereka tidak memerlukan sebarang data tambahan, boleh menyelesaikan dalam transaksi yang sama. Sebelum menutup transaksi, keadaan baharu proses perniagaan disimpan dalam pangkalan data, di mana ia akan menunggu peristiwa luaran seterusnya berlaku.

Data proses perniagaan yang berterusan yang disimpan dalam pangkalan data hubungan adalah titik yang sangat mudah untuk menyegerakkan pemprosesan jika anda menggunakan SELECT FOR UPDATE. Jika satu transaksi berjaya mendapatkan keadaan proses perniagaan dari pangkalan untuk menukarnya, maka tiada transaksi lain secara selari akan dapat memperoleh keadaan yang sama untuk perubahan lain, dan selepas selesai transaksi pertama, transaksi kedua adalah dijamin menerima keadaan yang telah berubah.

Menggunakan kunci pesimis pada bahagian DBMS, kami memenuhi semua keperluan yang diperlukan ASID, dan juga mengekalkan keupayaan untuk menskalakan aplikasi dengan logik perniagaan dengan meningkatkan bilangan kejadian yang sedang berjalan.

Walau bagaimanapun, kunci pesimis mengancam kami dengan kebuntuan, yang bermaksud bahawa PILIH UNTUK KEMASKINI masih harus dihadkan kepada beberapa tamat masa yang munasabah sekiranya kebuntuan berlaku pada beberapa kes yang mengerikan dalam logik perniagaan.

Masalah lain ialah penyegerakan permulaan proses perniagaan. Walaupun tiada contoh proses perniagaan, tiada keadaan dalam pangkalan data, jadi kaedah yang diterangkan tidak akan berfungsi. Jika anda perlu memastikan keunikan contoh proses perniagaan dalam skop tertentu, maka anda memerlukan sejenis objek penyegerakan yang dikaitkan dengan kelas proses dan skop yang sepadan. Untuk menyelesaikan masalah ini, kami menggunakan mekanisme penguncian berbeza yang membolehkan kami mengambil kunci pada sumber sewenang-wenang yang ditentukan oleh kunci dalam format URI melalui perkhidmatan luaran.

Dalam contoh kami, proses perniagaan InitialPlayer mengandungi pengisytiharan

uniqueConstraint = UniqueConstraints.singleton

Oleh itu, log mengandungi mesej tentang mengambil dan melepaskan kunci kunci yang sepadan. Tiada mesej sedemikian untuk proses perniagaan lain: uniqueConstraint tidak ditetapkan.

Masalah proses perniagaan dengan keadaan berterusan

Kadang-kadang mempunyai keadaan yang berterusan bukan sahaja membantu, tetapi juga benar-benar menghalang pembangunan.
Masalah bermula apabila perubahan perlu dibuat pada logik perniagaan dan/atau model proses perniagaan. Tidak setiap perubahan itu serasi dengan keadaan lama proses perniagaan. Jika terdapat banyak kejadian langsung dalam pangkalan data, maka membuat perubahan yang tidak serasi boleh menyebabkan banyak masalah, yang sering kami hadapi semasa menggunakan jBPM.

Bergantung pada kedalaman perubahan, anda boleh bertindak dalam dua cara:

  1. cipta jenis proses perniagaan baharu supaya tidak membuat perubahan yang tidak serasi pada yang lama dan gunakannya dan bukannya yang lama apabila melancarkan kejadian baharu. Salinan lama akan terus berfungsi "seperti dahulu";
  2. memindahkan keadaan berterusan proses perniagaan apabila mengemas kini logik perniagaan.

Cara pertama adalah lebih mudah, tetapi mempunyai batasan dan keburukan, contohnya:

  • pertindihan logik perniagaan dalam banyak model proses perniagaan, meningkatkan jumlah logik perniagaan;
  • Selalunya peralihan segera kepada logik perniagaan baharu diperlukan (dari segi tugas penyepaduan - hampir selalu);
  • pembangun tidak tahu pada tahap mana model lapuk boleh dipadamkan.

Dalam amalan kami menggunakan kedua-dua pendekatan, tetapi telah membuat beberapa keputusan untuk menjadikan hidup kami lebih mudah:

  • Dalam pangkalan data, keadaan berterusan proses perniagaan disimpan dalam bentuk yang mudah dibaca dan mudah diproses: dalam rentetan format JSON. Ini membolehkan migrasi dilakukan dalam kedua-dua aplikasi dan luaran. Sebagai pilihan terakhir, anda boleh membetulkannya secara manual (terutamanya berguna dalam pembangunan semasa penyahpepijatan);
  • logik perniagaan integrasi tidak menggunakan nama proses perniagaan, supaya pada bila-bila masa adalah mungkin untuk menggantikan pelaksanaan salah satu proses yang mengambil bahagian dengan yang baharu dengan nama baharu (contohnya, β€œInitialPlayerV2”). Pengikatan berlaku melalui nama mesej dan isyarat;
  • model proses mempunyai nombor versi, yang kami tambah jika kami membuat perubahan yang tidak serasi pada model ini, dan nombor ini disimpan bersama-sama dengan keadaan contoh proses;
  • keadaan berterusan proses dibaca daripada pangkalan data terlebih dahulu ke dalam model objek yang mudah, yang prosedur penghijrahan boleh berfungsi jika nombor versi model telah berubah;
  • prosedur migrasi diletakkan di sebelah logik perniagaan dan dipanggil "malas" untuk setiap contoh proses perniagaan pada masa pemulihannya daripada pangkalan data;
  • jika anda perlu memindahkan keadaan semua kejadian proses dengan cepat dan serentak, lebih banyak penyelesaian migrasi pangkalan data klasik digunakan, tetapi anda perlu bekerja dengan JSON.

Adakah anda memerlukan rangka kerja lain untuk proses perniagaan?

Penyelesaian yang diterangkan dalam artikel itu membolehkan kami memudahkan kehidupan kami dengan ketara, mengembangkan julat isu yang diselesaikan pada peringkat pembangunan aplikasi, dan menjadikan idea untuk memisahkan logik perniagaan kepada perkhidmatan mikro lebih menarik. Untuk mencapai matlamat ini, banyak kerja telah dilakukan, rangka kerja yang sangat "ringan" untuk proses perniagaan telah dicipta, serta komponen perkhidmatan untuk menyelesaikan masalah yang dikenal pasti dalam konteks pelbagai masalah aplikasi. Kami mempunyai keinginan untuk berkongsi keputusan ini dan menjadikan pembangunan komponen biasa akses terbuka di bawah lesen percuma. Ini memerlukan sedikit usaha dan masa. Memahami permintaan untuk penyelesaian sedemikian boleh menjadi insentif tambahan untuk kami. Dalam artikel yang dicadangkan, sangat sedikit perhatian diberikan kepada keupayaan rangka kerja itu sendiri, tetapi sebahagian daripadanya dapat dilihat dari contoh yang dibentangkan. Jika kami menerbitkan rangka kerja kami, artikel berasingan akan dikhaskan untuknya. Sementara itu, kami akan berterima kasih jika anda meninggalkan sedikit maklum balas dengan menjawab soalan:

Hanya pengguna berdaftar boleh mengambil bahagian dalam tinjauan. Log masuk, Sama-sama.

Adakah anda memerlukan rangka kerja lain untuk proses perniagaan?

  • 18,8% Ya, saya sudah lama mencari sesuatu seperti ini

  • 12,5% Saya berminat untuk mengetahui lebih lanjut tentang pelaksanaan anda, mungkin berguna2

  • 6,2% Kami menggunakan salah satu rangka kerja sedia ada, tetapi sedang memikirkan untuk menggantikan1

  • 18,8% Kami menggunakan salah satu rangka kerja yang ada, semuanya baik-baik saja3

  • 18,8% kita uruskan tanpa rangka3

  • 25,0% tulis milik anda4

16 pengguna mengundi. 7 pengguna berpantang.

Sumber: www.habr.com

Tambah komen