W dzisiejszej edycji w dalszym ciągu pozostajemy nieco w temacie KotlinConf, ale wyjdziemy poza ten event, prezentując nowość w build systemach javowych, a także skrót bardzo ciekawego informacyjnego JEP-a, dotykającego tematu enkapsulacji javowego kodu.
1. Co Roman Elizarov – twórca Korutyn – sądzi o Wirtualnych Wątkach?
Tydzień temu zakończyłem edycje cliffhangerem, obiecując że dziś zajmiemy się prezentacją Romana Elizarova, która rozpoczynała drugi dzień KotlinConf, a opowiadała o tym, co w świecie Korutyn zmieni Project Loom (spoiler: nie tak wiele, ale o tym za chwilę). Słowa dotrzymuje. Zapisu prezentacji jeszcze w sieci nie ma (choć pewnie pojawi się na dniach), ale jako że miałem okazję ją oglądać, podzielę się tym co się na niej dowiedzieliśmy.
Od razu zaznaczę – większość z tego co w prezentacji się pojawiło to nie jest jakaś zupełnie nowa wiedza, chyba po raz pierwszy mamy jednak informacje od samego źródła – Roman Elizarov to nie tylko obecny lead Kotlina, ale również architekt korutyn. Jego punkt widzenia jest następujący – Project Loom i Kotlinowe Korutyny zajmują się wyzwaniami programowania współbieżnego, ale celują w różne aspekty, w związku z tym mają różne implementacje. Project Loom skupia się na rozwinięciu istniejących API wątków i ma na celu umożliwienie aplikacjom serwerowym napisanym w stylu thread-per-request (gro javowego ekosystem) optymalne skalowanie. Wprowadza wątki wirtualne jako rozwinięcie API dla wątków „fizycznych”, umożliwiając zmianę wątków fizycznych na wirtualne przy minimalnych zmianach kodu. Jest to szczególnie przydatne w przypadku już istniejącego kodu, który będziemy utrzymywać jeszcze przez lata, co najwyżej modernizując go, na przykład dzięki wirtualnym wątkom.
Kotlin Coroutines, działają zupełnie inaczej i na innej warstwie (na zasadzie generowania dodatkowego kodu). Dzięki temu skupiają się na dostarczeniu jak najwygodniejszego asynchronicznych API i granularnej współbieżności – zarówno aplikacji serwerowych, jak i każdego innej części projektu, gdzie wykonanie fragmentu kodu w nowym wątku ma sens. Biorąc pod uwagę istotność Kotlina w świecie Androida, równie ważne jest np. dobre obsługiwanie frameworków UI. Zapewnia lekki sposób zarządzania nią za pomocą współbieżności strukturalnej (Structured Concurrency), która pomaga utrzymać hierarchię rodzic-dziecko i rozwiązuje zaskakująco trudny problem anulowania raz rozpoczętego asynchronicznego workflow. Java też posiada Structured Concurrency, ale Elizarov wskazuje jej ograniczenia – powstała bowiem jako nowa funkcjonalność do już zastanego API współbieżności, nie jako coś dodanego już na poziomie projektowania.
Żeby jednak nie było, że kortyny nic na powstaniu wirtualnych wątków nie skorzystają – jedną z ich słabości są wyzwania związane z łączeniem świata asynchronicznego z tym „blokującym”, który nadal może tworzyć fizyczne wąskie gardła, wymuszając użycie zasobożernych dyspozytorów korutyn jak Dispatcher.IO
, działający na klasycznych, blokujących wątkach. Project Loom może pomóc uniknąć blokowania i marnowania zasobów fizycznych poprzez uruchomienie tego typu interfejsów w ramach Virtual Threads, które zostały stworzone do ich obsługi – z myślą o kompatybilności ze starym kodem. Wątki wirtualne Loom są więc bardziej wydajne pod względem zasobów niż wątki „fizyczne” (jak mi to ciężko przez gardło przechodzi), a jako że Korutyny z założenia powstały z myślą o używania dowolnego silnika współbieżności, będą mogły wykorzystać nowości w ramach JVM.
Oczywiście, całość jest pewnie nieco zbiasowana – nie mieliśmy bowiem do czynienia z panelem dyskusyjnym między Ronem Presslerem, a Romanem Elizarovem (panowie, zróbcie coś takiego, Avengers Endgame by się chowało jako crossover dekady), ale argumentacją twórcy Korutyn na Kotlinowej konferencji. W dalszym ciągu jednak bardzo polecam polować na wideo, jeśli kiedyś KotlinConf zdecyduje się opublikować zapis prezentacji – na pewno dam znać o takowym.
Zainstaluj teraz i czytaj tylko dobre teksty!
2. Masa ruchu w świecie build systemów – Gradle i bld
To jednak nie koniec kontynuacji wątków z poprzedniej edycji. W zeszłym tygodniu mówiliśmy o Gradle raczej z perspektywy nowości dla Kotlina, ale dziś wracamy do 8.1. Wydanie to przynosi bowiem wiele ciekawych nowości, o których nie miałem okazji wspomnieć tydzień temu.
Przykładowo, ustabilizowano funkcję cache’owania fazy konfiguracji, co pozwala na buforowanie jej wyniku i ponowne wykorzystanie go w kolejnych buildach, skracając ich czas. Cache obsługuje teraz w pełni weryfikację zależności, repozytoria lokalne i posiada rozszerzoną kompatybilność z podstawowymi wtyczkami. A jak już jesteśmy przy konfiguracji – Gradle dodał dodatkowo szyfrowanie jej pamięci podręcznej, aby chronić wrażliwe dane.
Plugin JVM obsługuje teraz budowanie projektów z Java 20, a wtyczka Codenarc – służąca do analizy statycznej – została zoptymalizowana pod kątem zrównoleglenia działania. Jeśli chodzi o wzrosty wydajności, sam Gradle dostał lepsze zarządzanie pamięcią. Dodatkowo, wprowadzono nowe akcje Dataflow – alternatywnego mechanizmu do dobrze znanych tasków – które zastąpią istniejący listener buildFinished.
Tydzień temu pisaliśmy o tym, że Gradle dla Androida będzie domyślnie korzystać z Kotlina. Okazało się jednak, że platforma poszła o krok dalej i postanowiła wprowadzić build.kts
jako domyślny format na wszystkich platformach. Niejako w związku z tym faktem Kotlin DSL doczekał się usprawnień w kilku obszarach, w tym eksperymentalnego prostego przypisywania propertiesów w skryptach Kotlin DSL oraz kilku ulepszeń jeśli chodzi o obsługę pluginów.
Co ciekawe, mimo tak wielu nowości, to nie o Gradle mówiono najwięcej, jeśli chodzi o buildsystemy JVM. Okazuje się bowiem, że na rynku pojawił się nowy build system, który ma na siebie całkiem interesujący pomysł – bld
od RIFE2.
Zaczniemy od tego czym jest RIFE2. Projekt może nie być tak szeroko znany jak niektóre inne frameworki internetowe Java, ale mimo IMHO okropnej nazwy jego twórcy starają się przebić do masowej świadomości, i chyba po raz pierwszy im się to udało. Twórcy frameworki RIFE2 pokazali bowiem bld
, w którym postanowili podejść do tematu budowania aplikacji nieco inaczej od konkurencji. Zarówno Gradle, jak i Maven, opierają się na tworzeniu konfiguracji buildu w sposób deklaratywny, czy to za pomocą DSL (w Groovym lub Kotlinie), czy też XML. bld
pozwala zaś programistom na pisanie logiki buildów w czystej Javie. System bld
został zaprojektowany z myślą o prostocie użycia, kładąc nacisk na bezpośrednie definiowanie procesu, unikając elementów auto-magicznych, często spotykanych w innych narzędziach do budowania, takich jak Gradle. Filozofia, która przyświeca narzędziu mocno kojarzy mi się z przyjętą przez AWS w Cloud Development Kit.
W przeciwieństwie do Gradle, które opisuje plan przed jego wykonaniem, bld
natychmiast wykonuje zadania, gdy są one zdefiniowane. Ta różnica w podejściu zmniejsza obciążenie poznawcze programistów, ułatwiając rozumowanie o procesach budowania. Ponadto, bld
wymaga Javę 17, zapewniając jednocześnie autouzupełnianie i wsparcie Javadoc. Integracja z Javą zapewnia, że logika budowania może być zrozumiana i utrzymywana w tym samym ekosystemie co kod aplikacji.
Mój komentarz: Jestem rozdarty między „build system powinien być nudny i przewidywalny” a faktem, że CDK posiada masę fanów i stał się de facto standardem w świecie AWS, bazując na bardzo podobnej filozofii. Boję się też mocno przeciekającej abstrakcji i faktu, że bez wsparcia społeczności ciężko będzie taki ekosystem rozhulać – nie raz rozpisywałem się o tym, że nawet konwersja Groovy SDK na Kotlin SDK bywa bolesna. Nie zmienia to faktu, że wrzuciłem sobie bld
na mój radar i kiedyś chętnie go wypróbuje.
Zainstaluj teraz i czytaj tylko dobre teksty!
3. Po co Javie silna enkapsulacja platformy?
A na koniec JEP, bo jak tak można by bez JEP-a. Kandydackimi zajmiemy się za tydzień (a jest ich parę), ale dzisiaj chciałem przybliżyć nowy draft: JEP draft: Integrity and Strong Encapsulation. Jest to tak zwany JEP informacyjny, czyli swoisty Design Doc, przedstawiający pewne założenia projektowe przyświecające twórcom. Warto się z nim jednak zapoznać, dotyka on bowiem bardzo ciekawego tematu: modularyzacji całej platformy, a także powiązanej z nią enkapsulacji.
Każdy wie, że jeśli oznaczymy w javowej klasie pole jako private
, to nikt spoza tej klasy nie będzie miał do niej dostępu Refleksja i inne mechanizmy mogły ją bowiem przez lata ominąć to zachowanie, np. poprzez użycie metody java.lang.reflect.AccessibleObject.setAccessible
. Powody łamania enkapsulacji są bardzo różne, jak uzyskania dostępu do nieeksponowanej funkcjonalności, obejścia błędów (sam to robiłem kiedyś z testową wersją AWS SDK 2.0, gdy ta była jeszcze w becie) lub poprawy wydajności – tutaj sztandarowym przykładem jest sun.misc.Unsafe
, który przez lata był podawany jako sztandarowy przykład łamania zabezpieczeń w JVM. Z punktu widzenia twórców JDK sytuacja taka jest nie do pozazdroszczenia – bardzo trudno jest ewoluować platformę w momencie, gdy nie masz pewności czy jakiś refaktoring nie zepsuje jakiejś popularnej biblioteki (prawo Hyruma się kłania). Postanowiono więc ten dziki zachód nieco posprzątać i poprawić sytuacje.
Silna enkapsulacja jest kluczem do rozwiązania problemów. Prace nad jej wprowadzeniem do Javy rozpoczęły się w latach 2010+, ale ich znaczenie staje się coraz wyraźniejsze z każdym kolejnym rokiem. Java 9, wprowadzając moduły, wymusiła silną enkapsulację w czasie kompilacji, ale ze względu na kompatybilność wsteczną (i brak sensownej alternatywy dla niektórych interwałowych rozwiązań) zezwoliła na głęboką refleksję (wspomniane setAccessible
) w czasie wykonywania aplikacji z ostrzeżeniami. Sytuacja jednak przez lata ewoulowała, a oficjalne zamienniki dla wewnętrznych klas JDK (jak VarHandle
) zmniejszyły potrzebę łamania enkapsulacji, podczas gdy nowe API uczyniły starsze praktyki przestarzałymi. Wraz z JDK 16, użycie zaczął wymuszać silną enkapsulację w czasie próby dostania się do internali JDK (przynajmniej częściowo, wspomniane wcześniej sun.misc.Unsafe
pozostało dostępne), zamieniając ostrzeżenia w błędy.
Mimo wielu kroków naprzód, zakładany poziom integralność w platformie Java nie został jeszcze osiągnięty, właśnie z powodu braku powszechnej silnej enkapsulacji. Niektóre API, takie jak sun.misc.Unsafe
czy JNI, umożliwiają naruszenie enkapsulacji i integralności systemu. W rezultacie analiza bezpieczeństwa aplikacji i jej zależności staje się trudna, a aktualizacja wersji JDK może być problematyczna. Aby osiągnąć integralność, planowane jest stopniowe ograniczenie tych API i zamknięcie luk w przyszłych JEPach, co wymagać będzie od bibliotek dostosowania się do zmian. Stąd właśnie ten JEP – twórcy chcą, żeby całe community zdawało sobie sprawę z ich planów i mogło się na nie przygotować, a także dyskutować nad konsekwencjami.
Na koniec warto się zastanowić – skoro tyle czasu sobie bez tej silnej enkapsulacji radziliśmy, to dlaczego wzmożone zainteresowanie tym tematem? Okazuje się, że powodem jest to, że coraz większa część środowiska uruchomieni owego Javy jest pisana w Javie, więc sami jej twórcy mają coraz większą potrzebę silnej enkapsulacji. Utrzymanie JDK było utrudnione przez przestarzałe pakiety, a migracja między wersjami JDK stawała się problematyczna. Mimo supermocy, jaką brak integralności oferował niektórym bibliotekom, sytuacja jest nie do utrzymania.
To był taki mój TLDR – Oczywiście, jak to zawsze bywa w przypadku JEP-ów Rona Presslera, całość zawiera masę dodatkowych szczegółów, które w moim przykrótkim opisie musiałem pewnie nieco uprościć. Dlatego też traktujcie całość jako zajawkę, a po wszelkie dodatkowe detale odsyłam do JEP draft: Integrity and Strong Encapsulation.