Что может быть проще? Пишем команду сборкиgradle clean build
и все готово. На первый взгяд все действительно так, и займет это немного время. Но со временем кодовая база и, соответственно, количество тестов (ну я очень на это надесь) будет расти, вы не успеете опомниться как сборка будет у вас занимать уже 10 или больше минут
Давайте подумаем что нам может в этом помочь? А для этого проанализируем основные шаги этого процесса:
Разрешение зависимостей на внешние библиотеки
Компиляция Java кода
Прогон тестов
Бьем проект на модули
А так же вспомним (или узнаем) про такую фичу gradle как build cache. Как она может нам помочь? Ведь по факту, если мы внесем в java код какое либо измение, это вызовет перекомпиляцию кода и прогон всех тестов. В этом случае нам может помочь модульность проекта. Если у нас код довольно сильно сегментирован (например по доменным моделям), и зачастую в рамках выполнения задач мы вносим изменения только в какую либо часть проекта - то нет необходимости перекомпилировать весь проект и выполнять абсолютно все тесты.
Например у нас какой либо проект интернет магазина, у него есть несколько основных доменов:
account (клиенты)
product (товары)
orders (заказы, этот модуль уже в свою очередь зависит от обоих модулей account и product)
Например вы вносите изменения в
модуль account - это вызовет перекомпиляцию модуля account и orders (как зависимого) и выполнит в них тесты. Модуль product не нужно перекомпилировать и выполнять в нем тесты, т.к. код не менялся
модуль orders - это вызовет перекомпиляцию только этого модуля и выполнение его тестов. Модули account и product остаются не тронутыми - профита тут уже гораздо больше!
Итак, наш скрипт запуска уже выглядит так: gradle clean build --build-cache
Запуск на CI
Окей, проверили локально - все супер, повторный запуск команды отрабатывает за секунды, отпрявлям все это на Gitlab CI. И что в итоге видим? А никакого профита нет. Конечно же, каждая джоба выполняется в изолированном контейнере, и в нем нет никаких кэшей, каждый раз выполянется загрузка всех зависимостей с Maven Cental, каждый раз проект компилируется, и каждый раз выполняются все тесты.
Нужно как то переносить "артефакты" между сборками. Тут нам может помочь Gitlab Cache. Окей, инструмент у нас нас есть, теперь нужно определиться, что же мы хотим переносить.
Кэшируем зависимости
Загруженные зависимости по умолчанию хранятся в ${USER_HOME}/.gradle
build:
stage: build
image: gradle:7.2-jdk17
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle_home
script:
- gradle clean build check --stacktrace --info --build-cache
cache:
- key: dep-cache
paths:
- .gradle_home
Теперь каждый раз не будут загружаться зависимости, а будут кэшироваться (скорее всего в s3, смотря как настоен ваш Gitlab). Это хоть и не бесплатно, как локальное хранение, но все равно быстрее чем походы во внешнюю сеть.
Кэшируем результаты билда
Дальше нам нужно как то кэшировать результаты компиляции проекта и результатов тестов. Артефакты лежат по умолчанию в папке build
в каждом модуле. Прописывать кажду папку вручную не вариант, мы же программисты. Давайте изменим расположение buildDir в каждом моделе. Для этого внесем изменения в корневой build.gradle
ext {
set('rootProjectDir', "${projectDir}")
}
allprojects {
buildDir = "${rootProjectDir}/.build/${project.name}/build"
}
и добавляем папку .build
из корня проекта под кэш.
cache:
- key: build-cache
paths:
- .build
На данном шаге мы должны уже получить какой то профит от проделанной работы.
Немного про TestContainers
Давайте рассмотрим еще случай - например использование TestContainers для докеризации окружения в тестах. Здесь есть момент - что когда мы находимся внутри контейнера при запуске тестов мы будем каждый раз загружать образы для внутренних контрейнеров. Если у вас есть корпоративный docker hub (кэширующий прокси), то мы можем подсказать путь для более быстрой загрузки из него:
build:
stage: build
image: gradle:7.2-jdk17
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle_home
- export TESTCONTAINERS_RYUK_CONTAINER_IMAGE=my-docker-hub.io/testcontainers/ryuk:0.3.3
script:
- gradle clean build check --stacktrace --info --build-cache
Либо же пишем код ручками.
P.S. Что не надо бить монолит на модули, а разносить на микросервисы писать замечания не стоит - цель была показать возможность конфигурации buildDir