module-sequenceProcessor
Проект реализует модуль готового, независимого от остальной системы, CommandHandler'a.
Сущности:
- Step, команда, чей стейт существует только во время ее вызова. Содержит только логику. -- Она реализует интрфейсы IContextualStep<> / ISequenceStep<>, в зависимости от того, необходим ли контекст выполнения для работы команды
- StepDto содержит аргументы команды
- Context содержит в себе необходимые зависимости для выполнения команды -- Является отдельной сущностью, чтобы поддержать возможность работы с зависимостями, которые нельзя получить во время создания команд
- ContextConstraint реализует в себе набор ограничений, которые проверяются перед созданием контекста
- SequenceProcessingService предоставляет API для работы с модулем, позволяет вызвать команду по ее DTO
- SequenceStepProvider хранит в себе все команды в виде оберток и предоставляет их по типу DTO
- HandlersFactory занимается оборачиванием команд в хэндлеры, определяет тип команды и создает подходящий хендлер
- Handler'ы дают универсальный интерфейс для вызова команды
Мотивация:
- Реализация имеет вышеописанный вид, т.к. хотелось добиться независимости модуля и четких гарантий.
- Я не использовал частый вариант реализации, который видел на моей практике, где DTO передается через интерфейс в сервис и дальше сервис ищет подходящую команду, т.к. это нарушает LSP. Т.е. на вход передается объект по контракту, и соотв. подойти должен каждый переданный объект, исключение составляет уже не тип а стейт DTO и стейт провайдера. Иными словами поддерживаю статическую типизацию и даю конкретно понять через интерфейсы, что от объекта ждут.
- Т.к. типы команд бывают разными, то и интерфейс команд не однороден, для того были добавлены хендлеры, которые решают эту проблему. Конечно есть кейсы, когда приходится работать с рантайм ситуациями, когда тип в статическом контексте выполнения уже не известен, тогда уже приходится передавать тип через интерфейс, но чаще всего контекст будет статическим.
- На моей практике я видел разные варианты и один из них это гигантский свитч, где через PatternMatching находилась нужная команда и туда уже передавали необходимые зависимости, вариант нарушает SOLID и при этом является очень неудобным в использовании.
В моей реализации достаточно просто описать команду и ее параметры, после чего зарегистрировать в контейнере. Я бы еще расширил функционал и пересмотрел реализацию, но времени особо у меня не было. Слабым местом является рефлексия, но я собирался следующей итерацией сделать ее более надежной. Также, из-за того что я уже рефакторил сервис, пострадал нейминг. Также DTO находится на бехах, но это уже были не мои требования.