Framework

Czy Javalin może zastąpić Spring Boot na produkcji?

Czy Javalin nadaje się do wykorzystania w aplikacjach produkcyjnych? Czy jest lepszy od Spring Boot? Jakie są jego wady i zalety? Spróbuję odpowiedzieć na te i podobne pytania na podstawie mojej produkcyjnej przygody z tym frameworkiem.

Co to jest Javalin?

W skrócie Javalin to prosty i lekki framework przeznaczony dla Javy oraz Kotlina. Domyślnie działa w sposób blokujący, ale łatwo można go skonfigurować do asynchronicznej pracy.

Dla tych z was, którzy jeszcze o nim nie słyszeli zachęcam do zerknięcia na stronę projektu oraz na githuba.

Co Javalin robi dobrze?

Kilka rzeczy, dla których warto wybrać ten framework.

Jest prosty!

Javalin app = Javalin.create()
    .port(8090)
    .start()
    .get("/", ctx -> ctx.result("Hello World"));

Te kilka linijek wystarczy, aby wystawić prosty endpoint zwracający komunikat „Hello world”. Powyższy przykład oczywiście jest banalny, ale nawet konfiguracja bardziej złożonej logiki nie stanowi wyzwania.

Jest lekki!

O lekkości Javalina już na samym początku może świadczyć dość szybki start aplikacji trwający kilkaset milisekund. Dla porównania aplikacja „Hello World” w Spring Boot uruchamia się w granicach dwóch sekund.

Dodatkowo waga wygenerowanego jara dla najprostszej aplikacji to zaledwie 3 KB gdzie analogiczna funkcjonalność zaimplementowana w Spring Boot tworzy paczkę ważącą około 16 MB.

Szybki start oraz niska waga aplikacji jest spowodowana minimalną liczbą zależności w tym projekcie. Ma on jedynie dwie: Jetty jako wbudowany serwer aplikacji oraz SL4J służący jako fasada do logowania. Dzięki takiemu podejściu nie zaśmiecamy projektu niepotrzebnymi zależnościami.

Jest napisany w Kotlinie!

Niezależnie czy Javalina chcemy użyć w projekcie Javowym czy Kotlinowym, korzystamy z tej samej wersji biblioteki. Nie musimy używać żadnych portów czy korzystać ze specjalnych ustawień konfiguracyjnych specyficznych dla danego języka. Jest to szczególnie zaletą, kiedy w stosie technologicznym naszego projektu są oba te języki.

Ciemna strona Javalina

Jak każdy framework, Javalin ma oczywiście też wady.

Jest prosty!

Łatwość w podstawowej konfiguracji ma niestety swoje ograniczenia. W przypadku bardziej zaawansowanych czy skomplikowanych ustawień możemy się spotkać z brakiem danej opcji. W takich wypadkach zostaje nam próba obejścia problemu lub zgłaszanie pull requestu na githubie. Na szczęście z uwagi na aktywność projektu ta druga opcja jest jak najbardziej do zrealizowania.

W sekcji „Przypadki z produkcji” podaję kilka przykładów, które w projekcie w którym pracuję sprawiły pewne problemy.

Jest lekki!

Szybki start aplikacji oraz niska waga wynikowej paczki w porównaniu do Spring Boot nie bierze się znikąd. W przeciwieństwie do Springa, w którym mamy „miliard” zależności w standardzie, Javalin ma ich jedynie dwie. Niestety z tego powodu wszystkie popularne mechanizmy musimy ręcznie dodać zewnętrznymi bibliotekami lub sami zaimplementować.

Jest napisany w Kotlinie!

Dla niektórych to oczywiście będzie zaleta, szczególnie jeśli ktoś miał już większą styczność z tym językiem. Jednak dla mnie oraz zapewne dla większości ludzi, którzy takiej okazji nie mieli jest to pewna niedogodność. Jest to o tyle problematyczne, że czasami zdarza się, że jednak potrzebujemy przedebugować się przez kod frameworka.

Jest niszowy!

Może to zbyt dosadnie powiedziane. Mimo, że na obecną chwilę ma ponad 4 000 „gwiazdek” na githubie to jednak jest to ponad 10 razy mniej niż posiada Spring Boot. Przez te dysproporcje społeczność Javalin nie jest na tyle duża, aby łatwo można było znajdować rozwiązania problemów na stackoverflow czy blogach programistycznych.

Popularność repozytorium Javalin na githubie

W takim wypadku jesteśmy skazani na oficjalną dokumentację (która na szczęście jest całkiem przejrzyście napisana) lub dyskusję na repozytorium projektu. Zostaje nam jeszcze opcja debugowania się przez kod, ale trzeba pamiętać, że jest on napisany w Kotlinie.

Przypadki z produkcji

Kilka przypadków, które mogą sprawić nieco problemów lub wymuszają pewne podejście. Wynikają one z braku wbudowanych zależności lub ograniczeń konfiguracyjnych.

Problemy z mapowaniem i walidacją formularzy

W Javalin oczywiście mamy dostępny mechanizm mapowania requestu na konkretną klasę czy walidację zarówno pojedynczych pól jak i całych formularzy. Jednak jest to dość prosty mechanizm, który w ograniczony sposób może być konfigurowany. W ostateczności sprawdza się on do zwrócenia błędu HTTP 400 z pierwszym napotkanym błędem.

MyObject myObject = ctx.bodyValidator(MyObject.class)
        .check(obj -> obj.myObjectProperty == someValue)
        .get();

W Przypadku gdybyśmy potrzebowali zwrócić wszystkie błędy walidacji oraz niekoniecznie za pomocą przerzucania błędu HTTP 400 na front, to musimy napisać własny mechanizm.

Brak dependency injection

Nie jest to problem, ponieważ istnieje co najmniej kilka solidnych bibliotek, które oferują nam wstrzykiwanie zależności. Warto jednak przemyśleć wybór podejścia już na samym początku projektu, ponieważ na późniejszym etapie zwyczajnie będziemy mieli zbyt wiele rzeczy do refaktorowania.

Zbyt prosty mechanizm uwierzytelniania

Jeżeli tworzymy aplikację webową, która ma obsługiwać użytkowników na wielu różnych poziomach to potrzebujemy solidnego mechanizmu ról i uprawnień. Javalin oczywiście ma wbudowany taki mechanizm pod nazwą Access manager. Jest to jednak bardzo podstawowe rozwiązanie w porównaniu do tego co oferuje nam np. Spring Security. Nie jest to oczywiście jakiś duży problem, bo jak zawsze możemy skorzystać z dowolnego innego rozwiązania. Warto jednak o tym pamiętać i na samym początku powstawania projektu o tym zdecydować.

config.accessManager((handler, ctx, permittedRoles) -> {
    MyRole userRole = getUserRole(ctx);
    if (permittedRoles.contains(userRole)) {
        handler.handle(ctx);
    } else {
        ctx.status(401).result("Unauthorized");
    }
});

Obsługa plików statycznych

W marcu 2020 roku kiedy ruszaliśmy z projektem potrzebowaliśmy serwować pliki statyczne pod określonym adresem. Niestety wtedy nie było takiej możliwości. Po zgłoszeniu problemu na githubie w ciągu dwóch miesięcy udało się uzyskać taką funkcjonalność. W przypadku gdyby to była krytyczna funkcjonalność raczej był by to zbyt długi okres oczekiwania, jednak z drugiej strony podejrzewam, że prośba o brakującą funkcjonalność w Spring Boot mogłaby zająć zdecydowanie dłużej.

No to w końcu można użyć Javalina na produkcji czy nie?

Jak w większości przypadków zawsze należy dobrać narzędzia do danego problemu, a nie odwrotnie. O ile masz świadomość ograniczeń Javalina i jesteś je w stanie zaakceptować oraz masz czas na implementację brakujących mechanizmów, to zdecydowanie tak!

Javalin na pewno zdecydowanie najbardziej nada się do aplikacji serwujących jedynie API, gdzie nie potrzebujemy zaawansowanych mechanizmów autentykacji czy walidacji. W tym wypadku dodatkowym smaczkiem jest wbudowana serializacja odpowiedzi do JSON’a.

Przesiadając się ze Spring Boota na pewno szybko odczujecie brak wielu zależności oraz automatycznej konfiguracji. Nie będzie to przeszkadzać w prototypowaniu czy prostych serwisach. W przypadku tworzenia dużego monolitu jednak warto się kilka razy zastanowić czy akurat ten framework to dobry wybór.

Mimo, że sama aplikacja napisana w Javalin wstaje bardzo szybko, to jednak zdolność obsługiwania dużej liczby zapytań jest na podobnym poziomie co Spring Boot, a nawet nieco gorsza. Ogólne wyniki prędkości można porównać na The Benchmarker, gdzie obecnie Javalin zajmuje pozycję 61/203, a Spring Boot 53/203.

Mam nadzieję, że jeżeli w przyszłości zadasz sobie pytanie: „Czy do mojego nowego projektu użyć Javalin?” to ten artykuł pomoże ci w odpowiedzi na nie.

Join the conversation

  1. Słuszna uwaga. Dla bardziej skomplikowanych aplikacji rzeczywiście może to być czerwone światło. Wydaje mi się jednak, że w przypadku prototypowania czy prostych mikroserwisów jest to ciekawa alternatywa, zwłaszcza, że Javalin jest rozwijany od 2017 roku, więc nie jest to sezonowa świeżynka.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *