Łatwo znaleźć informacje o pierwszych doświadczeniach z daną technologią. Jeśli chodzi o doświadczenia po spędzeniu z nią większej ilości czasu, to jest tychże znacznie mniej…
W tym wpisie przedstawię subiektywne odczucia odnośnie frameworku Quarkus.
Kontekst projektu
Projekt polega na ogólnie mówiąc generowaniu wydruków (czyt. więcej przetwarzania niż oczekiwania na I/O). Mimo to interakcja z innymi serwisami jest również konieczna. Dodatkowym wymaganiem jest wsparcie dla Javascriptu na backendzie.
Dlaczego Quarkus?
Kluczowe byłyby dwa wymagania: wsparcie dla Javascriptu oraz szybkość generowania wydruku.
Pierwszy wymóg mógł zostać łatwo spełniony poprzez wybór GraalVM jako JVM – GraalVM zapewnia wsparcie dla różnych języków w ramach całej platformy. Co więcej – nie jest to symulowanie działania danego języka wewnatrz JVMa poprzez interpretowanie kolejnych instrukcji, ale to JVM (właściwie to GraalVM) rozumie dany język i wykonuje instrukcje na dość niskim poziomie. Stąd wydajność rozwiązania jest znacznie lepsza niż w wersji interpretowanej.
Alternatywnie wsparcie dla Javascriptu można było zapewnić poprzez dodanie zależności mavenowej/gradle’owej Graal.js. Ta wersja pozwala na wykorzystanie zwykłej JVMki zamiast GraalVMa, jednak kosztem wydajności – uruchomienie skryptów Javy (hi hi) odbywa się poprzez interpretowanie kodu.
Drugie wymaganie dotyczyło prędkości generowania wydruku. Należałoby tu rozważyć dwie sytuacje – na krótszą i dłuższą metę. Czas pierwszych wydruków możnaby zminimalizować korzystając z GraalVMa Native Image. Za to na dłuższą metę można skorzystać ze standardowej GraalVM bez kompilacji do kodu natywnego, gdyż JIT zapewnia lepszą wydajność niż kompilacja Ahead-of-time (o więcej o tym później).
Na etapie wyboru technologii Spring Native nie był jeszcze wydany, zatem najbardziej powszechny framework musiał zostać odrzucony. Był oczywiście jeszcze Micronaut, jednak wybór padł na Quarkusa. Jeśli szukacie porównania tych technologii, polecam artykuł o wyborze frameworku dla Stargate v2.
Jak poukładany jest Quarkus
Quarkus w swych marketingowych materiałach używa sentencji
Developer Joy: Unified configuration, Zero config, live reload in the blink of an eye, Streamlined code for the 80% common usages, flexible for the 20%, No hassle native executable generation, live coding.
Jest w tym dużo racji. Jest bardzo wiele tutoriali, które ogrywają standardowe sytuacje i działają jak należy. Live reloading odświeża kontekst aplikacji i przeładowuje źródła (choć dopiero przy wysłaniu requesta, a nie na zapis pliku, co może być nieintuicyjne). Generator Quarkusa oprócz hierarchii katalogów i wydmuszki aplikacji, tworzy również gotowe profile developerskie, produkcyjne, kompilacje do Native Image, a także obrazy dockerowe – właściwie to Dockerfile’e (z apka w wersjach JVM oraz natywnej).
Sam Quarkus jest skonstruowany tak, by możliwie najwięcej działań potrzebnych mu do uruchomienia było wykonanych przed uruchomieniem aplikacji. Chociażby określenie dostępnych beanów odbywa się poprzez indeksowanie ich w czasie kompilacji, a nie poprzez skanowanie classpatha w czasie inicjalizacji aplikacji, jak to ma miejsce w Spring Boot. Generowanie Proxy do obsługi przykładowo @Transactional
równiez generowane są na etapie kompilacji źródeł. Wszystkie beany są domyślnie inicjowane w trybie Lazy – dopóki czegoś nie potrzebujemy, to nie jest to tworzone. Dodatkowo same biblioteki frameworku i sam Quarkus jest ładowany przez osobny classloader niż klasy naszej aplikacji. Dzięki temu przy przeładowaniu kodu wszystkie biblioteki nie muszą być ponownie ładowane.
Oszczędzamy dzięki temu wszystkiemu czas na przeładowaniu kontekstu i w efekcie apka wstaje szybciej.
Tryb deweloperski, a właściwie jego konsola jest dość przydatna. Oprócz wymuszenia przeładowania i puszczenia wszystkich testów w projekcie jest tam bardzo przydatna funkcja zmiany poziomu logowania z użyciem jednego klawisza w czasie działania serwera. Opcja powszechna w różnego rodzaju JMXach, Actuatorach, havt.io i innych, ale jednoklawiszowej opcji dotychczas nie spotkałem 😉
Tryb deweloperski ma jeszcze jeden smaczek: jeśli port jest zajęty, to Quarkus podpowie komendę netstat
która sprawdzi PID procesu zajmującego ten port – mała rzecz, a cieszy 🙂
Jakkolwiek muszę też wspomnieć o tych 20%, gdzie trzeba wyjść przed szereg i skonfigurować coś niestandardowego. Jeśli mamy jakąś starą zależność (ot, jakieś JMSy sprzed 10 lat), z której musimy korzystać, bo cała reszta naszej platformy z niej korzysta, to jest to uciążliwa sprawa. Wszytko jest „zrabialne”, ale aby wykonać to porządnie, trzeba się mocno napracować. Wsparcie dla trybu deweloperskiego, natywnego, współpraca miedzy wątkami, przekazywanie kontekstu – wszystko zrobić się da, acz niezerowym nakładem.
Największym dramatem jest to, że większość tych customowych problemów da się rozwiązać, jednak dopiero po trafieniu na odpowiednie miejsce w internecie, które co prawda istnieje, jednak nawet google nie wie jak tam trafić… Najbardziej jednak boli to, że to przeważnie kilka linijek kodu…
Pluginy
Jak już wspomniałem, Quarkus posiada bardzo liczne integracje z popularnymi bibliotekami. Dodawanie integracji do projektu odbywa się poprzez dodanie odpowiedniego „pluginu” (plugin to nic innego jak zwykła zależność mavenowa/gradle’owa). Każdy plugin jest automatycznie przystosowany zarówno do trybu deweloperskiego, jak i produkcyjnego (jej i native). Dokumentacja ogólna, jak korzystać z pluginu, jak i szczególna szczegółowe propertiesy konfigurujące plugin są dostępne na tej stronie (jest też opcja wyszukiwania).
Podstawowe technologie
Quarkus integruje ze sobą przeróżne wypróbowane technologie i na nich się koncentruje. Dla obsługi wystawiania serwisów – adnotacje JAX-RS z serializacja Jacksonem. Wstrzykiwanie zależności – CDI. Konfiguracja projektu oparta jest na Smallrye Config. Logowanie – JBoss Logging (acz dostępne adaptery dla Log4J i SLF4J). Domyślny ORM to Hibernate, z opcją Hibernate Reactive. Jak już mowa o reaktywności to tutaj wykorzystywana jest biblioteka Mutiny, co więcej wszystkie reaktywne pluginy wystawiają interfejs z użyciem Mutiny. Całość postawiona jest na Vert.x.
Używanie bibliotek uznawanych za standard ma swoje zalety, lecz również wady. Przede wszystkim używanie bibliotek porządnie wygrzanych na produkcji jest potencjalnie bardziej odporne na błędy, niż pisanie swoich bibliotek. Odchodzi też kwestia utrzymania, dokumentacji i rozwoju – zajmuja się tym inne organizacje, a Quarkus jedynie integruje wszystko razem.
Problem z tym podejściem jest taki, że nie wszystko znajdziemy na stronie Quarkusa. Trzeba czasem szukać podpowiedzi na stronie danej biblioteki, a nawet specyfikacji standardu, który biblioteka implementuje (przykładowo CDI). Zdarza się czasem taka szara strefa jeśli chodzi o odpowiedzialności, kto za co odpowiada. Przykładowo jeśli dodamy OpenTelemetry z traceId, które logujemy, to w przypadku użycia vert.x’owych dodatkowych feature’ów, ten traceId się gubi na kilka wpisów w logach. A to właśnie za sprawą tego, że vert.x’owe dodatki działają na osobnej Poli wątków, których uogólniając OpenTelemetry nie jest świadome.
Ciąg dalszy nastąpi…
Tyle by było ogólnych informacjo-opinii na temat Quarkusa. W kolejnym wpisie/kolejnych wpisach będzie więcej szczegółów technicznych, a i ostatecznego podsumowania się spodziewajcie 😉
Pax et bonum
Cześć
Też wdrozylem przejście że Spring na Quarkus w swojej firmie. W sumie case był podobny, potrzebujemy niektóre rzeczy stawiać na jvm a niektóre native, Spring był wtedy jeszcze 5.x.x i zdecydowaliśmy się na Quarkus ponad Micronaut.
Co do przykładu z OpenTelemetry. Wiadomo, że nikt się nie chce grzebać i konfigurować. Z docami, też mamy podobne odczucie, że czasem nie mam pewnosci czy powinienem szukać w docach Quarkus czy danej dependecji. Jednak mi się koncept podoba, w moim odczuciu Quarkus jest gdzieś pomiędzy Springiem a Ratpackiem w swoim podejściu i to jest jak na mnie „sweet spot”.
Co do komentarzy w necie/na stacku mam to samo odczucie. Rzeczy nawet stricte Quarkusowe nie mogłem znaleźć w docu, a jak znajdę temat na staku to się okazuje że jest na to bean/annotacja/sposób do zrobienia w 2 linie. Też te odpowiedzi są od 3-4 osób które Quarkus piszą, więc tu czegos brakuje. Tu może być też kazus nowego frameworku. Jak pisało się długo w Spring to jakieś sposoby, główne beany etc się zna i ta wiedza jest z chodzenia po projektach parę lat a nie szukania w docu, może te rzeczy które narazie nie wiem jak zrobic z czasem też się staną „wiedzą powszechną”.
Chociaż tez przyznam, że generalnie Doci Quarkusa są dobre. Przykłady sa dość realne ponad „happy path” i realnie się kompiluja i działają co nie jest standardem 😀
Co jest bardzo spoko w dev narzędziach o czym nie wposmianles, to emulacja wysyłania eventow żeby testować lambdy integracyjnie i feature który stawia sam testcontainers żeby móc odpalić program na lokalnej maszynie bez przedwczesnego konfigurowania dockerow i propertasow.
Czekam na part 2 ^^
O devtoolsach nie rozpisywałem się, gdyż zbytnio nie korzystałem. Problematyczne okazało się tworzenie struktury multimodule w mavenie, aby zmiany w różnych modułach były brane pod uwagę. Prócz tego często przeładowanie kontekstu musiało być nie na request Http, a na nową wiadomość z niewspieranej wersji ActiveMQ. Ostatecznie nie tylko klasy się zmieniały, ale też resource’y, co wiem, że się da skonfigurować, ale też nietrywialnie. Zatem odpuściłem sobie konfigurowanie devtoolsów – trzeba kiedyś coś zaprogramować, a nie tylko siedzieć i konfigurować 😉
Co do dokumentacji, to ostatnio mam projekcie w Spring Boot 3 i jak tam coś guglam, to często pierwsze wyświetlenia to Baeldung (co przeważnie jest spoko, ale akurat szukam dokumentacji ;)). Drugie wyświetlenie to dokumentacja, ale dla wersji 1.4, albo w ogóle Spring bez butów.
Więc taka nowa dokumentacja Quarkusa jest pewna zaletą – nie zdążyła się nadto zdezaktualizować 😉