Почему отдельный класс CommandHandler с Handle () вместо обработки метода в самой Command

13

У меня есть часть шаблона CQRS, реализованного с использованием S # arp Architecture, например:

public class MyCommand
{
    public CustomerId { get; set; }

    // some other fields
}

public class MyCommandHandler<MyCommand> : ICommandHandler<MyCommand, CommandResult>
{
    Handle(MyCommand command)
    {
        // some code for saving Customer entity

        return CommandResult.Success;
    }
}

Интересно, почему бы просто не иметь класс, Commandсодержащий данные и метод обработки? Это своего рода преимущество тестируемости, когда вам нужно тестировать логику обработки команд отдельно от свойств команды? Или это какое-то частое деловое требование, когда вам нужно, чтобы одна команда обрабатывалась разными реализациями ICommandHandler<MyCommand, CommandResult>?

rgripper
источник
У меня был один и тот же вопрос, стоит посмотреть: blogs.cuttingedge.it/steven/posts/2011/...
rdhaundiyal

Ответы:

14

Забавно, этот вопрос только напомнил мне о том же разговоре, который у меня был с одним из наших инженеров о библиотеке коммуникаций, над которой я работал.

Вместо команд у меня были классы Request, а затем - RequestHandlers. Дизайн был очень похож на то, что вы описываете. Я думаю, что часть вашей путаницы в том, что вы видите английское слово «команда» и сразу же думаете «глагол, действие ... и т. Д.».

Но в этом проекте думайте о Команде (или Запросе) как о букве. Или для тех, кто не знает, что такое почтовый сервис, подумайте по электронной почте. Это просто контент, отделенный от того, как следует воздействовать на этот контент.

Зачем ты это делаешь? В большинстве простых случаев, в шаблонах команд нет причин, и вы могли бы заставить этот класс выполнять работу напрямую. Однако выполнение разделения, как в вашем проекте, имеет смысл, если ваше действие / команда / запрос должны пройти некоторое расстояние. Например, через сокеты или каналы, или между доменом и инфраструктурой. Или, возможно, в вашей архитектуре ваши команды должны быть постоянными (например, обработчик команд может выполнять по 1 команде за раз, из-за некоторых системных событий, поступает 200 команд и после завершения первых 40 процессов). В этом случае, имея простой класс только для сообщений, становится очень просто сериализовать только часть сообщения в JSON / XML / двоичный / что угодно и передавать его по конвейеру, пока обработчик команд не будет готов обработать его.

Другое преимущество отделения Command от CommandHandler заключается в том, что теперь у вас есть возможность параллельной иерархии наследования. Например, все ваши команды могут быть производными от базового класса команд, который поддерживает сериализацию. И, возможно, у вас есть 4 из 20 обработчиков команд, которые имеют большое сходство, теперь вы можете получить их из базового класса обработчика команд. Если бы вы обрабатывали данные и команды в одном классе, отношения такого типа быстро вышли бы из-под контроля.

Другим примером для развязки было бы, если вашей команде требовалось очень мало ввода (например, 2 целых числа и строка), но ее логика обработки была достаточно сложной, чтобы вы хотели хранить данные в переменных промежуточного члена. Если вы ставите в очередь 50 команд, вам не нужно выделять память для всего этого промежуточного хранилища, поэтому вы отделяете Command от CommandHandler. Теперь вы ставите в очередь 50 легких структур данных, а более сложное хранилище данных выделяется только один раз (или N раз, если у вас N обработчиков) с помощью CommandHandler, который обрабатывает команды.

DXM
источник
Дело в том, что в этом контексте команда / запрос не является удаленной / постоянной / и т. Д. Она обрабатывается напрямую. И я не вижу, как разделение этих двух поможет с наследованием. Это на самом деле сделает это сложнее. Последний абзац также отчасти скучает. Создание объекта - не дорогая операция, а 50 команд - ничтожное число.
Эйфорический
@Euphoric: откуда ты знаешь, что такое контекст? Если S # arp Architecture не является чем-то особенным, я вижу только пару объявлений классов, и вы не представляете, как они используются в остальной части приложения. Если вам не нравятся числа, которые я выбрал, например, 50, выберите что-то вроде 50 в секунду. Если этого недостаточно, выберите 1000 в секунду. Я просто пытался привести примеры. Или вы не думаете, что в этом контексте у него будет так много команд?
ДХМ
Например, точную структуру можно увидеть здесь. Weblogs.asp.net/shijuvarghese/archive/2011/10/18/… . И нигде там не написано, что ты сказал. Что касается скорости, проблема в том, что вы использовали аргумент «производительность» без профилирования. Если у вас есть требования для такой производительности, вы не собираетесь использовать общую архитектуру, а создадите нечто более специализированное.
Эйфорический
1
Позвольте мне посмотреть, был ли это ваш последний пункт: OP попросил примеры, и я должен был сказать, например, сначала вы разрабатываете простой способ, и ваше приложение работает, затем вы увеличиваете масштаб и расширяете места, где вы используете шаблон команды, затем вы начинаете работать, и вы получаете 10000 машин, говорящих с вашим сервером, и ваш сервер все еще использует вашу оригинальную архитектуру, затем вы профилируете и идентифицируете проблему, а затем вы можете отделить данные команд от обработки команд, но только после вашего профиля. Неужели это сделает вас счастливее, если я включу все это в ответ? Он попросил пример, я дал ему один.
ДХМ
... так что я просто просмотрел тот пост в блоге, который вы опубликовали, и, похоже, он совпадает с тем, что я написал: отделите их, если ваша команда должна пройти некоторое расстояние. В блоге он, похоже, ссылается на командную шину, которая по сути является просто еще одним каналом, сокетом, очередью сообщений, esb ... и т. Д.
DXM
2

Обычный шаблон команды - это наличие данных и поведения в одном классе. Этот тип «шаблона команд / обработчиков» немного отличается. Единственное преимущество по сравнению с обычным шаблоном состоит в том, что ваша команда не зависит от фреймворков. Например, вашей команде может потребоваться доступ к БД, поэтому она должна иметь некоторый контекст или сеанс БД, то есть она зависит от каркасов. Но эта команда может быть частью вашего домена, поэтому вы не хотите, чтобы она зависела от фреймворков в соответствии с принципом инверсии зависимости . Отделение входных и выходных параметров от поведения и наличие диспетчера для их соединения могут исправить это.

С другой стороны, вы потеряете преимущество как наследования, так и состава команд. Что я думаю, это настоящая сила.

Кроме того, незначительный придира. То, что в названии есть команда, не делает ее частью CQRS. Это о чем-то гораздо более фундаментальном. Такая структура может служить как командой, так и запросом даже в одно и то же время.

Euphoric
источник
Я видел ссылку weblogs.asp.net/shijuvarghese/archive/2011/10/18/… вы указали, но я не вижу никаких признаков шины в коде S # arp Arch, который у меня есть. Так что, я полагаю, такое разделение в моем случае только распространяет логику классов.
rgripper
1
@rgripper Тогда вы не искали должным образом. github.com/sharparchitecture/Sharp-Architecture/blob/… и github.com/sharparchitecture/Sharp-Architecture/blob/…
Euphoric
Хм, спасибо за указание. Тогда мой случай даже немного хуже, потому что в моем коде ICommandProcessor обрабатывается IOC и разрешается в CommandProcessor (который сам создает IOC для обработчиков команд) - грязная композиция. И в проекте, похоже, нет бизнес-кейсов для более чем одного хадлера для команды.
rgripper