Wyślij zapytanie Dołącz do Sii

Kilka lat temu zaangażowałem się w tworzenie aplikacji do komunikacji między pracownikami firm kurierskich – głównie pomiędzy kurierami a pracownikami BackOffice. Architektura aplikacji zakładała utworzenie kilku serwisów. Jeden z nich miał za zadanie komunikację z aplikacjami „zewnętrznych” dostawców, które w różnej formie dostarczały nam niezbędnych informacji o użytkownikach systemu. Budowany serwis miał zapewnić funkcjonalność książki adresowej.

By zebrać i odpowiednio przygotować dane do tej książki, należało wykonać według specjalnych reguł około 7 różnych żądań http. Wybrane z nich powtarzało się kilka razy – przez wzgląd na paginację i duże ilości danych. Następnie trzeba było je w jakiś sensowny sposób zagregować.

Podczas tworzeniu serwisu książki adresowej największym wyzwanie stanowiło to, w jaki sposób poskładać w sensowną całość wszystkie niezbędne żądania. Oczekiwanym rezultatem prac był endpoint serwisu zwracający tablicę nazw użytkowników oraz numery ich zarejestrowanych komunikatorów. Kiedy już dane zostały zebrane, to należało „wrzucić” je do cache’a, by nie musieć zbyt często ponawiać całej skomplikowanej procedury.

Powstało wówczas sporo kodu, który trzeba było co kilka kroków zrefaktoryzować, by zapewnić względną utrzymywalność oraz czytelność. Nie wspomnę o optymalizacji dostarczanego rozwiązania pod względem złożoności czy ewentualnych wycieków pamięci przez lawinowo tworzone kolejne instancje obiektów transferowych (DTO).

Choć serwis finalnie działał całkiem przyzwoicie, to ilość wysiłku włożonego w tworzenie implementacji, a przede wszystkim w szlifowanie rozwiązania, była niewspółmiernie duża do oczekiwanego i dostarczanego rezultatu. Tuż pod koniec trwania projektu, gdy już tylko uzupełnialiśmy ewentualne braki w dokumentacji, przy kawie odbyłem rozmowę z jednym ze swoich bardziej doświadczonych wówczas kolegów. Opowiedziałem mu o bolączkach, o tym z jakiej skali problemami musieliśmy się mierzyć, na co on skwitował opowieść pytaniem: „A właściwie, to dlaczego nie zdecydowaliście się na Apache Camel?”.

Z artykułu dowiesz się, z jakich powodów warto poznać się bliżej z tym narzędziem.

Czym właściwie jest Apache Camel?

Na stronie projektu znajdziemy informację, że Camel to uniwersalny integracyjny framework dystrybuowany na licencji open-source, bazujący na znanych wzorcach EIP (Enterprise Integration Patterns). Projekt doczekał się kilku publikacji książkowych, licznych artykułów, niemałej społeczności oraz co najmniej kilku poważnych firm konsultingowych.

Rewelacyjnie kooperuje i wzajemnie się uzupełnia z frameworkiem Spring oraz Spring Boot. Jest narzędziem bardzo rozbudowanym, a tworzona w oparciu o niego integracja jest łatwo rozszerzalna i modyfikowalna. Definiowane przy jego pomocy endpointy budowane są zgodnie z dostarczonymi DSL-ami (Domain Specific Language).

Choć wszystkich dostępnych jest całkiem sporo, to najbardziej popularnymi wydają się być Rest DSL, Java DSL oraz XML DSL. Głównymi obiektami definiowanymi z oparciu o składnię DSL są route’y. Route’y korzystają z niezwykle bogatego pakietu komponentów dedykowanych różnym kanałom, stylom oraz formom komunikacji. Wszystko to, by w ekstremalnie elastyczny sposób pozwolić na przepływ i odpowiednie dostosowanie danych z żądań i z odpowiedzi.

Przejrzysty kod oraz wygodna struktura – czyli Camel DSL w praktyce

O tym, jak istotne – z punktu widzenia zespołu java developerów – jest tworzenie czystego kodu, zgrabnych rozwiązań, przejrzystej implementacji, utrzymywanie właściwego i wyważonego poziomu abstrakcji, powstało sporo książek i artykułów. Znani i szanowani, bardzo doświadczeni programiści, architekci i mentorzy przekonują czytelników i słuchaczy swoich wykładów oraz podcastów, prezentując przykłady na to, w jaki sposób pisać kod, a jak tego nie robić oraz – rzecz jasna – czym grozi nie stosowanie się do sugestii.

Zwracają uwagę zainteresowanych na ważne aspekty, jednakże nieustannie z różnym skutkiem. Jeżeli tylko na chwilę w projekcie zabraknie osoby odpowiednio zaangażowanej w pielęgnowanie i budowanie świadomości programistycznej oraz uwrażliwiającej zespół na czystość dostarczanych rozwiązań, to poziom nieuporządkowania i przypadkowości w implementacjach drastycznie może wzrosnąć. Oczywiście, nie da się całkiem wyeliminować tego problemu, ale Apache Camel może się okazać niezwykle pomocny w tym obszarze działania.

Nie chcąc być gołosłownym, poniżej zaprezentuję dwa fragmenty kodu. Na pierwszym z nich znajdziemy implementację endpointu przy użyciu frameworka Spring, a na drugim jego ekwiwalent przy wykorzystaniu Rest DSL od Apache Camel.

Deklaracja endpointu /collections/books w klasie CollectionsController przy użyciu Spring Framework
Ryc. 1 Deklaracja endpointu /collections/books w klasie CollectionsController przy użyciu Spring Framework
Deklaracja endpointu /collections/books w klasie CollectionsController przy użyciu Apache Camel
Ryc. 2 Deklaracja endpointu /collections/books w klasie CollectionsController przy użyciu Apache Camel

Porównanie kodów

Jak możemy zauważyć, w obu przypadkach tworzenie endpointów REST-owych jest – co do zasady – nieskomplikowane. Jestem skłonny zaryzykować stwierdzenie, że przyzwyczajeni do stylu Springa, jako bardziej popularnego frameworka, możemy postrzegać zapisany w nim kod jako równie czytelny jak ten z Ryc. 2. Jednakże, w mojej opinii, dużo bardziej deskryptywny jest kod zapisany przy użyciu Rest DSL, gdzie znajdziemy mniej adnotacji, krótkie i jasne deklaracje metod HTTP takich jak na przykład GET czy POST, wygodny oraz przyjemny builder, którego używamy do „budowania” route’ów.

Rest DSL jest bardzo prosty i intuicyjny w użyciu, a czytelność deklarowanego endpointu została niejako „wydestylowana” do maksimum.

Bez względu na to, czy korzystamy z Rest DSL, Java DSL czy innego dostępnego, to czytelność, intuicyjność i prostota zawsze będą na tym samym poziomie. W moim odczuciu, niezwykle korzystne jest wykorzystanie wzorca builder do konstruowania struktury endpointów (czy ogólnie route’ów), co wymusza konsekwencję w definiowaniu i logiczną spójność na poziomie całej aplikacji.

Bardziej „co” niż „jak”

Wyobraźmy sobie, że do przykładu ze springowym kontrolerem REST-owym potrzebujemy wprowadzić pewną istotną modyfikację. Załóżmy, że to co do tej pory pobieraliśmy z własnej metody getBooks, zostało przeniesione do innego serwisu i od teraz będzie dostępne pod adresem: „http://innyserwis.com/collections/books”. 

By pobrać dane z tego endpointu należy wcześniej albo przygotować komponent do wykonywania żądań http, oparty na przykład o OkHttp, Apache Client itp., albo skorzystać z tego, co oferuje Spring, w RestTemplate czy jakiejś jego nowocześniejszej alternatywie.

Nie chcąc dokładać złożoności, skorzystajmy z RestTemplate.

Do klasy BooksServiceImpl dodajmy zatem pole o typie RestTemplate i przez konstruktor powiążmy z utworzoną instancją. Następnie do metody getBooks wprowadźmy poniższy fragment kodu:

Ryc. 3 Przykładowa implementacja metody getBooks korzystającej z RestTemplate
Ryc. 3 Przykładowa implementacja metody getBooks korzystającej z RestTemplate

Powyższy przykład pokazuje, że pobieranie danych z innego endpointu należącego do innego serwisu, musi zostać poprzedzone całym szeregiem operacji, nie zapominając o osobliwym obiekcie typu ParameterizedTypeReference, niezbędnym by wskazać typ spodziewanych danych z odpowiedzi.

Jak możemy zauważyć, w przypadku RestTemplate albo idąc krok dalej, w przypadku Springa, musimy posiadać bardzo szczegółową wiedzę o tym, w jaki sposób należy coś zaimplementować, aby móc to zrealizować wystarczająco dobrze. Pomimo popularności tego podejścia, za każdym razem musimy na takie rozwiązanie spojrzeć z perspektywy nie tylko tego, co chcemy osiągnąć, ale przede wszystkim – jak to chcemy zrobić.

Wykorzystanie Apache Camel

W Apache Camel sytuacje podobne do przedstawionej powyżej nie mają zwykle miejsca. Szczególnie, gdy mamy do czynienia z typowym użyciu dostarczonych komponentów. Spójrzmy zatem na poniższy fragment kodu, w którym przedstawiono sytuację, gdzie przepięto źródło danych z metody serwisu do komponentu frameworka Camel.

Deklaracja endpointu /collections/books w klasie CollectionsController przy użyciu Apache Camel korzystająca z komponentu http zamiast bean
Ryc. 4 Deklaracja endpointu /collections/books w klasie CollectionsController przy użyciu Apache Camel korzystająca z komponentu http zamiast bean

W powyższym fragmencie jedyną rzeczą, którą należało zrobić, było skorzystanie z innego komponentu. I tak: zamiast komponentu „bean”, w którym wskazywaliśmy na obiekt klasy BookService oraz jego metody „getBooks()”, wybieramy na przykład komponent „http”, który wykona dla nas przekierowanie do wskazanego endpointu i odbierze z niego uzyskaną odpowiedź.

Jeżeli wiemy, jak działają komponenty Camela, z których chcemy skorzystać oraz znamy sposób użycia dostępnych wzorców EIP, to wykorzystując ten framework, nie musimy się (równie mocno jak w Springu) koncentrować na tym, jak to chcemy zrobić. Generalnie, w Camelu wystarczy wiedzieć, co się chce osiągnąć.

Możliwości Camela

Chciałbym jeszcze przez chwilę skupić się na kilku wybranych możliwościach Camela. Już bez porównywania alternatywnych i analogicznych rozwiązań dostarczonych przez obydwa frameworki, zaprezentuję i pokrótce omówię dwa przykłady. Obydwa zostały przygotowane w oparciu o fragmenty serwisu integrującego się z kilkoma innymi aplikacjami.

Pierwszym problemem, który należało rozwiązać było wysłanie formularza z kilkoma indywidualnymi zestawami danych. Dane były połączone wcześniej nadanym numerem serwisowym. Istotnym założeniem było to, by z rozbitych dwóch żądań przygotować jedno zbiorcze. Numer serwisowy nadawany był przez zewnętrzny serwis, który zapewniał jego unikalność. Warto dodać, że wysyłane zestawy danych mogły być konsumowane pojedynczo przez wzgląd na wewnętrzne ograniczenia serwisu, z którym się integrowaliśmy.

Deklaracja endpointu POST „/upload” wykonującego serię operacji jak żądanie nadania indywidualnego numeru serwisowego oraz rozdzielenie zestawu danych na kolejne żądania realizowane przy wykorzystaniu wzorca Split EIP
Ryc. 5 Deklaracja endpointu POST „/upload” wykonującego serię operacji jak żądanie nadania indywidualnego numeru serwisowego oraz rozdzielenie zestawu danych na kolejne żądania realizowane przy wykorzystaniu wzorca Split EIP

W powyższym kodzie możemy zobaczyć rozbicie route’a z endpointu REST na dwa inne, a dla podkreślenia ich kolejności zawarte zostały w bloku „pipeline – end”.

Route zapewniający nam „direct:get-service-number” ma za zadanie pobrać nowo nadany numer serwisowy, zaś route „direct:upload-all-files” odpowiedzialny jest za rozdzielenie przychodzącego zestawu danych na osobne żądania. Ten route korzysta z wzorca Split EIP, który rozdzielił kolekcję nazw i dla każdej jednej – we właściwy sposób – wykonał żądanie „uploadu”. Żeby zwrócić użytkownikowi informację o tym, które operacje „uploadu” się powiodły, a które nie, wykorzystano przygotowany UploadStatusAggregator definiujący właściwą strategię agregacji.

Kolejnym zadaniem, które należało rozwiązać, było zebranie danych z trzech endpointów, zwracających dane w trzech różnych formatach. Dane należało ze sobą odpowiednio powiązać i popakować do wynikowej listy obiektów. Rozwiązanie oparto o Multicast EIP z odpowiednio opracowaną implementacją obiektu odpowiedzialnego za prawidłowe zagregowanie nadchodzących danych.  Do sterowania parsowaniem przychodzących danych w formacie JSON, na potrzeby niniejszej analizy wykorzystano „exchange properties”.

Deklaracja endpointu GET „/aggregation” zbierającego dane z trzech różnych endpointów o różnych typach odpowiedzi do jednego zbiorczego, tworzącego inna strukturę wynikową
Ryc. 6 Deklaracja endpointu GET „/aggregation” zbierającego dane z trzech różnych endpointów o różnych typach odpowiedzi do jednego zbiorczego, tworzącego inna strukturę wynikową

Jak widać na powyższym listingu, do utworzenia endpointu został użyty Multicast EIP oraz wykorzystano właściwości (exchange property) do przesyłania odpowiednich wartości pomiędzy kolejnymi route’ami.

Podsumowanie

Ponad dwa lata temu miałem ogromne szczęście, by trafić do projektu, w którym był Apache Camel. Wiele rzeczy zaimplementowanych przy pomocy Camela – podejściem i pewnie wiekiem – pamiętać mogło dinozaury. Mimo tego, że wiele rzeczy było tam zaimplementowanych niewłaściwie, to wspaniałe jednak było tam to, iż zastany kod mogłem potraktować jak poligon doświadczalny. Zmiana metodyki budowania route’ów i migracja konwencji do budowania takich o wiele łatwiejszych w utrzymaniu i nieporównywalnie bardziej czytelnych, niż to miało miejsce wcześniej, zakończyła się sukcesem.

W projekcie prowadzonym przez nasz zespół mogłem sporo zaobserwować w kontekście tego, jak nie należy budować route’ów. Dzięki temu finalnie miałem możliwość zaproponowania najbardziej dla nas wygodnego podejścia. Z każdym kolejnym kawałkiem implementacji z wykorzystaniem Apache Camel nabieram przekonania o tym, że jest to jeden z najlepiej dopracowanych frameworków do integracji z jakimi się spotkałem. Wspaniale działa razem ze Springiem czy SpringBootem.

Kiedy kilka lat temu zmagałem się z tworzeniem „książki adresowej” i na pewno brakowało mi wówczas zrozumienia tego, jak działa Apache Camel w stopniu umożliwiającym skuteczne negocjowanie możliwości użycia go w jednym z mikroserwisów. Dziś już mam pewność, że jeżeli projekt nosiłby znamiona integracji, albo zwyczajnie potrzebowałbym czegoś, co wsparłoby Springa w projekcie, to z całą pewnością sięgnąłbym po Apache Camel.

Jeżeli w swoim projekcie masz podobne potrzeby, to nie wahaj się ani chwili, tylko zacznij swoją przygodę z Camelem i zyskaj czas, łatwość implementowania oraz sporo satysfakcji.

***

Być może zainteresują Cię również inne artykuły naszych ekspertów m.in.:

5/5 ( głosy: 4)
Ocena:
5/5 ( głosy: 4)
Autor
Avatar
Marcin Jawor

W Sii od 3 lat. Java Software Developer, pracujący jako Solution Architect i Team Leader. Interesuje się tematami DevOps i BigData. Programuje również w Python, Scala i R.

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Może Cię również zainteresować

Pokaż więcej artykułów

Bądź na bieżąco

Zasubskrybuj naszego bloga i otrzymuj informacje o najnowszych wpisach.

Otrzymaj ofertę

Jeśli chcesz dowiedzieć się więcej na temat oferty Sii, skontaktuj się z nami.

Wyślij zapytanie Wyślij zapytanie

Natalia Competency Center Director

Get an offer

Dołącz do Sii

Znajdź idealną pracę – zapoznaj się z naszą ofertą rekrutacyjną i aplikuj.

Aplikuj Aplikuj

Paweł Process Owner

Join Sii

ZATWIERDŹ

This content is available only in one language version.
You will be redirected to home page.

Are you sure you want to leave this page?