Yazılım Entegrasyonunu Kolaylaştırın: Bir Apache Camel Eğitimi
Yayınlanan: 2022-03-11Yazılım nadiren bir bilgi boşluğunda bulunur. En azından, biz yazılım mühendislerinin geliştirdiğimiz uygulamaların çoğu için yapabileceğimiz varsayım budur.
Herhangi bir ölçekte, her yazılım parçası, şu veya bu şekilde, çeşitli nedenlerle başka bir yazılımla iletişim kurar: bir yerden referans verileri almak, izleme sinyalleri göndermek, dağıtılmış bir sistemin parçası olurken diğer hizmetlerle temas halinde olmak. sistem ve daha fazlası.
Bu öğreticide, büyük yazılımları entegre etmenin en büyük zorluklarından bazılarının neler olduğunu ve Apache Camel'in bunları nasıl kolaylıkla çözdüğünü öğreneceksiniz.
Sorun: Sistem Entegrasyonu için Mimari Tasarım
Yazılım mühendisliği hayatınızda en az bir kez aşağıdakileri yapmış olabilirsiniz:
- Veri göndermeyi başlatması gereken iş mantığınızın bir parçasını tanımlayın.
- Aynı uygulama katmanında, veri dönüşümlerini alıcının beklediğine uygun olarak yazın.
- Verileri ağ üzerinden aktarmaya ve yönlendirmeye uygun bir yapıya sarın.
- Uygun bir sürücü veya bir istemci SDK'sı kullanarak bir hedef uygulamaya bağlantı açın.
- Verileri gönderin ve yanıtı işleyin.
Bu neden kötü bir eylem çizgisi?
Bu türden yalnızca birkaç bağlantınız olsa da, yönetilebilir durumda kalır. Sistemler arasında artan sayıda ilişki ile uygulamanın iş mantığı, verileri uyarlama, iki sistem arasındaki teknolojik farklılıkları telafi etme ve SOAP, REST veya daha egzotik isteklerle verileri harici sisteme aktarma ile ilgili entegrasyon mantığı ile karışır. .
Birkaç uygulamayı entegre ediyor olsaydınız, bu tür bir koddaki bağımlılıkların bütün resmini geri almak inanılmaz derecede zor olurdu: Veriler nerede üretilir ve hangi hizmetler onu tüketir? Önyüklemek için entegrasyon mantığının çoğaltıldığı birçok yeriniz olacak.
Böyle bir yaklaşımla, görev teknik olarak tamamlanmış olsa da, entegrasyonun sürdürülebilirliği ve ölçeklenebilirliği ile ilgili büyük sorunlarla karşı karşıya kalıyoruz. Bu sistemdeki veri akışlarının hızlı bir şekilde yeniden düzenlenmesi neredeyse imkansızdır ve izleme eksikliği, devre kesme, zahmetli veri kurtarma vb. gibi daha derin sorunlardan bahsetmiyorum bile.
Bunların hepsi, yazılımı oldukça büyük bir kuruluş kapsamında entegre ederken özellikle önemlidir. Kurumsal entegrasyonla uğraşmak, çok çeşitli platformlarda çalışan ve farklı konumlarda bulunan bir dizi uygulama ile çalışmak anlamına gelir. Böyle bir yazılım ortamında veri alışverişi oldukça zorludur. Endüstrinin yüksek güvenlik standartlarını karşılamalı ve veri aktarımı için güvenilir bir yol sağlamalıdır. Kurumsal bir ortamda sistem entegrasyonu, ayrı ve ayrıntılı bir mimari tasarım gerektirir.
Bu makale, size yazılım entegrasyonunda karşılaşılan benzersiz zorlukları tanıtacak ve entegrasyon görevleri için deneyime dayalı bazı çözümler sağlayacaktır. Bir entegrasyon geliştiricisinin baş ağrısının en kötü kısımlarını hafifletebilecek kullanışlı bir çerçeve olan Apache Camel'e aşina olacağız. Camel'in Kubernetes tarafından desteklenen bir mikro hizmet kümesinde iletişim kurulmasına nasıl yardımcı olabileceğinin bir örneğini takip edeceğiz.
Entegrasyon Zorlukları
Sorunu çözmek için yaygın olarak kullanılan bir yaklaşım, uygulamanızdaki bir tümleştirme katmanını ayrıştırmaktır. Aynı uygulama içinde veya bağımsız olarak çalışan özel bir yazılım parçası olarak var olabilir - ikinci durumda ara yazılım olarak adlandırılır.
Ara yazılımı geliştirirken ve desteklerken genellikle hangi sorunlarla karşılaşıyorsunuz? Genel olarak, aşağıdaki temel öğelere sahipsiniz:
- Tüm veri kanalları bir dereceye kadar güvenilmezdir. Bu güvenilmezlikten kaynaklanan sorunlar, veri yoğunluğu düşük veya orta düzeydeyken ortaya çıkmayabilir. Uygulama belleğinden daha düşük önbelleklere ve altındaki donanıma kadar her depolama düzeyi, olası arızalara tabidir. Bazı nadir hatalar, yalnızca büyük hacimli verilerle ortaya çıkar. Olgun, üretime hazır satıcı ürünlerinde bile veri kaybıyla ilgili çözülmemiş hata izleyici sorunları vardır. Bir ara katman yazılımı sistemi, sizi bu veri kayıpları hakkında bilgilendirebilmeli ve mesajın zamanında yeniden teslim edilmesini sağlayabilmelidir.
- Uygulamalar farklı protokoller ve veri biçimleri kullanır. Bu, bir entegrasyon sisteminin diğer katılımcılara veri dönüşümleri ve adaptörler için bir perde olduğu ve çeşitli teknolojileri kullandığı anlamına gelir. Bunlar, düz REST API çağrılarını içerebilir, ancak bir kuyruk aracısına erişim, FTP üzerinden CSV siparişleri gönderme veya bir veritabanı tablosuna toplu veri çekme olabilir. Bu uzun bir liste ve asla kısalmayacak.
- Veri formatlarında ve yönlendirme kurallarında değişiklikler kaçınılmazdır. Veri yapısını değiştiren bir uygulamanın geliştirme sürecindeki her adım, genellikle entegrasyon veri formatlarında ve dönüşümlerinde değişikliklere yol açar. Bazen, yeniden organize edilmiş kurumsal veri akışları ile altyapı değişiklikleri gereklidir. Örneğin, bu değişiklikler, şirket genelinde tüm ana veri girişlerini işlemesi gereken tek bir referans veri doğrulama noktası tanıtıldığında meydana gelebilir.
N
sistemleriyle, aralarında maksimum neredeyseN^2
bağlantıya sahip olabiliriz, bu nedenle değişikliklerin uygulanması gereken yerlerin sayısı oldukça hızlı artar. Bir çığ gibi olacak. Sürdürülebilirliği sürdürmek için, bir ara yazılım katmanı, çok yönlü yönlendirme ve veri dönüşümü ile bağımlılıkların net bir resmini sağlamalıdır.
Entegrasyon tasarlanırken ve en uygun ara katman yazılımı çözümünü seçerken bu fikirler akılda tutulmalıdır. Bunu ele almanın olası yollarından biri, bir kurumsal hizmet veri yolundan (ESB) yararlanmaktır. Ancak büyük satıcılar tarafından sağlanan ESB'ler genellikle çok ağırdır ve genellikle değerlerinden daha fazla sorun yaratırlar: Bir ESB ile hızlı bir başlangıç yapmak neredeyse imkansızdır, oldukça dik bir öğrenme eğrisine sahiptir ve esnekliği uzun bir liste için feda edilir. özellikler ve yerleşik araçlar. Benim düşünceme göre, hafif açık kaynaklı entegrasyon çözümleri çok daha üstündür; daha esnektirler, buluta dağıtılması kolaydır ve ölçeklendirilmesi kolaydır.
Yazılım entegrasyonu yapmak kolay değildir. Bugün, biz mikro hizmet mimarileri oluştururken ve küçük hizmet yığınlarıyla uğraşırken, bunların ne kadar verimli iletişim kurmaları gerektiği konusunda da yüksek beklentilerimiz var.
Kurumsal Entegrasyon Modelleri
Tahmin edilebileceği gibi, genel olarak yazılım geliştirme gibi, veri yönlendirme ve dönüştürmenin geliştirilmesi de tekrarlayan işlemleri içerir. Bu alandaki deneyimler, uzun süredir entegrasyon sorunlarıyla ilgilenen profesyoneller tarafından özetlenmiş ve sistematik hale getirilmiştir. Sonuç olarak, veri akışlarını tasarlamak için kullanılan kurumsal entegrasyon kalıpları adı verilen bir dizi ayıklanmış şablon vardır. Bu entegrasyon yöntemleri, Gregor Hophe ve Bobby Wolfe tarafından aynı adlı kitapta, yani Gang of Four'un önemli kitabına çok benzeyen ancak yapıştırma yazılımı alanında anlatılmıştır.
Bir örnek vermek gerekirse, normalleştirici deseni, farklı veri biçimlerine sahip semantik olarak eşit mesajları tek bir kurallı modele eşleyen bir bileşen sunar veya toplayıcı, bir mesaj dizisini tek bir mesajda birleştiren bir EIP'dir.
Mimari sorunları çözmek için kullanılan teknolojiden bağımsız soyutlamalar oluşturdukları için, EIP'ler, kod düzeyine girmeyen ancak veri akışlarını yeterli ayrıntıda açıklayan bir mimari tasarımın yazılmasına yardımcı olur. Entegrasyon rotalarını tanımlamaya yönelik bu tür notasyon, tasarımı kısa ve öz hale getirmekle kalmaz, aynı zamanda çeşitli iş alanlarından ekip üyeleriyle bir entegrasyon görevinin çözülmesi bağlamında oldukça önemli olan ortak bir terminoloji ve ortak bir dil de belirler.
Apaçi Devesi ile Tanışın
Birkaç yıl önce, geniş çapta dağıtılmış konumlardaki mağazalarla büyük bir bakkal perakende ağında kurumsal bir entegrasyon kuruyordum. Bakımı aşırı zahmetli olduğu ortaya çıkan tescilli bir ESB çözümüyle başladım. Daha sonra ekibimiz Apache Camel ile karşılaştı ve bazı “kanıtlama” çalışmaları yaptıktan sonra Camel rotalarındaki tüm veri akışlarımızı hızla yeniden yazdık.
Apache Camel, aşina olduğum EIP'lerin listesini uygulayan mesaj odaklı bir ara katman yazılımı çerçevesi olan bir "arabuluculuk yönlendiricisi" olarak tanımlanabilir. Bu kalıpları kullanır, tüm yaygın aktarım protokollerini destekler ve çok sayıda kullanışlı bağdaştırıcıya sahiptir. Camel, kendi kodunuzu yazmanıza gerek kalmadan bir dizi entegrasyon rutininin işlenmesini sağlar.
Bunun dışında, aşağıdaki Apache Camel özelliklerini seçerdim:
- Entegrasyon yolları, bloklardan oluşan boru hatları olarak yazılır. Veri akışlarının izlenmesine yardımcı olmak için tamamen şeffaf bir resim oluşturur.
- Camel'in birçok popüler API için adaptörleri vardır. Örneğin, Apache Kafka'dan veri almak, AWS EC2 örneklerini izlemek, Salesforce ile entegrasyon - tüm bu görevler kutudan çıkan bileşenler kullanılarak çözülebilir.
Apache Camel rotaları Java veya Scala DSL ile yazılabilir. (Bir XML yapılandırması da mevcuttur, ancak çok ayrıntılı hale gelir ve daha kötü hata ayıklama yeteneklerine sahiptir.) İletişim hizmetlerinin teknoloji yığını üzerinde kısıtlamalar getirmez, ancak Java veya Scala'da yazarsanız, bunun yerine Camel'i bir uygulamaya gömebilirsiniz. bağımsız olarak çalıştırmaktır.
Camel tarafından kullanılan yönlendirme notasyonu, aşağıdaki basit sözde kodla açıklanabilir:
from(Source) .transform(Transformer) .to(Destination)
Source
, Transformer
ve Destination
, URI'leri tarafından uygulama bileşenlerine atıfta bulunan uç noktalardır.
Camel'in daha önce bahsettiğim entegrasyon problemlerini çözmesini sağlayan nedir? Bir bakalım. İlk olarak, yönlendirme ve dönüştürme mantığı artık yalnızca özel bir Apache Camel yapılandırmasında yaşıyor. İkinci olarak, EIP'lerin kullanımıyla bağlantılı olarak özlü ve doğal DSL aracılığıyla, sistemler arasındaki bağımlılıkların bir resmi ortaya çıkıyor. Anlaşılabilir soyutlamalardan yapılmıştır ve yönlendirme mantığı kolayca ayarlanabilir. Ve son olarak, uygun bağdaştırıcıların zaten dahil edilmiş olması muhtemel olduğundan, yığınlarca dönüştürme kodu yazmamız gerekmiyor.
Eklemeliyim, Apache Camel olgun bir çerçevedir ve düzenli güncellemeler alır. Büyük bir topluluğa ve kayda değer bir birikimli bilgi tabanına sahiptir.
Kendi dezavantajları var. Camel, karmaşık bir entegrasyon paketi olarak alınmamalıdır. Bu, iş süreci yönetimi araçları veya etkinlik izleyicileri gibi üst düzey özelliklere sahip olmayan bir araç kutusudur, ancak bu tür yazılımları oluşturmak için kullanılabilir.
Alternatif sistemler, örneğin Spring Integration veya Mule ESB olabilir. Spring Integration için, hafif olduğu düşünülse de, deneyimlerime göre, onu bir araya getirmek ve çok sayıda XML yapılandırma dosyası yazmak beklenmedik şekilde karmaşık olabilir ve kolay bir çıkış yolu değildir. Mule ESB, sağlam ve çok işlevsel bir araç setidir, ancak adından da anlaşılacağı gibi, bir kurumsal servis veriyoludur, bu nedenle farklı bir ağırlık kategorisine aittir. Mule, zengin özelliklere sahip Apache Camel tabanlı benzer bir ürün olan Fuse ESB ile karşılaştırılabilir. Benim için, yapıştırma hizmetleri için Apache Camel'i kullanmak bugün hiç de kolay değil. Kullanımı kolaydır ve neyin nereye gittiğine dair net bir açıklama üretir; aynı zamanda karmaşık entegrasyonlar oluşturmak için yeterince işlevseldir.
Örnek Rota Yazma
Kodu yazmaya başlayalım. İletileri tek bir kaynaktan bir alıcı listesine yönlendiren eşzamanlı bir veri akışından başlayacağız. Yönlendirme kuralları Java DSL'de yazılacaktır.
Projeyi oluşturmak için Maven'i kullanacağız. Öncelikle pom.xml
aşağıdaki bağımlılığı ekleyin:
<dependencies> ... <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> <version>2.20.0</version> </dependency> </dependencies>
Alternatif olarak, uygulama camel-archetype-java
arketipinin üzerine inşa edilebilir.
Camel rota tanımları RouteBuilder.configure
yönteminde bildirilir.
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"); }
Bu tanımda, JSON dosyasından kayıtları getiren, bunları öğelere bölen ve mesaj içeriğine göre bir dizi işleyiciye yönlendiren bir rota oluşturuyoruz.
Hazırlanan test verileri üzerinde çalıştıralım. Çıktıyı alacağız:
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
Beklendiği gibi, Camel mesajları hedeflere yönlendirdi.
Veri Aktarımı Seçenekleri
Yukarıdaki örnekte, bileşenler arasındaki etkileşim eşzamanlıdır ve uygulama belleği aracılığıyla gerçekleştirilir. Ancak, belleği paylaşmayan ayrı uygulamalarla uğraştığımızda iletişim kurmanın daha birçok yolu vardır:
- Dosya değişimi. Bir uygulama, diğerinin kullanması için paylaşılan veri dosyaları üretir. Eski okul ruhunun yaşadığı yer. Bu iletişim yönteminin çok sayıda sonucu vardır: işlemler ve tutarlılık eksikliği, düşük performans ve sistemler arasında izole koordinasyon. Pek çok geliştirici, süreci az çok yönetilebilir hale getirmek için ev yapımı entegrasyon çözümleri yazdı.
- Ortak veritabanı. Uygulamaların paylaşmak istedikleri verileri tek bir veritabanının ortak bir şemasında saklamasını sağlayın. Birleşik şema tasarlamak ve tablolara eşzamanlı erişimi yönetmek, bu yaklaşımın en belirgin zorluklarıdır. Dosya alışverişinde olduğu gibi, bunun kalıcı bir darboğaz haline gelmesi kolaydır.
- Uzak API çağrısı. Tipik bir yöntem çağrısı gibi, bir uygulamanın çalışan başka bir uygulamayla etkileşime girmesine izin verecek bir arabirim sağlayın. Uygulamalar, API çağrıları aracılığıyla işlevselliği paylaşır, ancak süreçte bunları sıkı bir şekilde birleştirir.
- Mesajlaşma. Her uygulamanın ortak bir mesajlaşma sistemine bağlanmasını sağlayın ve mesajları kullanarak eşzamansız olarak veri alışverişinde bulunun ve davranış çağırın. Mesajın teslim edilmesi için ne göndericinin ne de alıcının aynı anda çalışır durumda olması gerekmez.
Etkileşim kurmanın daha fazla yolu vardır, ancak genel olarak iki tür etkileşim olduğunu unutmamalıyız: eşzamanlı ve eşzamansız. İlki, kodunuzdaki bir işlevi çağırmak gibidir—yürütme akışı, yürütülene ve bir değer döndürene kadar bekleyecektir. Eşzamansız bir yaklaşımla, aynı veriler bir ara mesaj kuyruğu veya abonelik konusu aracılığıyla gönderilir. Eşzamansız bir uzak işlev çağrısı, istek-yanıt EIP'si olarak uygulanabilir.
Asenkron mesajlaşma her derde deva değildir; belirli kısıtlamalar içerir. Web'de mesajlaşma API'lerini nadiren görürsünüz; senkronize REST hizmetleri çok daha popüler. Ancak mesajlaşma ara yazılımı, kurumsal intranet veya dağıtılmış sistem arka uç altyapısında yaygın olarak kullanılmaktadır.
Mesaj Kuyruklarını Kullanma
Örneğimizi asenkron yapalım. Kuyrukları ve abonelik konularını yöneten bir yazılım sistemine mesaj aracısı denir. Tablolar ve sütunlar için bir RDBMS gibidir. Kuyruklar noktadan noktaya entegrasyon işlevi görürken, konular birçok alıcıyla yayınla-abonelik iletişimi içindir. Sağlam ve gömülebilir olduğu için Apache ActiveMQ'yu JMS mesaj aracısı olarak kullanacağız.
Aşağıdaki bağımlılığı ekleyin. Bazen projeye tüm ActiveMQ kavanozlarını içeren activemq-all
eklemek aşırı olabilir, ancak uygulamamızın bağımlılıklarını basit tutacağız.
<dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId> <version>5.15.2</version> </dependency>
Ardından aracıyı programlı olarak başlatın. Spring Boot'da, spring-boot-starter-activemq
Maven bağımlılığını takarak bunun için bir otomatik yapılandırma elde ederiz.
Yalnızca bağlayıcının uç noktasını belirterek aşağıdaki komutlarla yeni bir ileti aracısı çalıştırın:
BrokerService broker = new BrokerService(); broker.addConnector("tcp://localhost:61616"); broker.start();
Aşağıdaki yapılandırma parçacığını configure
yöntemi gövdesine ekleyin:
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616"); this.getContext().addComponent("activemq", ActiveMQComponent.jmsComponent(connectionFactory));
Şimdi mesaj kuyruklarını kullanarak önceki örneği güncelleyebiliriz. Kuyruklar, mesaj tesliminde otomatik olarak oluşturulacaktır.
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"); }
Pekala, şimdi etkileşim eşzamansız hale geldi. Bu verilerin potansiyel tüketicileri, hazır olduklarında verilere erişebilir. Bu, reaktif bir mimaride elde etmeye çalıştığımız bir gevşek bağlantı örneğidir. Hizmetlerden birinin kullanılamaması diğerlerini engellemez. Ayrıca, bir tüketici kuyruktan paralel olarak ölçeklendirebilir ve okuyabilir. Kuyruğun kendisi ölçeklenebilir ve bölümlenebilir. Kalıcı kuyruklar, tüm katılımcılar düştüğünde bile işlenmeyi bekleyen verileri diskte saklayabilir. Sonuç olarak, bu sistem hataya daha dayanıklıdır.

Şaşırtıcı bir gerçek, CERN'in Büyük Hadron Çarpıştırıcısı (LHC) sistemlerini izlemek için Apache Camel ve ActiveMQ kullanmasıdır. Bu görev için uygun bir ara katman yazılımı çözümünün seçimini açıklayan ilginç bir yüksek lisans tezi de var. Bu nedenle, açılış konuşmasında dedikleri gibi, "JMS yok - parçacık fiziği yok!"
izleme
Önceki örnekte, iki servis arasında veri kanalı oluşturduk. Bir mimaride ek bir potansiyel başarısızlık noktasıdır, bu yüzden ona bakmak zorundayız. Apache Camel'in sağladığı izleme özelliklerine bir göz atalım. Temel olarak, JMX tarafından erişilebilen MBeans aracılığıyla rotaları hakkında istatistiksel bilgiler sunar. ActiveMQ, sıra istatistiklerini aynı şekilde gösterir.
Komut satırı seçenekleriyle çalışmasını sağlamak için uygulamada JMX sunucusunu açalım:
-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
Şimdi uygulamayı çalıştırın, böylece rota işini yaptı. Standart jconsole
aracını açın ve uygulama sürecine bağlanın. URL service:jmx:rmi:///jndi/rmi://localhost:1099/camel
. MBeans ağacında org.apache.camel etki alanına gidin.
Yönlendirme ile ilgili her şeyin kontrol altında olduğunu görebiliriz. Sıralarda uçuş sırasındaki mesaj sayısı, hata sayısı ve mesaj sayısı var. Bu bilgiler, Graphana veya Kibana gibi zengin işlevselliğe sahip bazı izleme araç setlerine aktarılabilir. Bunu, iyi bilinen ELK yığınını uygulayarak yapabilirsiniz.
Ayrıca Camel, ActiveMQ ve hawt.io adı verilen daha fazlasını yönetmek için bir UI sağlayan takılabilir ve genişletilebilir bir web konsolu da vardır.
Test Rotaları
Apache Camel, sahte bileşenlerle test rotaları yazmak için oldukça geniş bir işlevselliğe sahiptir. Güçlü bir araçtır, ancak yalnızca test için ayrı yollar yazmak zaman alan bir süreçtir. Boru hattını değiştirmeden üretim yolları üzerinde testler yapmak daha verimli olacaktır. Camel bu özelliğe sahiptir ve AdviceWith bileşeni kullanılarak uygulanabilir.
Örneğimizde test mantığını etkinleştirelim ve örnek bir test çalıştıralım.
<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>
Test sınıfı:
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(); } }
Şimdi uygulama için testleri mvn test
ile çalıştırın. Test tavsiyesi ile rotamızın başarıyla yürütüldüğünü görebiliriz. Gerçek kuyruklardan herhangi bir mesaj geçmedi ve testler geçti.
INFO | Route: main started and consuming from: file://testInbox <...> INFO | Incoming File: test.json <...> INFO | Asserting: mock://activemq:queue:hotMealStation is satisfied
Apache Camel'i Kubernetes Cluster ile Kullanma
Günümüzün entegrasyon sorunlarından biri, uygulamaların artık statik olmamasıdır. Bir bulut altyapısında, aynı anda birden çok düğümde çalışan sanal hizmetlerle ilgileniyoruz. Mikro hizmet mimarisini, kendi aralarında etkileşime giren küçük, hafif hizmet ağıyla mümkün kılar. Bu hizmetlerin güvenilmez bir ömrü vardır ve bunları dinamik olarak keşfetmemiz gerekir.
Bulut hizmetlerini birbirine yapıştırmak, Apache Camel ile çözülebilecek bir görevdir. EIP özelliği ve Camel'in çok sayıda bağdaştırıcıya sahip olması ve çok çeşitli protokolleri desteklemesi nedeniyle özellikle ilgi çekicidir. Son sürüm 2.18, bir API çağırma ve adresini küme bulma mekanizmaları aracılığıyla çözme özelliğini tanıtan ServiceCall bileşenini ekler. Şu anda Consul, Kubernetes, Ribbon vb. destekler. ServiceCall'ın Consul ile yapılandırıldığı bazı kod örnekleri kolayca bulunabilir. Kubernetes'i burada kullanacağız çünkü bu benim favori kümeleme çözümüm.
Entegrasyon şeması aşağıdaki gibi olacaktır:
Order
hizmeti ve Inventory
hizmeti, statik veri döndüren birkaç önemsiz Spring Boot uygulaması olacaktır. Burada belirli bir teknoloji yığınına bağlı değiliz. Bu hizmetler, işlemek istediğimiz verileri üretiyor.
Servis kontrolörü sipariş edin:
@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); } }
Şu biçimde veri üretir:
[{"id":1,"items":[2,3,4]},{"id":2,"items":[5,3]}]
Inventory
hizmeti denetleyicisi, Order
hizmetininkine kesinlikle benzer:
@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
, verileri tutan genel bir depodur. Bu örnekte, aşağıdaki biçimde sıralanan statik önceden tanımlanmış nesneleri döndürür.
[{"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}]
Bunları birbirine bağlayan bir ağ geçidi yolu yazalım, ancak bu adımda ServiceCall olmadan:
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);
Şimdi her hizmetin artık belirli bir örnek değil, tek bir örnek olarak çalışan bir bulut bulutu olduğunu hayal edin. Kubernetes kümesini yerel olarak denemek için Minikube kullanacağız.
Kubernetes düğümlerini yerel olarak görmek için ağ yollarını yapılandırın (verilen örnek bir Mac/Linux ortamı içindir):
# 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
Hizmetleri aşağıdaki gibi bir Dockerfile yapılandırmasıyla Docker kapsayıcılarına sarın:
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
Hizmet görüntülerini oluşturun ve Docker kayıt defterine gönderin. Şimdi düğümleri yerel Kubernetes kümesinde çalıştırın.
Kubernetes.yaml dağıtım yapılandırması:
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
Bu dağıtımları kümede hizmet olarak kullanıma açın:
kubectl expose deployment order-srv --type=NodePort kubectl expose deployment inventory-srv --type=NodePort
Artık isteklerin kümeden rastgele seçilen düğümler tarafından sunulup sunulmadığını kontrol edebiliriz. Açıkta kalan hizmet için minikube NodePort'a erişmek için curl -X http://192.168.99.100:30517/info
sırayla birkaç kez çalıştırın (ana makinenizi ve bağlantı noktanızı kullanarak). Çıktıda istek dengelemeye ulaştığımızı görüyoruz.
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
camel-kubernetes
ve camel-netty4-http
bağımlılıkları pom.xml
. Ardından ServiceCall bileşenini, rota tanımları arasındaki tüm hizmet çağrıları için paylaşılan Kubernetes ana düğüm keşfini kullanacak şekilde yapılandırın:
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, Spring Boot'u iyi bir şekilde tamamlar. Seçeneklerin çoğu doğrudan application.properties
dosyasında yapılandırılabilir.
ServiceCall bileşeniyle Camel rotasını güçlendirin:
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);
Rotada Devre Kesiciyi de etkinleştirdik. Teslimat hataları veya alıcının bulunamaması durumunda uzak sistem çağrılarının duraklatılmasını sağlayan bir entegrasyon kancasıdır. Bu, kademeli sistem arızasını önlemek için tasarlanmıştır. Hystrix bileşeni, Devre Kesici modelini uygulayarak bunu başarmaya yardımcı olur.
Çalıştıralım ve bir test isteği gönderelim; yanıtı her iki hizmetten de toplayacağız.
[{"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}]}]
Sonuç beklendiği gibi.
Diğer Kullanım Durumları
Apache Camel'in mikro hizmetleri bir kümeye nasıl entegre edebileceğini gösterdim. Bu çerçevenin diğer kullanımları nelerdir? Genel olarak, kural tabanlı yönlendirmenin bir çözüm olabileceği her yerde kullanışlıdır. 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.
Çözüm
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.