Gunakan Spring untuk membuat mesin alur kerja sederhana

Banyak aplikasi perusahaan Jave memerlukan pemrosesan untuk dijalankan dalam konteks yang terpisah dari sistem utama. Dalam banyak kasus, proses backend ini melakukan beberapa tugas, dengan beberapa tugas bergantung pada status tugas sebelumnya. Dengan persyaratan tugas pemrosesan yang saling bergantung, implementasi yang menggunakan satu set metode pemanggilan gaya prosedural biasanya terbukti tidak memadai. Memanfaatkan Spring, pengembang dapat dengan mudah memisahkan proses backend menjadi agregasi aktivitas. Penampung Spring menggabungkan aktivitas tersebut untuk membentuk alur kerja sederhana.

Untuk tujuan artikel ini, alur kerja sederhana didefinisikan sebagai rangkaian aktivitas apa pun yang dilakukan dalam urutan yang telah ditentukan sebelumnya tanpa interaksi pengguna. Pendekatan ini, bagaimanapun, tidak disarankan sebagai pengganti kerangka kerja alur kerja yang ada. Untuk skenario di mana interaksi yang lebih maju diperlukan, seperti bercabang, bergabung, atau transisi berdasarkan input pengguna, sumber terbuka mandiri atau mesin alur kerja komersial dilengkapi dengan lebih baik. Satu proyek open source telah berhasil mengintegrasikan desain alur kerja yang lebih kompleks dengan Spring.

Jika tugas alur kerja yang ada sederhana, pendekatan alur kerja sederhana masuk akal dibandingkan dengan kerangka kerja mandiri yang berfungsi penuh, terutama jika Spring sudah digunakan, karena penerapan cepat dijamin tanpa memerlukan waktu peningkatan. Selain itu, mengingat sifat container Inversion-of-Control Spring yang ringan, Spring mengurangi overhead sumber daya.

Artikel ini secara singkat memperkenalkan alur kerja sebagai topik pemrograman. Menggunakan konsep alur kerja, Spring digunakan sebagai kerangka kerja untuk menggerakkan mesin alur kerja. Kemudian, opsi penerapan produksi dibahas. Mari kita mulai dengan ide alur kerja sederhana dengan berfokus pada pola desain alur kerja dan informasi latar belakang terkait.

Alur kerja sederhana

Alur kerja pemodelan adalah topik yang telah dipelajari sejauh tahun 1970-an, dan banyak pengembang telah mencoba untuk membuat spesifikasi pemodelan alur kerja standar. Pola Alur Kerja , kertas putih oleh WHM van der Aalst et al. (Juli 2003), telah berhasil mengklasifikasikan sekumpulan pola desain yang secara akurat memodelkan skenario alur kerja yang paling umum. Di antara pola alur kerja yang paling sepele adalah pola Urutan. Sesuai dengan kriteria alur kerja sederhana, pola alur kerja Urutan terdiri dari serangkaian aktivitas yang dijalankan secara berurutan.

Diagram aktivitas UML (Unified Modeling Language) biasanya digunakan sebagai mekanisme untuk memodelkan alur kerja. Gambar 1 menunjukkan proses alur kerja Urutan dasar yang dimodelkan menggunakan diagram aktivitas UML standar.

Alur kerja Urutan adalah pola alur kerja standar yang lazim di aplikasi J2EE. Aplikasi J2EE biasanya membutuhkan urutan kejadian untuk terjadi di thread latar belakang atau secara asinkron. Diagram aktivitas Gambar 2 mengilustrasikan alur kerja sederhana untuk memberi tahu pelancong yang tertarik bahwa tiket pesawat ke tujuan favorit mereka telah berkurang.

Alur kerja maskapai penerbangan pada Gambar 1 bertanggung jawab untuk membuat dan mengirim pemberitahuan email dinamis. Setiap langkah dalam proses merepresentasikan suatu aktivitas. Beberapa peristiwa eksternal harus terjadi sebelum alur kerja dijalankan. Dalam hal ini, peristiwa tersebut merupakan penurunan tarif untuk rute penerbangan suatu maskapai.

Mari kita telusuri logika bisnis alur kerja maskapai penerbangan. Jika aktivitas pertama tidak menemukan pengguna yang tertarik dengan pemberitahuan penurunan nilai, seluruh alur kerja dibatalkan. Jika pengguna yang tertarik ditemukan, aktivitas yang tersisa diselesaikan. Selanjutnya, transformasi XSL (Extensible Stylesheet Language) menghasilkan konten pesan, setelah itu informasi audit dicatat. Terakhir, dilakukan upaya untuk mengirim pesan melalui server SMTP. Jika pengiriman selesai tanpa kesalahan, sukses dicatat dan proses berakhir. Namun, jika kesalahan terjadi saat berkomunikasi dengan server SMTP, rutinitas penanganan kesalahan khusus akan mengambil alih. Kode penanganan kesalahan ini akan mencoba mengirim ulang pesan tersebut.

Mengingat contoh maskapai penerbangan, satu pertanyaan jelas: Bagaimana Anda bisa secara efisien memecah proses berurutan menjadi aktivitas individu? Masalah ini ditangani dengan fasih menggunakan Spring. Mari kita bahas Spring sebagai kerangka kerja Inversi Kontrol.

Kontrol pembalik

Pegas memungkinkan kita untuk menghilangkan tanggung jawab pengendalian ketergantungan objek dengan memindahkan tanggung jawab ini ke wadah Pegas. Pengalihan tanggung jawab ini dikenal sebagai Inversion of Control (IoC) atau Dependency Injection. Lihat "Inversi Kontainer Kontrol dan Pola Injeksi Ketergantungan" karya Martin Fowler (martinfowler.com, Januari 2004) untuk diskusi yang lebih mendalam tentang IoC dan Injeksi Ketergantungan. Dengan mengelola ketergantungan antar objek, Spring menghilangkan kebutuhan akan kode perekat , kode yang ditulis dengan tujuan membuat kelas saling berkolaborasi.

Komponen alur kerja sebagai kacang musim semi

Sebelum kita melangkah terlalu jauh, sekarang adalah saat yang tepat untuk membahas konsep utama di balik Spring. The ApplicationContextinterface, mewarisi dari BeanFactoryantarmuka, memaksakan dirinya sebagai entitas pengendali yang sebenarnya atau wadah dalam Spring. The ApplicationContextbertanggung jawab untuk Instansiasi, konfigurasi, dan manajemen siklus hidup satu set kacang dikenal sebagai kacang Spring. Ini ApplicationContextdikonfigurasi dengan memasang kabel Spring beans dalam file konfigurasi berbasis XML. File konfigurasi ini menentukan sifat di mana kacang Spring berkolaborasi satu sama lain. Jadi, dalam bahasa Spring, kacang Spring yang berinteraksi dengan orang lain dikenal sebagai kolaborator. Secara default, kacang musim semi ada sebagai lajang di fileApplicationContext, tetapi atribut singleton dapat disetel ke false, secara efektif mengubahnya untuk berperilaku dalam apa yang disebut Spring sebagai mode prototipe .

Kembali ke contoh kami, dalam penurunan tiket pesawat, abstraksi dari rutinitas pengiriman SMTP dikirim sebagai aktivitas terakhir dalam contoh proses alur kerja (contoh kode tersedia di Sumber daya). Menjadi aktivitas kelima, kacang ini diberi nama yang tepat activity5. Untuk mengirim pesan, activity5diperlukan kolaborator delegasi dan penangan kesalahan:

Menerapkan komponen alur kerja sebagai hasil Spring beans dalam dua produk sampingan yang diinginkan, kemudahan pengujian unit dan tingkat kegunaan kembali yang tinggi. Pengujian unit yang efisien terbukti dengan sifat wadah IoC. Menggunakan wadah IoC seperti Spring, dependensi kolaborator dapat dengan mudah ditukar dengan penggantian tiruan selama pengujian. Dalam contoh maskapai penerbangan, Activitykacang musim semi seperti activity5dapat dengan mudah diambil dari tes mandiri ApplicationContext. Mengganti delegasi SMTP tiruan menjadi activity5memungkinkan pengujian unit activity5secara terpisah.

Produk sampingan kedua, dapat digunakan kembali, direalisasikan oleh aktivitas alur kerja seperti transformasi XSL. Transformasi XSL, yang diabstraksikan menjadi aktivitas alur kerja, sekarang dapat digunakan kembali oleh alur kerja apa pun yang berhubungan dengan transformasi XSL.

Memasang kabel alur kerja

Dalam API yang disediakan (dapat diunduh dari Sumber), Spring mengontrol sekelompok kecil pemain untuk berinteraksi dengan cara yang membentuk alur kerja. Antarmuka utamanya adalah:

  • Activity: Merangkum logika bisnis dari satu langkah dalam proses alur kerja.
  • ProcessContext: Objek berjenis ProcessContextditeruskan di antara aktivitas dalam alur kerja. Objek yang mengimplementasikan antarmuka ini bertanggung jawab untuk mempertahankan status saat alur kerja bertransisi dari satu aktivitas ke aktivitas berikutnya.
  • ErrorHandler: Menyediakan metode callback untuk menangani error.
  • Processor: Menjelaskan bean yang berfungsi sebagai pelaksana thread alur kerja utama.

Kutipan berikut dari kode sampel adalah konfigurasi kacang musim semi yang mengikat contoh maskapai penerbangan sebagai proses alur kerja sederhana.

             /property>  org.iocworkflow.test.sequence.ratedrop.RateDropContext  

The SequenceProcessor class is a concrete subclass that models a Sequence pattern. Wired to the processor are five activities that the workflow processor will execute in order.

When compared with most procedural backend process, the workflow solution really stands out as being capable of highly robust error handling. An error handler may be separately wired for each activity. This type of handler provides fine-grained error handling at the individual activity level. If no error handler is wired for an activity, the error handler defined for the overall workflow processor will handle the problem. For this example, if an unhandled error occurs any time during the workflow process, it will propagate out to be handled by the ErrorHandler bean, which is wired up using the defaultErrorHandler property.

More complex workflow frameworks persist state to a datastore between transitions. In this article, we're only interested in simple workflow cases where state transition is automatic. State information is only available in the ProcessContext during the actual workflow's runtime. Having only two methods, you can see the ProcessContext interface is on a diet:

public interface ProcessContext extends Serializable { public boolean stopProcess(); public void setSeedData(Object seedObject); }

The concrete ProcessContext class used for the airline example workflow is the RateDropContext class. The RateDropContext class encapsulates the data necessary to execute an airline rate drop workflow.

Until now, all bean instances have been singletons as per the default ApplicationContext's behavior. But we must create a new instance of the RateDropContext class for every invocation of the airline workflow. To handle this requirement, the SequenceProcessor is configured, taking a fully qualified class name as the processContextClass property. For every workflow execution, the SequenceProcessor retrieves a new instance of ProcessContext from Spring using the class name specified. For this to work, a nonsingleton Spring bean or prototype of type org.iocworkflow.test.sequence.simple.SimpleContext must exist in the ApplicationContext (see rateDrop.xml for the entire listing).

Seeding the workflow

Sekarang setelah kita tahu cara menyusun alur kerja sederhana menggunakan Spring, mari fokus pada pembuatan instance menggunakan data seed. Untuk memahami cara menyemai alur kerja, mari kita lihat metode yang diekspos pada Processorantarmuka sebenarnya :

public interface Processor { public boolean supports(Activity activity); public void doActivities(); public void doActivities(Object seedData); public void setActivities(List activities); public void setDefaultErrorHandler(ErrorHandler defaultErrorHandler); }

Dalam kebanyakan kasus, proses alur kerja membutuhkan beberapa rangsangan awal untuk memulai. Ada dua opsi untuk memulai prosesor: doActivities(Object seedData)metode atau alternatif tanpa argumennya. Daftar kode berikut adalah doAcvtivities()implementasi untuk SequenceProcessordisertakan dengan kode sampel:

 public void doActivities(Object seedData) { if (logger.isDebugEnabled()) logger.debug(getBeanName() + " processor is running.."); //retrieve injected by Spring List activities = getActivities(); //retrieve a new instance of the Workflow ProcessContext ProcessContext context = createContext(); if (seedData != null) context.setSeedData(seedData); for (Iterator it = activities.iterator(); it.hasNext();) { Activity activity = (Activity) it.next(); if (logger.isDebugEnabled()) logger.debug("running activity:" + activity.getBeanName() + " using arguments:" + context); try { context = activity.execute(context); } catch (Throwable th) { ErrorHandler errorHandler = activity.getErrorHandler(); if (errorHandler == null) { logger.info("no error handler for this action, run default error" + "handler and abort processing "); getDefaultErrorHandler().handleError(context, th); break; } else { logger.info("run error handler and continue"); errorHandler.handleError(context, th); } }