Тестовое задание для MediaLand.

Установка

Клонировать проект:

git clone https://github.com/Amegatron/medialand-test.git

Docker Compose

cd medialand-test
docker-compose up

Веб-сервис будет доступен по адресу http://localhost:8080

Ручная настройка

Если Docker недоступен, требуется вручную настроить веб-сервер. В случае NGINX-а добавить конфиг nginx/conf.d/uuids.conf к конфигам NGINX-а, заменив в нем следующее:

  1. В строке listen 80 default_server; убрать default_server и по желанию заменить порт на более удобный.
  2. Путь в строке root /var/www/public; заменить на фактический путь, по которому лежит директория public проекта.
  3. В строке fastcgi_pass ...; заменить counters-app:9000 в соотв-ии с локальной конфигурацией PHP-FPM.

Комментарии по реализации

Core

Класс Counter представляет собой основную сущность с точки зрения бизнес-логики. Основная проблема со счетчиком заключается в соблюдении его атомарности. Для решения этой проблемы работа с этим классом должна происходить по принципу Синглтона, чтобы в рамках одного рабочего процесса один и тот же счетчик с точки зрения БЛ (счетчики с одним и тем же UUID) был представлен единственным экземпляром. По части управления различными экземплярами счетчиков класс Counter опирается на предоставляемого менеджера счетчиков (интерфейс CounterManagerInterface).

Атомарность также должна достигаться при параллельных запросах к приложению. В силу особенностей PHP, Counter сам по себе не имеет возможности контролировать это, и подобная атомарность может быть обеспечена только внешним по отношению к данному классу слоем. Поэтому Counter делегирует задачу увеличения значения счетчика внешним инкременторам посредством интерфейса IncrementorInterface.

Management

Данный слой является управляющим для слоя БЛ и также обеспечивает связь между Counter и слоем хранения, который, в свою очередь, также обеспечивает атомарные изменения счетчика. Класс CountManager является конкретной реализацией интерфейса CounterManagerInterface, обеспечивая работу со счетчиками в Синглтон-манере, для чего использует SingletonManagerInterface и соответствующую реализацию. Для реализации хранения CountManager работает с различными драйверами (CounterPersistenceDriverInterface), которые берут на себя роль как непосредственно хранения счетчиков, так и их атомарного изменения.

Persistence

Данный слой содержит различные драйверы для хранения и изменения счетчиков. На данный момент представлена одна реализация, обеспечивающая работу этих механизмов за счет файловой системы (FileSystemPersistenceDriver). Драйверы также предоставляют соответствующие инкременторы, которыми пользуются счетчики для инкремента.

Commands

Данный слой предоставляет функционал для общих используемых действий в системе независимо от вариантов использования самой системы (Веб/Консоль/...). А именно: создать или увеличить счетчик, а также просто получить существующий счетчик. Данный слой реализован в виде шины команд (Command Bus), где каждое действие в системе представлено отдельной командой. Шина реализована пакетом league/tactician.

Web

Данный слой отвечает исключительно за взаимодействие с приложением через Web: принимает HTTP-запросы, выполняет существующие команды и возвращает ответы. Для реализации взаимодействия с Web используются PSR-совместимые реализации для запросов и ответов:

  • laminas/laminas-diactoros - имплементация PSR-7
  • laminas/laminas-httphandlerrunner - эмиттер ответов
  • league/route - маршрутизатор

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

К сожалению, на полноценные тесты всего приложения времени не хватило. Но для простой проверки на надежность инкремента при большом кол-ве одновременных запросов создал отдельный скрипт на Go, расположенный здесь: https://gist.github.com/Amegatron/2c30a687b6468a52d7cb1d3eb5f3aa73

Скрипт производит множество параллельных запросов на инкремент счетчика и проверяет целостность данных.

Пример запуска:

medialand-test.go 100 http://localhost:8080/v1/counters/897e4dcb-68bc-46b2-87c4-758d77c00ee9

Где 100 - кол-во одновременных POST запросов на указанный URL.