Merampingkan Integrasi Perangkat Lunak: Tutorial Unta Apache
Diterbitkan: 2022-03-11Perangkat lunak jarang, jika sama sekali, ada dalam kekosongan informasi. Setidaknya, itulah asumsi yang dapat kami buat oleh para insinyur perangkat lunak untuk sebagian besar aplikasi yang kami kembangkan.
Pada skala apa pun, setiap perangkat lunak—dalam satu atau lain cara—berkomunikasi dengan beberapa perangkat lunak lain karena berbagai alasan: untuk mendapatkan data referensi dari suatu tempat, untuk mengirim sinyal pemantauan, untuk berhubungan dengan layanan lain saat menjadi bagian dari distribusi sistem, dan lainnya.
Dalam tutorial ini, Anda akan mempelajari beberapa tantangan terbesar dalam mengintegrasikan perangkat lunak besar dan bagaimana Apache Camel menyelesaikannya dengan mudah.
Masalah: Desain Arsitektur untuk Integrasi Sistem
Anda mungkin telah melakukan hal berikut setidaknya sekali dalam kehidupan rekayasa perangkat lunak Anda:
- Identifikasi fragmen logika bisnis Anda yang harus memulai pengiriman data.
- Di lapisan aplikasi yang sama, tulis transformasi data sesuai dengan apa yang diharapkan penerima.
- Bungkus data dalam struktur yang cocok untuk mentransfer dan perutean melalui jaringan.
- Buka koneksi ke aplikasi target menggunakan driver yang sesuai atau SDK klien.
- Kirim data dan tangani responsnya.
Mengapa ini tindakan yang buruk?
Meskipun Anda hanya memiliki beberapa koneksi semacam ini, itu tetap dapat dikelola. Dengan semakin banyaknya hubungan antar sistem, logika bisnis aplikasi bercampur dengan logika integrasi, yaitu tentang mengadaptasi data, mengkompensasi perbedaan teknologi antara dua sistem, dan mentransfer data ke sistem eksternal dengan SOAP, REST, atau permintaan yang lebih eksotis .
Jika Anda mengintegrasikan beberapa aplikasi, akan sangat sulit untuk menelusuri kembali seluruh gambaran dependensi dalam kode seperti itu: Di mana data dihasilkan dan layanan apa yang mengkonsumsinya? Anda akan memiliki banyak tempat di mana logika integrasi diduplikasi, untuk boot.
Dengan pendekatan seperti itu, meskipun tugas secara teknis diselesaikan, kami berakhir dengan masalah besar dengan keterawasan dan skalabilitas integrasi. Reorganisasi cepat aliran data dalam sistem ini hampir tidak mungkin, belum lagi masalah yang lebih dalam seperti kurangnya pemantauan, pemutusan sirkuit, pemulihan data yang melelahkan, dll.
Ini semua sangat penting ketika mengintegrasikan perangkat lunak dalam lingkup perusahaan yang cukup besar. Untuk menangani integrasi perusahaan berarti bekerja dengan satu set aplikasi, yang beroperasi pada berbagai platform dan ada di lokasi yang berbeda. Pertukaran data dalam lanskap perangkat lunak semacam itu cukup menuntut. Itu harus memenuhi standar keamanan industri yang tinggi dan menyediakan cara yang andal untuk mentransfer data. Dalam lingkungan perusahaan, integrasi sistem memerlukan desain arsitektur yang terpisah dan diuraikan secara menyeluruh.
Artikel ini akan memperkenalkan Anda pada kesulitan unik yang dihadapi dalam integrasi perangkat lunak serta memberikan beberapa solusi berbasis pengalaman untuk tugas integrasi. Kita akan mengenal Apache Camel, sebuah kerangka kerja yang berguna yang dapat meringankan bagian terburuk dari sakit kepala pengembang integrasi. Kami akan mengikuti dengan contoh bagaimana Camel dapat membantu membangun komunikasi dalam sekelompok layanan mikro yang didukung oleh Kubernetes.
Kesulitan Integrasi
Pendekatan yang banyak digunakan untuk memecahkan masalah adalah dengan memisahkan lapisan integrasi dalam aplikasi Anda. Itu bisa ada dalam aplikasi yang sama atau sebagai perangkat lunak khusus yang berjalan secara independen—dalam kasus terakhir disebut middleware.
Masalah apa yang biasanya Anda hadapi saat mengembangkan dan mendukung middleware? Secara umum, Anda memiliki item utama berikut:
- Semua saluran data tidak dapat diandalkan sampai batas tertentu. Masalah yang berasal dari ketidakandalan ini mungkin tidak terjadi saat intensitas data rendah hingga sedang. Setiap tingkat penyimpanan dari memori aplikasi hingga cache yang lebih rendah dan peralatan di bawahnya berpotensi mengalami kegagalan. Beberapa kesalahan langka muncul hanya dengan volume data yang besar. Bahkan produk vendor siap produksi yang matang memiliki masalah pelacak bug yang belum terselesaikan terkait dengan kehilangan data. Sistem middleware harus dapat memberi tahu Anda tentang korban data ini dan mengirimkan pengiriman kembali pesan secara tepat waktu.
- Aplikasi menggunakan protokol dan format data yang berbeda. Ini berarti bahwa sistem integrasi adalah tirai untuk transformasi data dan adaptor ke peserta lain dan memanfaatkan berbagai teknologi. Ini dapat mencakup panggilan REST API biasa, tetapi juga dapat mengakses broker antrian, mengirim pesanan CSV melalui FTP, atau menarik data secara batch ke tabel database. Ini adalah daftar yang panjang dan tidak akan pernah menjadi lebih pendek.
- Perubahan format data dan aturan perutean tidak dapat dihindari. Setiap langkah dalam proses pengembangan aplikasi, yang mengubah struktur data, biasanya mengarah pada perubahan format dan transformasi data integrasi. Terkadang, perubahan infrastruktur dengan aliran data perusahaan yang ditata ulang diperlukan. Misalnya, perubahan ini mungkin terjadi saat memperkenalkan satu titik validasi data referensi yang harus memproses semua entri data master di seluruh perusahaan. Dengan sistem
N
, kita mungkin memiliki maksimum hampirN^2
koneksi di antara mereka, sehingga jumlah tempat di mana perubahan harus diterapkan tumbuh cukup cepat. Ini akan menjadi seperti longsoran salju. Untuk mempertahankan pemeliharaan, lapisan middleware harus memberikan gambaran yang jelas tentang dependensi dengan perutean serbaguna dan transformasi data.
Ide-ide ini harus diingat ketika merancang integrasi dan memilih solusi middleware yang paling cocok. Salah satu cara yang mungkin untuk menanganinya adalah dengan memanfaatkan bus layanan perusahaan (ESB). Tetapi ESB yang disediakan oleh vendor besar umumnya terlalu berat dan seringkali lebih banyak masalah daripada nilainya: Hampir tidak mungkin untuk memulai dengan cepat dengan ESB, ia memiliki kurva belajar yang cukup curam, dan fleksibilitasnya dikorbankan untuk daftar panjang fitur dan alat bawaan. Menurut pendapat saya, solusi integrasi sumber terbuka yang ringan jauh lebih unggul—lebih elastis, mudah diterapkan ke cloud, dan mudah diskalakan.
Integrasi perangkat lunak tidak mudah dilakukan. Saat ini, saat kami membangun arsitektur layanan mikro dan menangani sekawanan layanan kecil, kami juga memiliki harapan yang tinggi tentang seberapa efisien layanan tersebut harus berkomunikasi.
Pola Integrasi Perusahaan
Seperti yang diharapkan, seperti pengembangan perangkat lunak pada umumnya, pengembangan perutean dan transformasi data melibatkan operasi berulang. Pengalaman di bidang ini telah dirangkum dan disistematisasikan oleh para profesional yang menangani masalah integrasi selama beberapa waktu. Hasilnya, ada satu set templat yang diekstraksi yang disebut pola integrasi perusahaan yang digunakan untuk merancang aliran data. Metode integrasi ini dijelaskan dalam buku dengan nama yang sama oleh Gregor Hope dan Bobby Wolfe, yang sangat mirip dengan buku Gang of Four yang signifikan tetapi di bidang perangkat lunak perekatan.
Sebagai contoh, pola normalizer memperkenalkan komponen yang memetakan pesan yang sama secara semantik yang memiliki format data berbeda ke model kanonik tunggal, atau agregator adalah EIP yang menggabungkan urutan pesan menjadi satu.
Karena mereka adalah abstraksi agnostik teknologi yang digunakan untuk memecahkan masalah arsitektur, EIP membantu dalam menulis desain arsitektur, yang tidak menyelidiki tingkat kode tetapi menjelaskan aliran data dengan cukup detail. Notasi untuk menggambarkan rute integrasi tidak hanya membuat desain menjadi ringkas tetapi juga menetapkan nomenklatur umum dan bahasa yang sama, yang sangat penting dalam konteks penyelesaian tugas integrasi dengan anggota tim dari berbagai area bisnis.
Memperkenalkan Unta Apache
Beberapa tahun yang lalu, saya sedang membangun integrasi perusahaan dalam jaringan ritel grosir besar dengan toko-toko di lokasi yang tersebar luas. Saya mulai dengan solusi ESB berpemilik, yang ternyata terlalu rumit untuk dipelihara. Kemudian, tim kami menemukan Apache Camel, dan setelah melakukan beberapa pekerjaan "bukti konsep", kami dengan cepat menulis ulang semua aliran data kami di rute Camel.
Apache Camel dapat digambarkan sebagai "router mediasi," kerangka kerja middleware berorientasi pesan yang mengimplementasikan daftar EIP, yang saya biasakan sendiri. Itu menggunakan pola-pola ini, mendukung semua protokol transport umum, dan memiliki banyak adaptor berguna yang disertakan. Camel memungkinkan penanganan sejumlah rutinitas integrasi tanpa perlu menulis kode Anda sendiri.
Terlepas dari ini, saya akan memilih fitur Apache Camel berikut:
- Rute integrasi ditulis sebagai jalur pipa yang terbuat dari blok. Ini menciptakan gambar yang benar-benar transparan untuk membantu melacak aliran data.
- Camel memiliki adaptor untuk banyak API populer. Misalnya, mendapatkan data dari Apache Kafka, memantau instans AWS EC2, berintegrasi dengan Salesforce—semua tugas ini dapat diselesaikan menggunakan komponen yang tersedia langsung.
Rute Apache Camel dapat ditulis dalam Java atau Scala DSL. (Konfigurasi XML juga tersedia tetapi menjadi terlalu bertele-tele dan memiliki kemampuan debug yang lebih buruk.) Ini tidak memberlakukan batasan pada tumpukan teknologi layanan komunikasi, tetapi jika Anda menulis dalam Java atau Scala, Anda dapat menyematkan Camel dalam aplikasi sebagai gantinya menjalankannya secara mandiri.
Notasi perutean yang digunakan oleh Camel dapat digambarkan dengan pseudocode sederhana berikut:
from(Source) .transform(Transformer) .to(Destination)
Source
, Transformer
, dan Destination
adalah titik akhir yang mengacu pada komponen implementasi oleh URI mereka.
Apa yang memungkinkan Camel menyelesaikan masalah integrasi yang saya jelaskan sebelumnya? Mari kita lihat. Pertama, logika perutean dan transformasi sekarang hanya aktif dalam konfigurasi Apache Camel khusus. Kedua, melalui DSL yang ringkas dan alami dalam hubungannya dengan penggunaan EIP, gambaran ketergantungan antar sistem muncul. Itu terbuat dari abstraksi yang dapat dipahami, dan logika perutean mudah disesuaikan. Dan terakhir, kita tidak perlu menulis tumpukan kode transformasi karena adaptor yang sesuai mungkin sudah disertakan.
Saya harus menambahkan, Apache Camel adalah kerangka kerja yang matang dan mendapat pembaruan rutin. Ini memiliki komunitas yang hebat dan basis pengetahuan kumulatif yang cukup besar.
Itu memang memiliki kekurangannya sendiri. Unta tidak boleh dianggap sebagai rangkaian integrasi yang kompleks. Ini adalah kotak alat tanpa fitur tingkat tinggi seperti alat manajemen proses bisnis atau monitor aktivitas, tetapi dapat digunakan untuk membuat perangkat lunak semacam itu.
Sistem alternatif mungkin, misalnya, Integrasi Pegas atau ESB Mule. Untuk Integrasi Musim Semi, meskipun dianggap ringan, menurut pengalaman saya, menggabungkannya dan menulis banyak file konfigurasi XML bisa menjadi rumit secara tak terduga dan bukan jalan keluar yang mudah. Mule ESB adalah perangkat yang kuat dan sangat fungsional, tetapi seperti namanya, ini adalah bus layanan perusahaan, sehingga termasuk dalam kategori bobot yang berbeda. Mule dapat dibandingkan dengan Fuse ESB, produk serupa berdasarkan Apache Camel dengan serangkaian fitur yang kaya. Bagi saya, menggunakan Apache Camel untuk menempelkan layanan adalah hal yang mudah hari ini. Mudah digunakan dan menghasilkan deskripsi yang jelas tentang apa yang terjadi di mana—pada saat yang sama, ini cukup fungsional untuk membangun integrasi yang kompleks.
Menulis Rute Sampel
Mari kita mulai menulis kodenya. Kita akan mulai dari aliran data sinkron yang mengarahkan pesan dari satu sumber ke daftar penerima. Aturan perutean akan ditulis dalam Java DSL.
Kami akan menggunakan Maven untuk membangun proyek. Pertama tambahkan ketergantungan berikut ke pom.xml
:
<dependencies> ... <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> <version>2.20.0</version> </dependency> </dependencies>
Sebagai alternatif, aplikasi dapat dibangun di atas camel-archetype-java
.
Definisi rute unta dideklarasikan dalam metode RouteBuilder.configure
.
public void configure() { errorHandler(defaultErrorHandler().maximumRedeliveries(0)); from("file:orders?noop=true").routeId("main") .log("Incoming File: ${file:onlyname}") .unmarshal().json(JsonLibrary.Jackson, Order.class) // unmarshal JSON to Order class containing List<OrderItem> .split().simple("body.items") // split list to process one by one .to("log:inputOrderItem") .choice() .when().simple("${body.type} == 'Drink'") .to("direct:bar") .when().simple("${body.type} == 'Dessert'") .to("direct:dessertStation") .when().simple("${body.type} == 'Hot Meal'") .to("direct:hotMealStation") .when().simple("${body.type} == 'Cold Meal'") .to("direct:coldMealStation") .otherwise() .to("direct:others"); from("direct:bar").routeId("bar").log("Handling Drink"); from("direct:dessertStation").routeId("dessertStation").log("Handling Dessert"); from("direct:hotMealStation").routeId("hotMealStation").log("Handling Hot Meal"); from("direct:coldMealStation").routeId("coldMealStation").log("Handling Cold Meal"); from("direct:others").routeId("others").log("Handling Something Other"); }
Dalam definisi ini, kami membuat rute yang mengambil catatan dari file JSON, membaginya menjadi item, dan merutekan ke kumpulan penangan berdasarkan konten pesan.
Mari kita jalankan pada data uji yang telah disiapkan. Kami akan mendapatkan output:
INFO | Total 6 routes, of which 6 are started INFO | Apache Camel 2.20.0 (CamelContext: camel-1) started in 10.716 seconds INFO | Incoming File: order1.json INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Drink', name='Americano', qty='1'}] INFO | Handling Drink INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Hot Meal', name='French Omelette', qty='1'}] INFO | Handling Hot Meal INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Hot Meal', name='Lasagna', qty='1'}] INFO | Handling Hot Meal INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Hot Meal', name='Rice Balls', qty='1'}] INFO | Handling Hot Meal INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Dessert', name='Blueberry Pie', qty='1'}] INFO | Handling Dessert
Seperti yang diharapkan, Unta mengarahkan pesan ke tujuan.
Pilihan Transfer Data
Pada contoh di atas, interaksi antar komponen bersifat sinkron dan dilakukan melalui memori aplikasi. Namun, ada lebih banyak cara untuk berkomunikasi saat kita berurusan dengan aplikasi terpisah yang tidak berbagi memori:
- Pertukaran berkas. Satu aplikasi menghasilkan file data bersama untuk yang lain untuk dikonsumsi. Di situlah semangat sekolah lama hidup. Metode komunikasi ini memiliki banyak konsekuensi: kurangnya transaksi dan konsistensi, kinerja yang buruk, dan koordinasi antar sistem yang terisolasi. Banyak pengembang akhirnya menulis solusi integrasi buatan sendiri untuk membuat proses lebih atau kurang dapat dikelola.
- Basis data umum. Minta aplikasi menyimpan data yang ingin mereka bagikan dalam skema umum dari satu database. Merancang skema terpadu dan menangani akses bersamaan ke tabel adalah tantangan paling menonjol dari pendekatan ini. Seperti halnya pertukaran file, hal ini mudah menjadi hambatan permanen.
- Panggilan API jarak jauh. Menyediakan antarmuka untuk memungkinkan aplikasi berinteraksi dengan aplikasi lain yang sedang berjalan, seperti pemanggilan metode biasa. Aplikasi berbagi fungsionalitas melalui pemanggilan API, tetapi aplikasi ini secara erat memasangkannya dalam proses.
- Perpesanan. Mintalah setiap aplikasi terhubung ke sistem perpesanan umum, dan bertukar data serta menjalankan perilaku secara asinkron menggunakan pesan. Baik pengirim maupun penerima tidak harus aktif dan berjalan pada saat yang sama agar pesan terkirim.
Ada lebih banyak cara untuk berinteraksi, tetapi kita harus ingat bahwa, secara umum, ada dua jenis interaksi: sinkron dan asinkron. Yang pertama seperti memanggil fungsi dalam kode Anda—alur eksekusi akan menunggu hingga dieksekusi dan mengembalikan nilai. Dengan pendekatan asinkron, data yang sama dikirim melalui antrian pesan perantara atau topik langganan. Panggilan fungsi jarak jauh asinkron dapat diimplementasikan sebagai EIP permintaan-balasan.
Pesan asinkron bukanlah obat mujarab; itu melibatkan batasan-batasan tertentu. Anda jarang melihat API perpesanan di web; layanan REST sinkron jauh lebih populer. Tetapi middleware perpesanan banyak digunakan di intranet perusahaan atau infrastruktur back-end sistem terdistribusi.
Menggunakan Antrian Pesan
Mari kita buat contoh kita asinkron. Sistem perangkat lunak yang mengelola antrian dan topik langganan disebut perantara pesan. Ini seperti RDBMS untuk tabel dan kolom. Antrian berfungsi sebagai integrasi point-to-point sementara topik adalah untuk komunikasi publish-subscribe dengan banyak penerima. Kami akan menggunakan Apache ActiveMQ sebagai perantara pesan JMS karena solid dan dapat disematkan.
Tambahkan ketergantungan berikut. Terkadang terlalu berlebihan untuk menambahkan activemq-all
, yang berisi semua stoples ActiveMQ, ke proyek, tetapi kita akan menjaga dependensi aplikasi kita tidak rumit.
<dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId> <version>5.15.2</version> </dependency>
Kemudian mulai broker secara terprogram. Di Spring Boot, kami mendapatkan konfigurasi otomatis untuk ini dengan mencolokkan dependensi spring-boot-starter-activemq
Maven.
Jalankan broker pesan baru dengan perintah berikut, yang hanya menentukan titik akhir konektor:
BrokerService broker = new BrokerService(); broker.addConnector("tcp://localhost:61616"); broker.start();
Dan tambahkan cuplikan konfigurasi berikut ke badan metode configure
:
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616"); this.getContext().addComponent("activemq", ActiveMQComponent.jmsComponent(connectionFactory));
Sekarang kita dapat memperbarui contoh sebelumnya menggunakan antrian pesan. Antrian akan dibuat secara otomatis pada pengiriman pesan.
public void configure() { errorHandler(defaultErrorHandler().maximumRedeliveries(0)); ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616"); this.getContext().addComponent("activemq", ActiveMQComponent.jmsComponent(connectionFactory)); from("file:orders?noop=true").routeId("main") .log("Incoming File: ${file:onlyname}") .unmarshal().json(JsonLibrary.Jackson, Order.class) // unmarshal JSON to Order class containing List<OrderItem> .split().simple("body.items") // split list to process one by one .to("log:inputOrderItem") .choice() .when().simple("${body.type} == 'Drink'") .to("activemq:queue:bar") .when().simple("${body.type} == 'Dessert'") .to("activemq:queue:dessertStation") .when().simple("${body.type} == 'Hot Meal'") .to("activemq:queue:hotMealStation") .when().simple("${body.type} == 'Cold Meal'") .to("activemq:queue:coldMealStation") .otherwise() .to("activemq:queue:others"); from("activemq:queue:bar").routeId("barAsync").log("Drinks"); from("activemq:queue:dessertStation").routeId("dessertAsync").log("Dessert"); from("activemq:queue:hotMealStation").routeId("hotMealAsync").log("Hot Meals"); from("activemq:queue:coldMealStation").routeId("coldMealAsync").log("Cold Meals"); from("activemq:queue:others").routeId("othersAsync").log("Others"); }
Baiklah, sekarang interaksi menjadi tidak sinkron. Calon konsumen data ini dapat mengaksesnya saat mereka siap. Ini adalah contoh kopling longgar, yang kami coba capai dalam arsitektur reaktif. Tidak tersedianya salah satu layanan tidak akan memblokir yang lain. Selain itu, konsumen dapat mengukur dan membaca dari antrian secara paralel. Antrian itu sendiri dapat diskalakan dan dipartisi. Antrian persisten dapat menyimpan data pada disk, menunggu untuk diproses, bahkan ketika semua peserta down. Akibatnya, sistem ini lebih toleran terhadap kesalahan.

Fakta yang mencengangkan adalah bahwa CERN menggunakan Apache Camel dan ActiveMQ untuk memantau sistem Large Hadron Collider (LHC). Ada juga tesis master yang menarik yang menjelaskan pilihan solusi middleware yang tepat untuk tugas ini. Jadi, seperti yang mereka katakan di keynote, “Tidak ada JMS—tidak ada fisika partikel!”
Pemantauan
Pada contoh sebelumnya, kami membuat saluran data antara dua layanan. Ini adalah titik potensial kegagalan tambahan dalam arsitektur, jadi kita harus menjaganya. Mari kita lihat fitur pemantauan apa yang disediakan Apache Camel. Pada dasarnya, ia memaparkan informasi statistik tentang rutenya melalui MBeans, yang dapat diakses oleh JMX. ActiveMQ mengekspos statistik antrian dengan cara yang sama.
Mari kita nyalakan server JMX di aplikasi, untuk mengaktifkannya dengan opsi baris perintah:
-Dorg.apache.camel.jmx.createRmiConnector=true -Dorg.apache.camel.jmx.mbeanObjectDomainName=org.apache.camel -Dorg.apache.camel.jmx.rmiConnector.registryPort=1099 -Dorg.apache.camel.jmx.serviceUrlPath=camel
Sekarang jalankan aplikasi sehingga rute telah melakukan tugasnya. Buka alat jconsole
standar dan sambungkan ke proses aplikasi. Hubungkan ke service:jmx:rmi:///jndi/rmi://localhost:1099/camel
. Buka domain org.apache.camel di pohon MBeans.
Kita dapat melihat bahwa segala sesuatu tentang perutean terkendali. Kami memiliki jumlah pesan dalam penerbangan, jumlah kesalahan, dan jumlah pesan dalam antrian. Informasi ini dapat disalurkan ke beberapa perangkat pemantauan dengan fungsionalitas yang kaya seperti Graphana atau Kibana. Anda dapat melakukan ini dengan menerapkan tumpukan ELK yang terkenal.
Ada juga konsol web yang dapat dipasang dan diperpanjang yang menyediakan UI untuk mengelola Camel, ActiveMQ, dan banyak lagi, yang disebut hawt.io.
Rute Pengujian
Apache Camel memiliki fungsionalitas yang cukup luas untuk menulis rute pengujian dengan komponen tiruan. Ini adalah alat yang ampuh, tetapi menulis rute terpisah hanya untuk pengujian adalah proses yang memakan waktu. Akan lebih efisien untuk menjalankan pengujian pada rute produksi tanpa memodifikasi jalur pipanya. Camel memiliki fitur ini dan dapat diimplementasikan menggunakan komponen AdviceWith.
Mari aktifkan logika pengujian dalam contoh kita dan jalankan pengujian sampel.
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-test</artifactId> <version>2.20.0</version> <scope>test</scope> </dependency>
Kelas tesnya adalah:
public class AsyncRouteTest extends CamelTestSupport { @Override protected RouteBuilder createRouteBuilder() throws Exception { return new AsyncRouteBuilder(); } @Before public void mockEndpoints() throws Exception { context.getRouteDefinition("main").adviceWith(context, new AdviceWithRouteBuilder() { @Override public void configure() throws Exception { // we substitute all actual queues with mock endpoints mockEndpointsAndSkip("activemq:queue:bar"); mockEndpointsAndSkip("activemq:queue:dessertStation"); mockEndpointsAndSkip("activemq:queue:hotMealStation"); mockEndpointsAndSkip("activemq:queue:coldMealStation"); mockEndpointsAndSkip("activemq:queue:others"); // and replace the route's source with test endpoint replaceFromWith("file://testInbox"); } }); } @Test public void testSyncInteraction() throws InterruptedException { String testJson = "{\"id\": 1, \"order\": [{\"id\": 1, \"name\": \"Americano\", \"type\": \"Drink\", \"qty\": \"1\"}, {\"id\": 2, \"name\": \"French Omelette\", \"type\": \"Hot Meal\", \"qty\": \"1\"}, {\"id\": 3, \"name\": \"Lasagna\", \"type\": \"Hot Meal\", \"qty\": \"1\"}, {\"id\": 4, \"name\": \"Rice Balls\", \"type\": \"Hot Meal\", \"qty\": \"1\"}, {\"id\": 5, \"name\": \"Blueberry Pie\", \"type\": \"Dessert\", \"qty\": \"1\"}]}"; // get mocked endpoint and set an expectation MockEndpoint mockEndpoint = getMockEndpoint("mock:activemq:queue:hotMealStation"); mockEndpoint.expectedMessageCount(3); // simulate putting file in the inbox folder template.sendBodyAndHeader("file://testInbox", testJson, Exchange.FILE_NAME, "test.json"); //checks that expectations were met assertMockEndpointsSatisfied(); } }
Sekarang jalankan tes untuk aplikasi dengan mvn test
. Kita dapat melihat bahwa rute kita telah berhasil dijalankan dengan saran pengujian. Tidak ada pesan yang melewati antrian yang sebenarnya dan tes telah lulus.
INFO | Route: main started and consuming from: file://testInbox <...> INFO | Incoming File: test.json <...> INFO | Asserting: mock://activemq:queue:hotMealStation is satisfied
Menggunakan Apache Camel dengan Cluster Kubernetes
Salah satu masalah integrasi saat ini adalah aplikasi tidak lagi statis. Dalam infrastruktur cloud, kami menangani layanan virtual yang berjalan di beberapa node secara bersamaan. Ini memungkinkan arsitektur layanan mikro dengan jaringan layanan kecil dan ringan yang berinteraksi di antara mereka sendiri. Layanan ini memiliki masa pakai yang tidak dapat diandalkan, dan kami harus menemukannya secara dinamis.
Merekatkan layanan cloud bersama-sama adalah tugas yang dapat diselesaikan dengan Apache Camel. Ini sangat menarik karena rasa EIP dan fakta bahwa Camel memiliki banyak adaptor dan mendukung berbagai protokol. Versi terbaru 2.18 menambahkan komponen ServiceCall, yang memperkenalkan fitur pemanggilan API dan penyelesaian alamatnya melalui mekanisme penemuan klaster. Saat ini, ia mendukung Consul, Kubernetes, Ribbon, dll. Beberapa contoh kode, di mana ServiceCall dikonfigurasi dengan Consul, dapat ditemukan dengan mudah. Kami akan menggunakan Kubernetes di sini karena ini adalah solusi pengelompokan favorit saya.
Skema integrasi akan menjadi sebagai berikut:
Layanan Order
dan layanan Inventory
akan menjadi beberapa aplikasi Spring Boot sepele yang mengembalikan data statis. Kami tidak terikat pada tumpukan teknologi tertentu di sini. Layanan ini menghasilkan data yang ingin kami proses.
Pengontrol Layanan Pesanan:
@RestController public class OrderController { private final OrderStorage orderStorage; @Autowired public OrderController(OrderStorage orderStorage) { this.orderStorage = orderStorage; } @RequestMapping("/info") public String info() { return "Order Service UU/orders") public List<Order> getAll() { return orderStorage.getAll(); } @RequestMapping("/orders/{id}") public Order getOne(@PathVariable Integer id) { return orderStorage.getOne(id); } }
Ini menghasilkan data dalam format:
[{"id":1,"items":[2,3,4]},{"id":2,"items":[5,3]}]
Pengontrol layanan Inventory
benar-benar mirip dengan layanan Order
:
@RestController public class InventoryController { private final InventoryStorage inventoryStorage; @Autowired public InventoryController(InventoryStorage inventoryStorage) { this.inventoryStorage = inventoryStorage; } @RequestMapping("/info") public String info() { return "Inventory Service UU/items") public List<InventoryItem> getAll() { return inventoryStorage.getAll(); } @RequestMapping("/items/{id}") public InventoryItem getOne(@PathVariable Integer id) { return inventoryStorage.getOne(id); } }
InventoryStorage
adalah repositori generik yang menyimpan data. Dalam contoh ini, ia mengembalikan objek statis yang telah ditentukan sebelumnya, yang disusun ke format berikut.
[{"id":1,"name":"Laptop","description":"Up to 12-hours battery life","price":499.9},{"id":2,"name":"Monitor","description":"27-inch, response time: 7ms","price":200.0},{"id":3,"name":"Headphones","description":"Soft leather ear-cups","price":29.9},{"id":4,"name":"Mouse","description":"Designed for comfort and portability","price":19.0},{"id":5,"name":"Keyboard","description":"Layout: US","price":10.5}]
Mari kita tulis rute gateway yang menghubungkannya, tetapi tanpa ServiceCall pada langkah ini:
rest("/orders") .get("/").description("Get all orders with details").outType(TestResponse.class) .route() .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .to("http4://localhost:8082/orders?bridgeEndpoint=true") .unmarshal(formatOrder) .enrich("direct:enrichFromInventory", new OrderAggregationStrategy()) .to("log:result") .endRest(); from("direct:enrichFromInventory") .transform().simple("${null}") .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .to("http4://localhost:8081/items?bridgeEndpoint=true") .unmarshal(formatInventory);
Sekarang bayangkan bahwa setiap layanan bukan lagi instans tertentu, melainkan awan instans yang beroperasi sebagai satu. Kami akan menggunakan Minikube untuk mencoba cluster Kubernetes secara lokal.
Konfigurasikan rute jaringan untuk melihat node Kubernetes secara lokal (contoh yang diberikan adalah untuk lingkungan Mac/Linux):
# remove existing routes sudo route -n delete 10/24 > /dev/null 2>&1 # add routes sudo route -n add 10.0.0.0/24 $(minikube ip) # 172.17.0.0/16 ip range is used by docker in minikube sudo route -n add 172.17.0.0/16 $(minikube ip) ifconfig 'bridge100' | grep member | awk '{print $2}' # use interface name from the output of the previous command # needed for xhyve driver, which I'm using for testing sudo ifconfig bridge100 -hostfilter en5
Bungkus layanan dalam wadah Docker dengan konfigurasi Dockerfile seperti ini:
FROM openjdk:8-jdk-alpine VOLUME /tmp ADD target/order-srv-1.0-SNAPSHOT.jar app.jar ADD target/lib lib ENV JAVA_OPTS="" ENTRYPOINT exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar
Bangun dan dorong image layanan ke registri Docker. Sekarang jalankan node di cluster Kubernetes lokal.
Konfigurasi penerapan Kubernetes.yaml:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: inventory spec: replicas: 3 selector: matchLabels: app: inventory template: metadata: labels: app: inventory spec: containers: - name: inventory image: inventory-srv:latest imagePullPolicy: Never ports: - containerPort: 8081
Paparkan penerapan ini sebagai layanan dalam klaster:
kubectl expose deployment order-srv --type=NodePort kubectl expose deployment inventory-srv --type=NodePort
Sekarang kita dapat memeriksa apakah permintaan dilayani oleh node yang dipilih secara acak dari cluster. Jalankan curl -X http://192.168.99.100:30517/info
secara berurutan beberapa kali untuk mengakses minikube NodePort untuk layanan yang terbuka (menggunakan host dan port Anda). Dalam output, kami melihat bahwa kami telah mencapai keseimbangan permintaan.
Inventory Service UUID = 22f8ca6b-f56b-4984-927b-cbf9fcf81da5 Inventory Service UUID = b7a4d326-1e76-4051-a0a6-1016394fafda Inventory Service UUID = b7a4d326-1e76-4051-a0a6-1016394fafda Inventory Service UUID = 22f8ca6b-f56b-4984-927b-cbf9fcf81da5 Inventory Service UUID = 50323ddb-3ace-4424-820a-6b4e85775af4
Tambahkan camel-kubernetes
dan camel-netty4-http
ke pom.xml
proyek. Kemudian konfigurasikan komponen ServiceCall untuk menggunakan penemuan node master Kubernetes yang dibagikan untuk semua panggilan layanan di antara definisi rute:
KubernetesConfiguration kubernetesConfiguration = new KubernetesConfiguration(); kubernetesConfiguration.setMasterUrl("https://192.168.64.2:8443"); kubernetesConfiguration.setClientCertFile("/Users/antongoncharov/.minikube/client.crt"); kubernetesConfiguration.setClientKeyFile("/Users/antongoncharov/.minikube/client.key"); kubernetesConfiguration.setNamespace("default”); ServiceCallConfigurationDefinition config = new ServiceCallConfigurationDefinition(); config.setServiceDiscovery(new KubernetesClientServiceDiscovery(kubernetesConfiguration)); context.setServiceCallConfiguration(config);
ServiceCall EIP melengkapi Spring Boot dengan baik. Sebagian besar opsi dapat dikonfigurasi langsung di file application.properties
.
Berdayakan rute Camel dengan komponen ServiceCall:
rest("/orders") .get("/").description("Get all orders with details").outType(TestResponse.class) .route() .hystrix() .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .serviceCall("customer-srv","http4:customer-deployment?bridgeEndpoint=true") .unmarshal(formatOrder) .enrich("direct:enrichFromInventory", new OrderAggregationStrategy()) .to("log:result") .endRest(); from("direct:enrichFromInventory") .transform().simple("${null}") .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .serviceCall("order-srv","http4:order-srv?bridgeEndpoint=true") .unmarshal(formatInventory);
Kami juga mengaktifkan Pemutus Arus di rute. Ini adalah kait integrasi yang memungkinkan jeda panggilan sistem jarak jauh jika terjadi kesalahan pengiriman atau penerima tidak tersedia. Ini dirancang untuk menghindari kegagalan sistem kaskade. Komponen Hystrix membantu mencapai hal ini dengan menerapkan pola Pemutus Sirkuit.
Mari kita jalankan dan kirim permintaan pengujian; kami akan mendapatkan respons yang dikumpulkan dari kedua layanan.
[{"id":1,"items":[{"id":2,"name":"Monitor","description":"27-inch, response time: 7ms","price":200.0},{"id":3,"name":"Headphones","description":"Soft leather ear-cups","price":29.9},{"id":4,"name":"Mouse","description":"Designed for comfort and portability","price":19.0}]},{"id":2,"items":[{"id":5,"name":"Keyboard","description":"Layout: US","price":10.5},{"id":3,"name":"Headphones","description":"Soft leather ear-cups","price":29.9}]}]
Hasilnya seperti yang diharapkan.
Kasus Penggunaan Lainnya
Saya menunjukkan bagaimana Apache Camel dapat mengintegrasikan layanan mikro dalam sebuah cluster. Apa kegunaan lain dari kerangka kerja ini? Secara umum, ini berguna di tempat mana pun di mana perutean berbasis aturan dapat menjadi solusi. For instance, Apache Camel can be a middleware for the Internet of Things with the Eclipse Kura adapter. It can handle monitoring by ferrying log signals from various components and services, like in the CERN system. It can also be an integration framework for enterprise SOA or be a pipeline for batch data processing, although it doesn't compete well with Apache Spark in this area.
Kesimpulan
You can see that systems integration isn't an easy process. We're lucky because a lot of experience has been gathered. It's important to apply it correctly to build flexible and fault-tolerant solutions.
To ensure correct application, I recommend having a checklist of important integration aspects. Must-have items include:
- Is there a separate integration layer?
- Are there tests for integration?
- Do we know the expected peak data intensity?
- Do we know the expected data delivery time?
- Does message correlation matter? What if a sequence breaks?
- Should we do it in a synchronous or asynchronous way?
- Where do formats and routing rules change more frequently?
- Do we have ways to monitor the process?
In this article, we tried Apache Camel, a lightweight integration framework, which helps save time and effort when solving integration problems. As we showed, it can serve as a tool, supporting the relevant microservice architecture by taking full responsibility for data exchange between microservices.
If you're interested in learning more about Apache Camel, I highly recommend the book “Camel in Action” by the framework's creator, Claus Ibsen. Official documentation is available at camel.apache.org.