Jakieś 1,5 roku temu obiecałem podsumowanie… Zatem wracam by dopełnić obowiązków 😉
Ale najpierw jeszcze wpis o różnych ciekawych sprawach, których w poprzednich wpisach nie poruszyłem.
Wsparcie dla Native Image
Jak już wspominałem w poprzednim wpisie, wstępne skonfigurowanie kompilacji do natywki jest czasochłonne.
Po pierwsze – potrzeba skonfigurować, które klasy można zainicjalizować w run-time, gdyż Quarkus domyślnie wszystkie klasy inicjalizuje w build-time. Są takie przypadki, gdzie chcemy za każdym razem przy starcie aplikacji. Najpowszechniejszym przykładem jest Random
. Szczęśliwie, GraalVM sam wykrywa inicjalizowanie Random
ów w polach statycznych. Jeśli klasa posiadająca takie pole jest skonfigurowana do inicjalizacji w build-time, kompilacja kończy się błędem jednocześnie racząc nas informacjami potrzebnymi do namierzenia go.
Po drugie – GraalVM Native Image zakłada „zamknięty świat”. To założenie polega na tym, że wszystkie źródła podane w build-time, to wszystkie źródła, które będą uruchamiane. Nic nad to, co zadeklarujemy w build-time, nie będzie uruchamiane w runtime. A że GraalVM obcina wszystko co się da, żeby zmniejszyć startup time oraz memory footprint, to obcina informacje o refleksji, serializacji, dynamicznych proxy, jni i embeddowanych resource’ach (a także przykładowo wsparcie dla SSL, wszystkich charset’ów).
I o ile niektóre kwestie konfiguruje się tylko raz (np. włączanie ssla) o tyle dane o refleksji dla mogą się zmieniać częściej. Konfiguracja tych rzeczy jest czasochłonna, jeśli by ją wykonywać ręcznie. Dlatego GraalVM udostępnia agenta javowego, który zbiera używana konfigurację za nas. Trzeba jednak uruchomić plik jar
z podczepionym agentem, następnie „przeklikać” wszystkie dostępne edge-case’y, żeby że zebrać wszelkie metadane. Jednak to również może być czasochłonne…
GraalVM rozpoczął zatem prowadzenie repozytorium metadanych dla konkretnych wersji najpowszechniejszych bibliotek. Ale nie wszystkich wersji i nie wszystkich bibliotek.
I tutaj Quarkus nieco wychodzi naprzeciw potrzebom native buildów. Otóż w Quarkusie można definiować nie tylko testy whiteboxowe, ale też blackoxowe (testy integracyjne). Polegają one na uruchomieniu odpowiednio skonfigurowanego docelowego artefaktu (Jar/Docker Image). Quarkus w czasie tych testów może pozbierać metadane zapewniając, że testowane przypadki użycia będą działać w trybie natywnym.
Swoją drogą, Quarkusowe testy blackboxowe (integracyjne) moga też służyć do testowania dockerowego obrazu z aplikacją jvm’ową, jak i natywną co jest nieoczywistą cechą.
Feedback loop
Dev expirience jest w Quarkusie ważnym tematem, zatem skracanie feedback loopy – czyli w tym przypadku czasu od zmiany w kodzie do sprawdzenia efektu tej zmiany – jest w cenie.
W lokalnym developmencie możemy uruchomić projekt w dev mode. Pozwala on na automatyczne przeładowanie aplikacji – poprzez przeładowanie całej aplikacji lub poszczególnych klas (poprzez instrumentalizację). Co ciekawe, przeładowanie kodu jest wyzwalane przez żądanie na endpoint lub wysłanie wiadomości na kolejkę wystawianą przez daną aplikację, a nie na zapisanie źródeł. Co prawda, wymaga to zmiany nawyków, żeby uruchomić curla i poczekać zamiast standardowe mvn clean install
-> curl, ale warto się przestawić na to działanie.
Jeśli jednak nie mamy tego wspaniałego przywileju uruchamiania całego środowiska lokalnie (chociażby ze względu na zbyt mała ilość zasobów, żeby postawić n mikroserwisów) i musimy korzystać z środowisk w chmurze, Quarkus również ma dla nas pewną propozycję. Otóż jest możliwość zbudowania tzw mutable jar. Pokrótce polega ona na stworzeniu pewnej niezmiennej bazy i ładowaniu samej aplikacji w osobnym classloaderze. Następnie należy uruchomić dev mode w trybie zdalnym podając standardowy adres http aplikacji. Przy zmianie kodu (oraz przeładowaniu aplikacji triggerowanego requestem/messagem) zostanie zmieniony obraz na zdalnym serwerze (będą podmienione zmienione klasy i przeładowana aplikacja). Porównując do standardowego mvn clean install
z wypychaniem obrazu przez sieć, na które można poczekać kilka minut, podejście z mutable jar trwa kilka sekund.
To podejście może pomóc zaoszczędzić mnóstwo czasu!
Jakkolwiek ma to również swoje plusy ujemne 😉 Na szybkości widzę dwa:
- Obraz dockerowy z mutable jar jest de facto inny niż obraz production-ready. Teoretycznie może działać inaczej. Takie podejście jest też niezgodne z jednym z 12 factor – „Keep development, staging, and production as similar as possible”.
- Kara w postaci długiego redeploy’u, może zachęcać do poczynienia go dopiero, gdy coś sensownego już stworzymy. Zachęca również do szukania innych sposobów upewnienia się, że wytworzony kod działa – przykładowo poprzez stworzenie przeróżnych testów, co jest wartością samą w sobie. Z drugiej strony długi redeploy może też zachęcać też do wypicia kawy w tym czasie lub zagrania rundki w piłkarzyki 😉
A jeśli chcemy stworzyć „(re)konfigurowalny artefakt”?
Przerzucanie budowanie najwięcej jak się da na fazę build-time’u ma swoją konsekwencję – jak coś zbudujemy, to w runtime nie zawsze da radę zmienić… Wszak niektóre „propertiesy” są konfigurowane tylko w build-time.
No… w sumie to nie do końca 😉
Quarkus daje nam możliwość reaugmentowania (czyli zmienienia efektów build-time’u) w runtime. Są jednak pewne warunki:
- Aplikacja musi być zbudowana jako mutable jar
- Przy starcie (
java -jar
) trzeba poinformować, że chcemy zmienić to co uprzednio zbudowaliśmy i na co chcemy zmienić (przez standardowe przełączniki-D
).
Tak reaugmentowany artefakt ma też swoje minusy – dodatkowy narzut na przekonfigurowanie przed startem zwiększa startup time. Ponadto wymuszenie budowania mutable jara wyklucza kompilację do kodu natywnego.
Mimo wszystko samo istnienie takiego rozwiązania mocno mnie zaskoczyło. Nie wpadł bym na to, że można chcieć taką ficzkę, a żeby to zaimplementować to już w ogóle.
Podsumowanie
Powtórzę za poprzednim Quarkusowym wpisem – Podsumowanie zostanie udostępnione w następnym wpisie, a tymczasem:
Pax et Bonum 😉