Hej, dzisiaj znowu przybliżę Ci jeden z elementów mojego projektu – My Coach. Każdy trochę bardziej zaawansowany projekt korzysta z systemu do automatyzacji buildów i dodawania zależności do projektu. W świecie Javy są dwa popularne narzędzia – Maven oraz Gradle. Kiedyś popularny był Ant, ale już nie jest on wykorzystywany. Osobiście korzystam z Gradle’a i na nim się skupię.
Gradle – krótkie wprowadzenie
O Gradle’u wspominałem już trochę przy okazji opisania wdrażania SonarQube’a do projektu. Przewiduję w przyszłości napisać osobny post o Gradle’u, więc tutaj nie będę się zbytnio rozpisywał. Tak jak wspomniałem na początku, jest to narzędzie do automatyzacji buildów. Charakteryzuje się tym, iż jego konfiguracja nie odbywa się dzięki plików XML jak to często się spotyka, a dzięki zmodyfikowanego pod potrzeby projektu Groovy’ego. Ładnie nazywa się to Gradle DSL.
Jeżeli chodzi o same użycie Gradle’a w projekcie to nie jest to nic skomplikowanego. Wystarczy mieć Gradle’a zainstalowanego na swoim PC oraz – przynajmniej w większości przypadków – mieć stworzony plik w głównym folderze danego modułu/projektu, który ma dość czytelną nazwę – build.gradle. Stworzenie pliku jest rzeczą banalną, raczej nie muszę mówić, jak to zrobić. Samą strukturę tego pliku dokładniej opiszę zaraz na przykładzie swojej aplikacji. o ile chodzi o instalację to jest to nic innego jak ściągnięcie ze strony producenta odpowiedniej paczki oraz, gdy system sam nie ogarnie, dodanie zmiennej środowiskowej, która wskaże gdzie ten Gradle się znajduje. Dzięki temu będziemy mogli łatwo uruchamiać nasze skrypty.
Sam proces instalacji jest świetnie opisany w dokumentacji.
Jak używam Gradle’a
W moim przypadku użycie Gradle’a jest bardzo standardowe. Posiadam go w tej chwili w wersji 3.2.1. Z racji, iż mój projekt posiada jeden moduł, umiejscowienie konfiguracji jest w głównym folderze (możesz zobaczyć to wchodząc do repozytorium) w pliku build.gradle.
Możesz zauważyć też plik gradle.properties. Cóż to jest? Jest to plik, w którym możemy nadpisywać część parametrów domyślnych Gradle’a. Możemy też tutaj definiować zmienne, których możemy użyć w plikach konfiguracyjnych. U mnie znajduje to zastosowanie w przede wszystkim w tym drugim przypadku:
Na początku jest jedyny przypadek tego pierwszego użycia. Ustawiam tutaj argumenty dla JVM’ki z której Gradle korzysta. W tym przypadku są to kolejno początkowy oraz maksymalny rozmiar heapu (z ang. sterta, ale heap myślę brzmi lepiej).
Następny akapit to nic innego jak podefiniowane zmienne, których używam w build.gradle. Są to wersje konkretnych bibliotek. Nie potrzebuję przeszukiwać całego pliku z konfiguracją i skryptami. Wszystkie wersje mam w jednym miejscu. Polecam stosować tą praktykę. Jak widzisz, używam między innymi bibliotek Springa, bazy danych PostgreSQL lub H2 (w zależności od potrzeb) oraz innych mniejszych narzędzi.
Ostatni “akapit” to ustawienia dla jednego z pluginów którego używam w Gradle’u. Jest to plugin dla SonarQube’a. Zmienne te są wymagane przez plugin, a ja dostosowałem je do swojego serwera. Więcej o tym możesz przeczytać w poście o SonarQube.
build.gradle – struktura pliku
Tutaj opiszę wszystkie fragmenty mojego pliku po kolei. Plik w całości możesz zobaczyć tutaj.
Zacznijmy od zdefiniowania czego potrzebujemy użyć dla skryptów budujących. Ta część kodu jest oznaczona jako buildscript. Definiuję tam repozytoria (repositories) oraz zależności, z których korzystam (dependencies). W repozytoriach zdefiniowane jest główne repozytorium maven’owe do którego możemy się odwołać poprzez funkcję mavenCentral(), która jest zaszyta w Gradle’u. Następne repozytorium do którego się odwołuje to repozytorium z pluginami Gradle’a, które definiuję w następnym bloku.
W dependencjach definiuję nazwy dwóch pluginów, które nie są domyślnie dostępne i potrzebują być zdefiniowane. Są to kolejno pluginy dla Spring Boot’a oraz SonarQube Scanner’a. Jak widzisz, definiuję tu dla wszystkich grupę, do której należy, jego nazwę, oraz wersję. o ile się przyjrzysz, wersje nie są stringami w tym przypadku, a zwykłym ciągiem znaków. Tak się składa, iż w gradle.properties mam dwie takie zmienne zdefiniowane. Stamtąd więc te wersje są brane i wstawiane do kodu.
Następna część odwołań nie jest zdefiniowana w żadnym bloku, tak więc postanowiłem omówić je wspólnie. Pierwsze cztery odwołania mówią, jakich pluginów Gradle ma dokładnie użyć. Są to odpowiednio plugin dla Javy (java), IntelliJ IDEA (idea), Spring Boot’a (spring-boot) oraz SonarQube Scanner’a (org.sonarqube).
Dzięki użyciu Javy mamy możliwość tworzenia poprzez Gradle’a Javowych archiwów, czyli popularnych jarów (pliki z rozszerzeniem *.jar). W tym celu możemy w bloku jar zdefiniować dla niego między innymi takie atrybuty jak grupa, bazowa nazwa archiwum oraz wersja tego archiwum. Nazwy i grupy raczej się nie zmieniają, natomiast z czasem jak aplikacja się rozwija, naturalnie numer wersji też ulega zmianie. Następną taką zmianę przewiduję, gdy aplikacja będzie zdatna do użytku. Zniknie wtedy ten sympatyczny napis “SNAPSHOT”.
Dwa ostatnie parametry są ściśle związane z Javą. sourceCompatibility mówi, jakiej wersji Javy używamy do kompilacji, natomiast targetCompatibility mówi jakiej wersji Javy używamy do generowania klas. zwykle obie wersje są takie same. Warto zaznaczyć, iż defaultowo wartość tego drugiego to wartość sourceCompatibility. W moim przypadku więc ta konfiguracja jest redundantna, aczkolwiek wolę to widzieć na własne oczy.
Ponownie możesz zobaczyć blok repositories. Jaka jest różnica? On nie jest definiowany w buildscript. Znaczy to tyle, iż repozytoria zdefiniowane w tym miejscu to repozytoria do których Gradle będzie się odwoływać, gdy będzie szukać bibliotek do naszego projektu. Sama zasada jest podobna jak wcześniej. Używam tutaj dwóch repozytoriów zdefiniowanych jako funkcja, czyli repozytorium jCenter oraz głównego repozytorium mavenowego. Ponadto, zdefiniowane są linki do trzech repozytoriów spring’owych. Łatwo się zorientować po ich linkach, które jakie biblioteki posiada.
W bloku sonarqube definiuje kolejne parametry dla SonarQube scannera. Niestety, część parametrów musi być zdefiniowana globalnie w pliku application.properties, a część w pliku build.gradle. Tutaj podaję nazwę i klucz projektu. Ponownie zapraszam do posta o SonarQube po więcej szczegółów.
Blok dependencies to blok z którego możemy się dowiedzieć jakie biblioteki są używane w projekcie. Przed definicją każdej z bibliotek definiujemy, czy będzie używana ona podczas kompilacji (compile) lub w czasie wykonania (runtime). Możemy je też poprzedzić słówkiem test i zapisać w postaci camelCase’a. Oznacza to, iż wtedy używamy bibliotek tak jak wcześniej, z tą różnicą, iż tylko podczas testów aplikacji. Stąd biblioteki takie jak jUnit lub Mockito są definiowane jako testCompile. Dalsza definicja zależności jest analogiczna do tych jak w buildscript. Jak się przyjrzysz to w każdym przypadku wersje bibliotek też są definiowane jako zmienna, której wartość jest ustawiana w application.properties.
Jeżeli chodzi o biblioteki, możesz zauważyć, iż dominują tu te z rodziny Spring’a. Używam też Swagger’a do łatwej dokumentacji API oraz używania end point’ów w jednym miejscu. End pointy to adresy do których możemy się odwołać z aplikacji klienckich. Pod tymi adresami zwykle “triggerujemy” wykonanie konkretnej funkcjonalności, która zwraca potrzebne dane do klienta. Generalnie bardzo polecam Swagger’a, dużo potrafi uprościć podczas developmentu. Myślę, iż też o tym co nieco wspomnę w przyszłości. Kolejne trzy biblioteki są typowo bazodanowe. Liquibase jest narzędziem do wersjonowania bazy danych i tworzenia osobnych list ze zmianami. W teorii definiujemy XML/JSON/YAML raz, a działa to na każdym silniku bazodanowym. W praktyce jest z tym różnie, ale zawsze możemy zrobić małe rozróżnienie w skrypcie, w zależności od tego jaki silnik bazodanowy jest w użyciu. H2 i PostgreSQL to silniki, których używam w swojej aplikacji.
Na koniec definiuję jeszcze Lomboka, który pozwala dzięki adnotacji uniknąć tzw boilerplate’u (czyli względnie dużej ilości kodu dla prostej i podstawowej funkcjonalności). Przykładowo, taka bazowa klasa w Javie ma kilka pól. zwykle musimy do każdej z nich dopisywać/generować dzięki IDE gettery, settery, hashCode’y itd. Lombok pozwala na zastąpienie pisania tego kodu poprzez dodanie do definicji klasy odpowiedniej adnotacji. Przykładowo, adnotacja @Getter zapewnia, iż lombok doda nam podczas kompilacji gettery dla pól. Oczywiście, możliwości jest wiele więcej. Ostatnia zależność jest używana do testów. Jest to paczka spring’owa z różnymi bibliotekami, m. in. wspomniany wcześniej jUnit lub Mockito.
Całość pliku build.gradle prezentuje się więc następująco:
Podsumowanie
Cóż, to by było na tyle. Jak widzisz, nie jest to wcale takie trudne. Podstawowa definicja to dosłownie kilkadziesiąt linii. Dzięki temu nie musimy “zaśmiecać” repozytorium plikami z bibliotekami, martwić się o odpowiednie wersje, a ponadto możemy łatwo część rzeczy zautomatyzować poprzez swoje skrypty lub zewnętrzne pluginy. o ile widzisz, iż mógłbym zrobić coś lepiej, bo dobrze znasz Gradle’a, zachęcam do uwag. o ile go natomiast nie znasz to mam nadzieję czytelniku, iż dzisiaj trochę się o nim dowiedziałeś i zachęciłem Cię do sprawdzenia tego potężnego narzędzia.