module-sequenceProcessor

Проект реализует модуль готового, независимого от остальной системы, CommandHandler'a.

Сущности:

  • Step, команда, чей стейт существует только во время ее вызова. Содержит только логику. -- Она реализует интрфейсы IContextualStep<> / ISequenceStep<>, в зависимости от того, необходим ли контекст выполнения для работы команды
  • StepDto содержит аргументы команды
  • Context содержит в себе необходимые зависимости для выполнения команды -- Является отдельной сущностью, чтобы поддержать возможность работы с зависимостями, которые нельзя получить во время создания команд
  • ContextConstraint реализует в себе набор ограничений, которые проверяются перед созданием контекста
  • SequenceProcessingService предоставляет API для работы с модулем, позволяет вызвать команду по ее DTO
  • SequenceStepProvider хранит в себе все команды в виде оберток и предоставляет их по типу DTO
  • HandlersFactory занимается оборачиванием команд в хэндлеры, определяет тип команды и создает подходящий хендлер
  • Handler'ы дают универсальный интерфейс для вызова команды

Мотивация:

  • Реализация имеет вышеописанный вид, т.к. хотелось добиться независимости модуля и четких гарантий.
  • Я не использовал частый вариант реализации, который видел на моей практике, где DTO передается через интерфейс в сервис и дальше сервис ищет подходящую команду, т.к. это нарушает LSP. Т.е. на вход передается объект по контракту, и соотв. подойти должен каждый переданный объект, исключение составляет уже не тип а стейт DTO и стейт провайдера. Иными словами поддерживаю статическую типизацию и даю конкретно понять через интерфейсы, что от объекта ждут.
  • Т.к. типы команд бывают разными, то и интерфейс команд не однороден, для того были добавлены хендлеры, которые решают эту проблему. Конечно есть кейсы, когда приходится работать с рантайм ситуациями, когда тип в статическом контексте выполнения уже не известен, тогда уже приходится передавать тип через интерфейс, но чаще всего контекст будет статическим.
  • На моей практике я видел разные варианты и один из них это гигантский свитч, где через PatternMatching находилась нужная команда и туда уже передавали необходимые зависимости, вариант нарушает SOLID и при этом является очень неудобным в использовании.

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