JobRunr — используем для сложных интеграций

JobRunr — используем для сложных интеграций

Иногда приходится интегрироваться с системами, которые работают в асинхронном режиме, но при этом выставляют API с синхронными вызовами.

Часто возникает такая последовательность действий:

① Отправляем запрос, получаем request_id и время ожидания.

② Ожидаем заданное время.

③ Через некоторое время вызываем метод для получения статуса запроса по request_id, здесь можем либо получить подтверждение выполнения, либо опять уйти в ожидание (переходим в ② и потом опять в ③).

④ Получаем статус выполнения запроса, понимаем, что нужно отправить дополнительные данные.

⑤ Отправляем дополнительные данные, request_id и время ожидания.

⑥ И так далее.

Схематично диаграмма взаимодействия будет выглядеть так:

JobRunr — используем для сложных интеграций

В этих случаях для реализации планирования задач отлично подойдет JobRunr.

Пример использования JobRunr

Сценарий: наш сервис должен создавать пользователей в другой системе, которая создает записи о пользователях с некоторой задержкой.

  • Мы отправляем запрос для создания записи пользователя и переходим в режим ожидания.
  • Через некоторое время пытаемся получить статус нашего запроса. Если запись не создана, продолжаем ожидать.
  • По прошествии некоторого времени мы получаем результат запроса и фиксируем его в БД.

Код примеров доступен здесь, также в корневом каталоге файл main.py с сервисом, имитирующим создание пользователя через некоторый промежуток времени.

Подключаем и конфигурируем JobRunr

Для подключения к spring-boot проекту JobRunr нужно добавить библиотеку и spring-boot-starter в pom.xml:

... <dependency> <groupId>org.jobrunr</groupId> <artifactId>jobrunr</artifactId> <version>7.0.0</version> </dependency> <dependency> <groupId>org.jobrunr</groupId> <artifactId>jobrunr-spring-boot-3-starter</artifactId> <version>7.0.0</version> </dependency> ...

Теперь осталось только настроить свойства в application.yaml:

# для spring-boot 3 используется jobrunr-spring-boot3-starter там jobRunr 7 версии, поэтому нужно через org. задавать свойства org: jobrunr: # Указываем, что используем sql СУБД, JobRunr автоматом подцепит datasource database: type: sql # создаение необходимых табличек skip-create: false job-scheduler: # без этого scheduller не работает enabled: true # без этого задачи не будут выполняться, нуэно включить функционал сервера background-job-server: enabled: true worker-count: 5 # Включаем дашборд для анализа текущих задач dashboard: enabled: true port: 8081

На этом все, JobRunr готов к работе.

Запускаем отложенную задачу

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

@Service public class UserCreationService { private static final Logger log = LoggerFactory.getLogger(UserCreationService.class); private final UserCreationRequestRepository repository; private final ExternalApiClient externalApiClient; private final JobScheduler jobScheduler; private final UserCreationStatusHandlingService statusCheckJob; private final int initialDelaySeconds; public UserCreationService( UserCreationRequestRepository repository, ExternalApiClient externalApiClient, JobScheduler jobScheduler, UserCreationStatusHandlingService statusCheckJob, @Value("${app.poll.initial-delay-seconds}") int initialDelaySeconds) { this.repository = repository; this.externalApiClient = externalApiClient; this.jobScheduler = jobScheduler; this.statusCheckJob = statusCheckJob; this.initialDelaySeconds = initialDelaySeconds; } public UserCreationResponse createUser(CreateUserRequest request) { UserCreationRequest userCreationRequest = new UserCreationRequest(); userCreationRequest.setName(request.getName()); userCreationRequest.setEmail(request.getEmail()); userCreationRequest.setLocalStatus(UserCreationRequest.LocalStatus.NEW); userCreationRequest.setAttemptCount(0); userCreationRequest = repository.save(userCreationRequest); log.info("Creating user with name={}, email={}", request.getName(), request.getEmail()); ExternalCreateUserResponse externalResponse = externalApiClient.createUser(request.getName(), request.getEmail()); userCreationRequest.setExternalTaskId(externalResponse.getTaskId()); userCreationRequest.setExternalStatus(externalResponse.getStatus()); userCreationRequest.setLocalStatus(UserCreationRequest.LocalStatus.PENDING); userCreationRequest.setNextCheckAt(LocalDateTime.now().plusSeconds(initialDelaySeconds)); repository.save(userCreationRequest); final Long requestId = userCreationRequest.getId(); jobScheduler.schedule(Instant.now().plusSeconds(initialDelaySeconds), () -> statusCheckJob.checkStatus(requestId)); return UserCreationResponse.fromEntity(userCreationRequest); } public Optional<UserCreationRequest> findById(Long id) { return repository.findById(id); } public List<UserCreationRequest> findAll() { return repository.findAll(); } }

Помимо schedule, в классе JobScheduler есть множество других методов для создания задач:

  • enqueue — добавляет задачу в очередь на обработку.
  • createRecurrently — создает повторяющуюся задачу.

Важные моменты production-ready

  • Сериализация параметров: JobRunr использует Jackson. Убедитесь, что все аргументы методов с @Job сериализуемы.
  • Обработка идемпотентности: обязательно нужно реализовывать функционал так, чтобы обработка задач была идемпотентной.
  • Мониторинг через Actuator: для прод сред включайте мониторинг — management.endpoints.web.exposure.include: ..., metrics, jobrunr.
  • Ограничение ретраев для критичных задач, иначе задачи не будут выполняться вовсе или будут выполняться дольше, чем нужно.

Что мы получили в итоге?

  1. Надежность: Если ваш сервис упадет в процессе, после перезагрузки JobRunr увидит незавершенную задачу в PostgreSQL и запустит её снова.
  2. Масштабируемость: Вы можете запустить 10 экземпляров этого приложения. Благодаря PostgreSQL, задача выполнится ровно один раз тем узлом, который первым её возьмет.
  3. Визуализация: Перейдите на localhost:8081, и вы увидите красивый дашборд со всей статистикой.

Пример интерфейса дашборда:

JobRunr — используем для сложных интеграций

Итоги

JobRunr — это идеальный баланс между простотой и мощностью. Он избавляет от необходимости писать свои велосипеды для синхронизации потоков и дает полный контроль над фоновыми процессами.

Использовали уже JobRunr в своих проектах или храните верность Quartz? Пишите в комментариях!

Подпишись на мой канал в telegram

2 комментария