„Czym właściwie jest ten projekt?”
Projekt w mojej obecnej firmie to coś, co można nazwać „frankenstackiem”. MyBatis i JPA współistnieją w jednym projekcie. Problem polegał na tym, że wszyscy „stwórcy”, czyli pierwotni członkowie zespołu, którzy napisali ten kod, dawno już odeszli z firmy.
My, którzy zostaliśmy, czyli obecny zespół, byliśmy zdezorientowani. Nie było ani jednego dokumentu wyjaśniającego, według jakich zasad te technologie zostały wymieszane. Przez to sytuacja robiła się coraz bardziej chaotyczna.
W efekcie w ramach jednej usługi jedni grzebali w XML-u, żeby zrobić podobną funkcję, a inni projektowali encje. Całość zamieniła się w dziwny stan „technicznej anarchii”. Projekt coraz bardziej przeistaczał się w potwora trudnego do utrzymania, aż w końcu zwołano spotkanie.
„Ujednolićmy to na jednym stacku technologicznym.”

„Przecież JPA jest za wolne, prawda?”
Atmosfera na spotkaniu była napięta. Szczególnie koledzy przyzwyczajeni do MyBatis mocno sprzeciwiali się ujednoliceniu wszystkiego na JPA. Ich głównym argumentem była wydajność.
„Nie pamiętacie tej strony ze statystykami napisanej w JPA, która ładowała się 20 sekund? Gdybyśmy napisali SQL ręcznie, wynik byłby w sekundę. Poleganie na czarnej skrzynce, nad którą nie mamy pełnej kontroli, jest ryzykowne.”
Rzeczywiście, niektóre fragmenty logiki potrzebowały kilkudziesięciu sekund tylko na załadowanie danych. Ale byłem przekonany, że to nie problem technologii, tylko poziomu opanowania narzędzia. Na miejscu otworzyłem laptop i wyświetliłem słynny „20-sekundowy kod”.
Kod był fatalny. Pobierał List<Entity>, ale za każdym razem, gdy pętla dotykała powiązanego obiektu, baza danych była odpytywana ponownie, po jednym rekordzie. To był podręcznikowy problem N+1.
„To nie znaczy, że JPA jest wolne. To znaczy, że my kazaliśmy JPA pracować nieefektywnie. Zobaczcie.”
Od razu zastosowałem Fetch Join i sprowadziłem zapytanie do jednego uderzenia. Po wdrożeniu czas ładowania spadł z 20 sekund do 1 sekundy. To był moment, w którym zobaczyłem, jak zmieniły się miny moich współpracowników.

Nieoczekiwane odkrycie: to nie dziwny mix, tylko hybryda
Skoro wyjaśniliśmy nieporozumienia wokół JPA, to czy należało po prostu wszystko ujednolicić na JPA? Gdy przyjrzeliśmy się temu bliżej, okazało się, że to też nie jest pełna odpowiedź.
Obsługa skomplikowanych zapytań statystycznych albo eksportów Excela liczących setki tysięcy wierszy przez JPA była niewygodna: koszt mapowania obiektów był zbyt duży, a pisanie dynamicznych zapytań było kłopotliwe. Z kolei MyBatis w takich zadaniach był zdecydowanie bardziej intuicyjny i szybszy. Zacząłem się zastanawiać: „Jak rozwiązują ten problem inne firmy?” Z frustracji spędziłem noc na Google i technicznych blogach.
Ku mojemu zaskoczeniu wiele firm technologicznych wcale nie upierało się przy samym JPA. Niektóre używały QueryDSL do złożonych, wydajnych odczytów, a inne, tak jak my, korzystały równolegle z MyBatis.
Wtedy wszystko zaskoczyło. „A więc ci, którzy odeszli, wcale nie zmieszali tego bezmyślnie.”
Oni już znali mocne i słabe strony każdego narzędzia i świadomie rozdzielili odpowiedzialności między technologie. Prawdziwy problem polegał na tym, że odeszli bez udokumentowania tych decyzji, więc my, którzy przejęliśmy system, zaczęliśmy się kłócić o to, dlaczego nie został ujednolicony.
Poza ograniczenia JPA: QueryDSL i Projection
Pojawiło się więc kolejne pytanie: czy da się usunąć MyBatis i rozwiązać ten problem wyłącznie technologiami z ekosystemu JPA? Właśnie tutaj wchodzą QueryDSL i Projection.
// 1. Interfejs deklarujacy tylko potrzebne pola (bez DTO)
public interface DailyStat {
String getDate();
Long getTotalSales();
}
// 2. Pobierz przez QueryDSL lub JPA Repository
// Z bazy pobierane sa tylko te dwie kolumny (SELECT), wiec jest tak szybkie jak MyBatis.
List<DailyStat> stats = repository.findDailySales();
Gdy poznałem te technologie, powodów do wracania do XML-owego piekła MyBatis było znacznie mniej. Okazało się, że także w ekosystemie JPA da się pisać naprawdę wydajne zapytania.
Praktyczna rada: zamiast walczyć, pozwól im współistnieć
Ostatecznie nasz zespół wybrał nie „bezwzględne ujednolicenie”, ale „współistnienie oparte na zasadach”. (A długofalowo celowaliśmy w przejście na QueryDSL.)
Co ciekawe, ta strategia przetrwania, którą wybraliśmy, czyli „JPA do zapisu, wyspecjalizowane narzędzie do odczytu”, z perspektywy architektury jest bardzo podstawową formą wzorca CQRS(Command and Query Responsibility Segregation).
Nie trzeba od razu spektakularnie dzielić bazy danych. Już sama filozofia rozdzielenia odpowiedzialności za komendy i zapytania na poziomie kodu sprawia, że projekt staje się znacznie czystszy.
Na koniec: technologia nie jest winna
Często spieramy się, mówiąc: „JPA jest najlepsze” albo „Nie, w praktyce to MyBatis wygrywa”. Ale to, czego nauczyłem się z tego doświadczenia, to fakt, że nie ma złych technologii. Są tylko sytuacje, w których technologia zostaje użyta do niewłaściwego zadania.
JPA jest jak magiczna różdżka, ale jeśli źle wypowiesz zaklęcie, wszystko wybuchnie. MyBatis jest jak solidny młotek, ale może sprawić, że wszędzie będziesz widzieć gwoździe.
Jeśli twój zespół właśnie zmaga się z konfliktami przez legacy code albo przez różne preferencje technologiczne, zatrzymajcie się na chwilę. Być może w tym chaosie ukryte jest głębokie przemyślenie wcześniejszych developerów. Odkryć je i zamienić w jasne zasady, właśnie tak buduje się naprawdę mocny zespół developerski.