Недавно, когда я работал полный рабочий день, мы с моей командой представили новую систему, которая заменила Sonarqube для статического анализа кода. Двумя годами ранее мы начали POC, чтобы узнать, что думают разработчики ING Germany о покупке лицензии для Sonarqube или о том, стоит ли нам представить новый инструмент Teamscale.

POC закончился в прошлом году в начале апреля, и мы решили начать процесс замены Sonarqube на Teamscale. В банках обычно непросто внедрить новые инструменты, поскольку они должны пройти сложный процесс проверки, чтобы убедиться, что инструменты соответствуют правилам, установленным правительством Германии.

Всего два месяца назад мы достигли точки в процессе утверждения, когда наша команда решила заменить текущую интеграцию Sonarqube в одно из наших собственных разработанных приложений и интегрировать Teamscale.

Интеграция с Sonarqube работала следующим образом:

  1. Сборка Jenkins построила приложение и выполнила тесты
  2. После выполнения тестов были созданы отчеты JUnit, отчеты Surefire и отчет JaCoCo.
  3. Сканирование Sonarqube было запущено.
  4. После завершения сканирования плагин maven (также разработанный нами) в сборке отправил запрос в Sonarqube для получения показателей о ранее загруженных отчетах тестирования.
  5. Полученные метрики были отправлены в наше центральное приложение для хранения в базе данных Oracle, а сгенерированные отчеты были отправлены в корзину S3 для архивации.

Поскольку мы заменяем Sonarqube на Teamscale, нам пришлось заменить часть кода в плагине maven, полученную метриками из Sonarqube, и загрузить их в наше центральное приложение.

В прошлом у команд, использующих наш плагин maven, были проблемы с ним, из-за которых их сборки занимали неожиданно много времени, и мы решили, что хотим изменить это с помощью интеграции Teamscale.

Идея

Мы заметили, что узким местом для сборок обычно был наш плагин maven, и решили, что извлечение метрик из инструмента статического анализа кода не несет ответственности плагин, а скорее это ответственность нашего центрального приложения за извлечение метрик, поскольку оно необходимо хранить метрики в БД.

Планируя реализацию другим способом, мы поняли, что можем легко сократить время ожидания сборки и позволить центральному приложению получать требуемые метрики, отправляя событие при загрузке отчетов из подключаемого модуля. Это означало, что нам пришлось ввести в приложение некую обработку событий.

Начало работы с событиями

Мы уже интегрировали некоторую обработку событий в наше приложение, когда разрабатывали функцию уведомлений по электронной почте, когда тест FitNesse не удался. Мы позволили пользователям подписаться на определенный домен, чтобы они получали уведомление по электронной почте, когда тест не удался.

Мы начали с использования событий Spring. Если вы не знакомы с весенними событиями, я кратко расскажу о них.

Spring предлагает разработчикам механизм для создания собственного объекта события и его публикации, когда они хотят, чтобы в приложении что-то произошло. Механизм состоит из трех основных компонентов:

  • Сам класс событий. Класс расширяет предоставленный Spring ApplicationEvent
  • Издатель мероприятия. Издатель публикует ранее созданный объект вашего мероприятия. Издатель должен быть аннотирован аннотацией @Component и содержать автосоединение bean-компонента ApplicationEventPublisher.
  • Слушатель событий. Слушатель ожидает публикации определенного события и после его получения выполняет некоторую дальнейшую обработку. Слушатель расширяет ApplicationListener типом, который будет классом настраиваемого события.

Реализация этого будет выглядеть так:

Мы также реализовали это таким же образом и скорректировали все наши тесты, чтобы принять внесенные нами изменения. Однако мы были недовольны тем, что добавили еще одно поле autowired в наш класс обслуживания только для публикации события, и подумали, что должен быть другой способ сделать это. В конце концов, мы сохраняли сущность перед отправкой события.

Именно тогда мы наткнулись на JPA Events.

JPA События

События JPA позволяют разработчикам создавать слушателей, которые прослушивают события, отправляемые в течение жизненного цикла JPA. Доступные события:

  • @PrePersist
  • @PostPersist
  • @PreRemove
  • @PostRemove
  • @PreUpdate
  • @PostUpdate
  • @PostLoad

Поскольку нам нужно было обновить ранее сохраненные данные, мы создали прослушиватель JPA, который ждал отправки события @PostPersist. Как только событие было получено, мы смогли продолжить некоторую обработку, в нашем случае получение показателей из Teamscale.

Другими вариантами использования могут быть, например, отправка пользователю электронного письма с подтверждением, когда объект пользователя сохраняется в базе данных, или отправка двухфакторного токена на смартфон пользователя. Приложения для использования событий JPA бесконечны, так как в какой-то момент вашего приложения вам все равно нужно сохранить некоторые данные.

Реализация прослушивателя событий JPA довольно проста:

Пользователь аннотируется аннотацией @EventListeners, которой передается класс, который мы зарезервировали для событий JPA, которые влияют на пользователя. У слушателя есть два метода: один аннотирован @PrePersist, а другой - @PostPersist. Методы требуют от пользователя в качестве аргумента, с помощью которого мы можем выполнить некоторую дальнейшую обработку.

Любая из вышеперечисленных аннотаций может использоваться для каких-либо действий, когда JPA что-то требует.

Лично я предпочитаю это решение, поскольку оно очень чистое и не требует для работы множества дополнительных зависимостей. Если он уже предлагается JPA, почему бы не использовать его?

Используя события JPA, мы смогли избавиться от необходимости отправлять событие для получения показателей самостоятельно. Это упростило наш код и уменьшило количество зависимостей, необходимых для подключения к нашему классу обслуживания, что в итоге дало нам более чистый код.

Если вы хотите поэкспериментировать с кодом, посмотрите проект на Github: https://github.com/Felix-Seip/spring-events