Skip to content

Dvurechensky-Test-Tasks/NET_Junior_Ads_Test_Task

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

✨Dvurechensky✨

AdPlatformService - Тестовое задание 📄 для Junior `.NET`

AdPlatformService — высокопроизводительный in-memory веб-сервис для хранения и поиска рекламных площадок по локациям.

📄 Документация по тестовому заданию доступна также в PDF: TASK.NET.pdf
📄 Тестовый файл с входными данными: test_input_data.txt

⭐ Оглавление

🚀 Запуск

  • Старт
dotnet run --project AdRegionService
Раскрыть подробности API
POST /api/load
Content-Type: multipart/form-data

file=@platforms.txt
{
	"message": "Data loaded",
	"loaded": 123,
	"skipped": 5
}
GET /api/search?location=/ru/svrd
[
	{ "name": "Крутая реклама", "locations": ["/ru/svrd"] },
	{
		"name": "Ревдинский рабочий",
		"locations": ["/ru/svrd/revda", "/ru/svrd/pervik"]
	}
]

✨ Возможности

  • 📂 Загрузка списка рекламных площадок из файла (Stream).
  • ⚡ Хранение в Immutable коллекциях для потокобезопасности.
  • 📊 Подсчёт статистики загрузки (загружено / пропущено).
  • 🔍 Поиск площадок по иерархическим локациям:
    • /loc1 найдёт все площадки по этому уровню.
    • /loc1/loc2 учитывает и /loc1.
  • 🛡 Обработка ошибок:
    • OperationCanceledException (отмена загрузки).
    • OutOfMemoryException (слишком большие файлы).
    • Общие ошибки логируются, состояние не портится.

🔹 Основной функционал

1. Загрузка рекламных площадок из файла

  • Метод LoadFromStreamAsync(Stream stream, CancellationToken cancellationToken = default) загружает данные о рекламных площадках из текстового потока.
    При успешной загрузке обновляется текущее состояние сервиса (список платформ и индекс по локациям).
    Если загрузка не удалась, старые данные сохраняются.

Ключевые особенности

  • Принимает поток (Stream) — можно загружать как локальные файлы, так и данные из сети.
  • Игнорирует некорректные строки:
    • пустые строки;
    • строки без разделителя :;
    • пустое имя площадки;
    • отсутствие валидных локаций.
  • Индексирует локации
    Каждая площадка привязывается к своим локациям, которые складываются в ImmutableDictionary<string, ImmutableHashSet<AdPlatform>>.
  • Статистика загрузки
    Сохраняется количество загруженных и пропущенных строк (LoadStats).
  • Логирование прогресса
    Каждые 100 000 строк выводится лог «Обработано N строк…».
  • Защита от ошибок
    • OperationCanceledException → корректная отмена загрузки;
    • OutOfMemoryException → логируется, состояние не меняется;
    • другие ошибки → логируются, состояние не меняется.

Пример формата файла

Яндекс.Директ:/ru
Ревдинский рабочий:/ru/svrd/revda,/ru/svrd/pervik
Газета уральских москвичей:/ru/msk,/ru/permobl,/ru/chelobl
Крутая реклама:/ru/svrd

2. Поиск рекламных площадок по локации

  • ✅ Метод Search(string location) возвращает все площадки, совпадающие с заданной локацией.
  • ✅ Используется индекс по локациям для мгновенного поиска.
  • ✅ Поддержка частичных совпадений в имени и локациях.
  • ✅ Возвращает IEnumerable<AdPlatform> без лишнего копирования данных.
  • ✅ Потокобезопасный и защищён от битых объектов (null Name или null Locations).
  • ✅ Комфортно работает с _platforms до 1–2 млн элементов; для >10 млн элементов.
  • ⚡ При росте >10 млн площадок или >1–2 GB данных потребуется переход на внешние решения (например, PostgreSQL + полнотекстовый поиск или специализированные индексы).

🔹 Технический подход

  • Immutable коллекции (ImmutableArray, ImmutableDictionary, ImmutableHashSet)

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

    • Локации индексируются в ImmutableDictionary<string, ImmutableHashSet<AdPlatform>>.
    • Поиск учитывает все уровни иерархии: запрос /a/b/c проверяет /a, /a/b, /a/b/c.
  • Логирование через ILogger

    • Каждые 100 000 строк загрузки выводится прогресс.
    • Логируются ошибки (OutOfMemoryException, OperationCanceledException, общие исключения).
    • Логи можно подключить к системе мониторинга (например, Seq, Kibana, Zabbix).
  • Устойчивость к ошибкам

    • Некорректные строки файла игнорируются.
    • Если загрузка не удалась, предыдущее состояние (_platforms и индекс) сохраняется.
    • Поиск никогда не кидает исключения наружу, при ошибке возвращается пустой результат.
  • Поиск через объединение множеств

    • Для найденных уровней локации собирается HashSet<AdPlatform>.
    • Дубли платформ автоматически исключаются.
    • Возврат результата происходит сразу как IEnumerable<AdPlatform>.

🔹 Преимущества

  • Высокая производительность загрузки и поиска
    Оптимизировано для работы с файлами до 1–2 млн строк (100–500 MB).
    Индексация по локациям обеспечивает быстрый поиск без полного перебора.

  • 🛡 Устойчивость и надёжность
    Некорректные строки автоматически игнорируются.
    При ошибке загрузки текущее состояние не теряется.
    Поиск никогда не выбрасывает исключения наружу.

  • 🔗 Простая интеграция с REST API
    Логика сервиса изолирована, endpoints легко строятся на её основе (/api/load, /api/search).

  • 🧵 Готовность к многопоточности
    Использование ImmutableArray и ImmutableDictionary гарантирует потокобезопасность без явных блокировок.
    Несколько запросов поиска могут выполняться одновременно.


🔹 Тестирование

  • Используются модульные тесты (xUnit) для проверки корректности поиска и индексации.
  • Покрытие включает: загрузку данных, поиск по названию и локациям, работу с пустыми и некорректными входными данными.
  • Команда запуска: dotnet test

🛠 CI/CD

  • Автоматическая сборка Docker-образа при пуше в main.
  • Публикация образа в GitHub Container Registry (ghcr.io).
  • Сборка и пуш всех сервисов через Docker Compose.
  • Минимальные проверки через dotnet build и dotnet test.

🔹 Контейнер Docker

Генерация сертификата

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout key.pem -out cert.pem -subj "/CN=localhost"

Запуск контейнера

docker-compose up --build
  • Адрес сервера после запуска в контейнере:
    • http://localhost:5411/swagger/index.html
    • https://localhost:5412/swagger/index.html

Использование контейнера в Github

Загрузка

docker pull ghcr.io/dvurechensky/net_junior_ads_test_task/adservice:latest

Запуск напрямую, пробрасывая порт

docker run -it --rm -p 5411:5411 ghcr.io/dvurechensky/net_junior_ads_test_task/adservice:latest

После запуска можно проверить: http://localhost:5411/swagger

✨Dvurechensky✨