Общая часть

Привет, читатель, хочу поделиться своей историей о разработке проекта для сбора фриланс заказов, на данный момент с русских фриланс бирж, реализованных на языке джава, то есть агрегатора. Разработка проекта https://github.com/gdevby/alert-job была начата 15.10.2022, запущенный проект доступен https://aj.gdev.by. Данная статья будет интересна следующим группам людей:

  • Кто хочет попробовать оптимизировать получения уведомлений о новых заказах с фриланс бирж, то есть уменьшить время затраты на эту задачу

  • Кто хочет сравнить свой тестовый проект в области java spring для микросервисной архитектуры.

  • Кто хочет попробовать разработать приложения с микросервисной архитектурой с помощью java spring.

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

Идея проекта появилась из задачи поиска работы на фриланс бирже. Поиск аналогичных проектов на тот момент ни к чему не привел, хотя после реализации мы смогли найти несколько. Так как ежедневная проверка заказов на нескольких биржах и выборка по критериям спустя два месяца утомила, нужна была какая-то автоматизация, чтобы можно было очень гибко настроить и потом уже просматривать заказы, которые с большой долей вероятности подходили бы. В среднем уходило около 20-45 минут каждый день. Сначала я сделал базовое ядро для себя, это было просто консольное приложение, оно настраивалось через файлы настроек, потом захотел поделиться с другими, то есть необходимо полноценное веб приложение, которое будет доступно для каждого с помощью веб браузера.

Каким функционалом должна данная система обладать:

  1. Фильтровать заказы по технологиям

  2. Фильтровать заказы по названию

  3. Фильтровать заказы по описанию

  4. Фильтровать заказы по цене

  5. Должны быть исключающие слова, чтобы отбросить заказы, которые вам не подойдут, например заказ, название которое содержит «для учёбы»

  6. Настройка уведомлений, чтобы не беспокоить в ночное время и выходные

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

  1. В данной системе есть понятие модуль, это вид деятельности, по которому будут собираться заказы, к примеру «Бэк или Мобильная разработка». Он служит, чтобы объединять информацию с нескольких бирж для «Бэк или Мобильная разработка», чтобы указать ключевые слова для данного типа заказов

    Модули
    Модули
  2. Дальше указываем источники - это откуда будут браться заказы, мы можем указать сразу несколько источников (фриланс бирж) и категории для них

    Источники
    Источники
  3. Теперь настраиваем «Фильтр» на то, какие мы будем заказы получить, то есть указываем ключевые слова и слова-минусы, если данное слово содержится в определенном месте (например, только для описания), то этот заказ будет отброшен, так как вы точно знаете, что это не ваш заказ.

    Фильтры
    Фильтры
  4. Так же хотелось бы знать, какие заказы к вам не приходят, чтобы изменить фильтры, то есть иметь быструю связь на изменения в фильтрах

    Заказы вам не подходят
    Заказы вам не подходят
  5. Настройка уведомлений имеет такую форму. В нем можно задать промежутки и дни, когда можно получать уведомления

    Система уведомлений
    Система уведомлений

На этом общее описание функционала и задач подошло к концу, дальше у нас пойдет описание разработки: что и как делали. Мы так же добавляем новые биржи по запросам, на данный момент 5 бирж, хотя проблемы есть с FREELANCEHUNT.COM — не работает, они
деактивировали мой аккаунт, да и нет моей страны в списке, видно, мое происхождение им не нравится.

Разработка

Так как этот проект создавался на свободных началах, как открытый проект, то было решено взять технологии, с которыми мы не работали, чтобы жизнь простой не казалась. В результате было решено написать его в реактивном стиле или хотя бы максимально приблизиться к этому. В первую очередь мы решили попробовать spring cloud, spring webflux, так же решили обкатать протокол SSE https://ru.wikipedia.org/wiki/Server-sent_events, хотя по нему было не так много документации, но он хорошо подходил для связи между двумя сервисами для получения новых заказов. На фронт решили попробовать React JS. В целом микросервисная архитектура для такого типа проекта как сбор заказов не нужна, достаточно было бы реализовать монолитное веб приложение. Но так как у нас много времени и очень сильное желание пробовать новые вещи, мы решились на это, понимая, что некоторые проблемы могут нас преследовать. Это потребует временных затрат в два-три раза больше, чем если бы мы использовали текущие знания и наработки.

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

Модули, из которых состоит приложение, микросервисную архитектуру мы разбили так:

  1. alert-job-config-repo — репозиторий с конфигурацией, храним здесь все конфиги для дев и для прода

  2. alert-job-config — отдает конфигурацию для бизнес логики по http, когда сервис запускатеся он обращается сюда и получает конфигурацию, с которым запустится

  3. alert-job-eureka — для регистрации и связи сервисов

  4. alert-job-gateway — предоставляет единую точку входа запросов

  5. keycloak — отвечает за авторизацию и аутентификацию open authenticaion 2.0 протокол(oauth 2.0)

  6. logstash — отвечает за сбор логов

  7. front — мы использовали react js

  8. notification-alert-job — отвечает за отправку сообщений о заказах

  9. prometheus и grafana - за отображение логов, метрики, и оповещений об ошибках уровня ERROR

  10. Docker — технология для запуска контейнеров

  11. nginx-proxy — вспомогательный контейнер для конфигурации доменных имен и ssl сертификатов.

  12. Parser-alert-job — отвечает за парсинг заказов

  13. core-alert-job — основной бэк для взаимодействия с сайтом

Интересные выжимки из сервисов при решении проблем

  1. Для сбора логов и анализа, мы сперва смотрели на одно из популярных решений - ELK stack, но в ходе изучения столкнулись с тем, что необходима защита сервисов на уровне Kibana, для этого требуется платная лицензия. Выход нашли из ситуации — перенесли эти требования на grafana, там можно получать логи и посылать уведомления.

  2. При работе с sse и webflux через некоторое время у нас перестали поступать заказы с нашего сервиса, это проблема повторилась один раз. Что-то похожее у нас было, когда на уровне подписки возникала ошибка и тогда подписка не восстанавливалась, тогда мы обернули проблемный код try catch, чтобы не убивало подписку webflux, после перезапуска у нас все заработало, пока наблюдаем

	@GetMapping(value = "/stream-sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
	public Flux<ServerSentEvent<List<OrderDTO>>> streamFlruEvents() {
		log.trace("subscribed on orders");
		Flux<ServerSentEvent<List<OrderDTO>>> flruFlux = Flux
				.interval(Duration.ofSeconds(parserInterval)).map(sequence -> ServerSentEvent.<List<OrderDTO>>builder()
						.id(String.valueOf(sequence)).event("periodic-flru-parse-event").data(fl.flruParser()).build())
				.doOnNext(s -> {
					int size = s.data().size();
					context.getBean(COUNTER_FLRU, Counter.class).increment(size);
				});
		...
		return Flux.merge(flruFlux, hubrFlux, freelanceRuFlux, weblancerFlux, freelancehuntOrderParcerFlux);
	}

И сама подписка на заказы

public void sseConnection() {
		ParameterizedTypeReference<ServerSentEvent<List<OrderDTO>>> type = new ParameterizedTypeReference<ServerSentEvent<List<OrderDTO>>>() {
		};
		Flux<ServerSentEvent<List<OrderDTO>>> sseConection = webClient.get().uri("http://parser:8017/api/stream-sse")
				.accept(MediaType.TEXT_EVENT_STREAM).retrieve().bodyToFlux(type)
				.doOnSubscribe(s -> log.info("trying subscribe"))
				.retryWhen(Retry.backoff(Integer.MAX_VALUE, Duration.ofSeconds(30)));
		sseConection.subscribe(event -> {
			try {
				log.trace("got elements by subscription {} size {}", event.event(), event.data().size());
				Set<AppUser> users = userRepository.findAllUsersEagerOrderModules();
				forEachOrders(users, event.data());
			} catch (Throwable ex) {
				log.error("problem with subscribe", ex);
			}
		}, error -> log.warn("failed to get orders from parser {}", error));
	}
  1. При запуске в докере требовалось чтобы порт был прописан, хотя мы считали, что эврика это разрешит. В результате обращались мы к серверу с помощью http://host:port

  2. Elasticsearch не стартовал корректно для logstash, при такой конфигурации. Для корректировки поведения необходимо было изменить права доступа для этой директории, в которую смонтирован elasticsearch chmod 777 public

elasticsearch:
    restart: always
    image: elasticsearch:8.9.2
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - ES_JAVA_OPTS=-Xms256m -Xmx1024m
      - xpack.security.enabled=false
      - TZ=Europe/Moscow
    volumes:
       - $elasticsearch_directory:/usr/share/elasticsearch/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9200/?pretty"]
      interval: 10s
      timeout: 10s
      retries: 3
      start_period: 60s
  1. Проблема keycloak, так как были разные докер контейнеры keycloak, попробовал использовать quay.io/keycloak/keycloak, но когда я создавал конфигурацию, я не мог сохранить это в файл конфигах, чтобы потом любой пользователь смог построить образ с нужными настройками, готовый для развертывания в среде разработки, здесь описан мой пример решения проблемы https://keycloak.discourse.group/t/keycloak-17-docker-container-how-to-export-import-realm-import-must-be-done-on-container-startup/13619/23

  2. Что касается безопасности, было реализовано только на уровне gateway, это единая точка входа. Нижележащим сервисам передаются уже данные пользователя в аргументах. Хотели реализовать просто, не дублировать код для каждого сервиса. Как оказалось, не так уж и много будем дублировать, можно создать общий модуль и основную логику реализовать там. И для каждого сервиса реализовать проверку прав для доступа, то есть проверка jwt и извлечения данных из токена.

  3. Интересный докер образ мы нашли при настройке обратного прокси и https сертификата. Это nginx-proxy и nginx-proxy-acme. С помощью данных образов можно получать сертификаты, генерировать прокси маршрутизацию nginx для твоих контейнеров, которые находится в одной сети.

  4. Все динамические параметры для докера мы описывали .env, оказался очень удобный способ для задания параметров для docker compose

Вывод

Я рад, что взялся за такого рода проект, так как принесло практический и теоретический опыт. Так же смогли решить качественно эту задачу. Времени на написание было затрачено много и администрирование, поддержка будет отнимать какое-то время, но это ожидаемые затраты.

Книги, которые помогли реализовать приложение:

  1. Использование docker - Эдриен Моуэт

  2. ELK - Шукла,Кумар

  3. Микросервисы spring в действии - Карнем, Санчес

  4. Spring security in action - Laurentiu Spilca

  5. Практика реактивного программирования в spring — Докука, Лозинский

Комментарии (8)


  1. Junecat
    23.12.2023 15:52

    Извините, текст выглядит так, как будто русскй язык для Вас - не родной (несогласованные падежи, неправильное единственное-множественное число и т.п.)
    Из за этого статью читать сложно. Может, сделать для Вас редакторскую правку?


    1. 314159abc
      23.12.2023 15:52

      "Из-за" пишется через дефис.


    1. robertmakrytski Автор
      23.12.2023 15:52

      1)Русский язык - родной.
      2)Я нашёл, только две проблемы с будет.
      3)Что касается составных предложений, возможно не очень удачно написано. Но если у вас есть желание и время помочь, то можете попробовать. Мне кажется это присуще второй части текста - разработка.


  1. Barabas79
    23.12.2023 15:52

    Проект работает уже достаточно продолжительное время.

    А много людей пользуются им регулярно?


    1. robertmakrytski Автор
      23.12.2023 15:52

      Мало пользователей, это узконаправленный проект и мы не даем рекламы.


  1. dyadyaSerezha
    23.12.2023 15:52

    Начал читать, зашёл на адрес проекта и... И ничего, кроме некого описания. Я ожидал увидеть список работ и какие-то кнопки/меню/поля для фильтров и конфигурации, но там просто описание.

    Если для того, чтобы увидеть список работ, нужно зарегиться, это в корне неправильно, но даже и это не написано (а должно быть в самом верху). Или я не туда зашёл?


    1. robertmakrytski Автор
      23.12.2023 15:52

      Да, надо регистрироваться. Там написано, надо выше поднять эту надпись наверно.


  1. baldr
    23.12.2023 15:52

    Писал для себя что-то похожее лет 7 назад - собирал с Upwork (oDesk) и eLance (когда он ещё был живой). Собирал все проекты, клал в базу, а потом на страничке выводил только фильтрованные.

    Написал сначала на Ruby и RubyOnRails (чтобы прокачать RoR), а потом переписал на Python/Django. Делал для себя, поэтому особо с дизайном не заморачивался - Bootstrap/jQuery на фронте был.