- DownloadWorker — скачиватель (вернее, генератор, на текущем этапе) документов.
- Gateway — REST-интерфейс всего этого.
- MessageQueue — велосипед очередей. В реальном мире должно быть заменено на RabbitMQ или что-то похожее, что может дать возможность создавать временные неперсистентные очереди и удобно проводить рассылки с неопределённым количеством получателей.
- SearchEngine — невероятно мощный движок поиска вокруг функции string.IndexOf. Для демонстрации вполне достаточно.
- TextSearchOrchestrator — логика, соединяющая всё это во что-то целое. Внутри него есть in-memory sqlite.
Индексация списка документов по указанным источникам (на текущий момент гененрирует случайную строку из латинских букв, используя хеш источника как seed). Ждёт завершения всех индексаций. Повторный вызов без указания reindex использует старые результаты индексации.
Поиск подстроки. Идёт напрямую в SearchEngine. Возвращает source и позицию подстроки в тексте.
Всё общение между сервисами по grpc. Включено проталкивание traceId где возможно, в остальных случаях путь можно отслеживать по taskId. Добавлено логирование (не везде, можно больше, но логов всегда можно больше)
-
(TextSearchOrchestrator) При вызове индексации происходит поиск в базе существующих задач на индексацию для каждого указанного source. Для всех ненайденных создаются задания и отправляются в очередь. Все задания (существующие ранее и созданные сейчас) объединяются в группу (много-ко-многим), вызов встаёт на ожидании переключения статуса этой группы в done
-
(DownloadWorker) Задача на скачивание обрабатывается. Сейчас там генерация мусора и Delay на 5 секунд чисто для имитации. Здесь нужно отметить, что в реальных условиях необходим рейт-лимитер, чтобы не сильно мучить сторонние сервисы. Алгоритмы рейт-лимитинга для этого сценария это отдельная самостоятельная задача, на которую нет единственного верного ответа. После получения контента (или неудачи), он кладётся в другую очередь. В реальных условиях контент должен быть помещён в S3 или какое-то другое хранилище. Отправлять неопределённо большой объём через очередь не лучшая практика. Но для демострации сгодится.
-
(TextSearchOrchestrator) Если контент получен, переключает статус задачи на индексацию и отправляет в очередь индексации. Если не получилось, переключает в ошибку. После этого пересчитывает статус всех затронутых групп задач как минимальный из всех статусов задач этой группы. Ошибка там наименьший статус, поэтому если случилась хоть одна ошибка, вся группа будеь объявлена упавшей, однако другие задачи из этой группы обработаны будут
-
(SearchEngine) Получает задачу на индексацию. Сейчас просто добавляет в словарь (или обновляет если уже было) и записывает source в список для перечисления в сценарии поиска текста. После индексации отправляет новое сообщение в очередь
-
(TextSearchOrchestrator) Получает сообщение из очереди завершения индексаций. Снова обновляет статус задач и пересчитывает статус групп. Для тех групп, для которых статус стал Done, отправляет сообщение в последнюю очередь и ещё одну временную очередь, созданную специально для этой группы.
-
(TextSearchOrchestrator) Внутри той функции, что создала самую первую задачу на обработку, происходит получение сообщения из специальной временной очереди, после чего вызов завершается
Вызов индексации оборвётся при ожидании: ничего критичного. Повторный вызов создаст новую группу, но к ней привяжет уже существующие задачи на индексацию. Если же вызов был на переиндексацию, то создаст новые задачи (но в таком случае трудно сходу сказать, как сделать лучше).
Какое-то сообщение потеряется: индексация не завершится, но в базе останется запись. Там есть поле created, по которому можно сделать проверку и "доиндексацию" таких задач. Сейчас не сделано, но технически это реализуемо достаточно легко.
Будет несколько вызовов поиска с одинаковыми запросами: сейчас будет честно искать несколько раз. В реальных условиях нужно исходить из возможностей поискового движка и статистики использования. Кеширование ответов возможно несколькими путями, но не сделано умышленно, так как в этом моменте слишком много неопределённостей.
-
Grpc Client'ы везде получается напрямую. В реальных условиях это должно быть скрыто за абстракциями, интерфейс которых будет повторять интерфейс клиентов, но с моделями, описанными внутри каждого проекта отдельно, для того, чтобы не было прямой зависимости на grpc реализации транспорта
-
Файлы proto вынесены в контрактную сборку. Это удобно на этапе разработки, но из-за этого появляются сообщения о конфликтах имён типов, определенных в прото-файлах. В реальных условиях положение прото-файлов обсуждается отдельно с учётом локальных требований
-
Сообщения в логе на русском потому, что почему бы нет