Quarkus – luźne przemyślenia po 500h developmentu (cz. 1)

jgardo.dev 1 rok temu

Łatwo znaleźć informacje o pierwszych doświadczeniach z daną technologią. jeżeli 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żeli 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 – adekwatnie 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 adekwatnie 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żeli 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żeli 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, kooperacja miedzy wątkami, przekazywanie kontekstu – wszystko zrobić się da, acz niezerowym nakładem.

Największym dramatem jest to, iż 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 choćby google nie wie jak tam trafić… Najbardziej jednak boli to, iż 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, ale 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, iż nie wszystko znajdziemy na stronie Quarkusa. Trzeba czasem szukać podpowiedzi na stronie danej biblioteki, a choćby specyfikacji standardu, który biblioteka implementuje (przykładowo CDI). Zdarza się czasem taka szara strefa jeżeli chodzi o odpowiedzialności, kto za co odpowiada. Przykładowo jeżeli 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, iż 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

Idź do oryginalnego materiału