Чому відокремлюємо клас CommandHandler з Handle (), а не метод обробки в Command себе


13

У мене є частина CQRS-схеми, реалізованої за допомогою архітектури S # arp :

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>?


У мене було те саме питання, яке варто подивитися: 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, яка обробляє команди.


Справа в тому, що в цьому контексті команда / запит не є віддаленою / зберігається / тощо. Це обробляється безпосередньо. І я не можу зрозуміти, як розділення двох допоможе у спадщині. Це насправді ускладнить. Останній абзац також є своєрідним промахом. Створення об'єкта - це не дорога операція, і 50 команд - це нехтуючий номер.
Ейфорія

@Euphoric: як ти знаєш, що таке контекст? Якщо архітектура S # arp не є чимось особливим, я бачу лише кілька класових декларацій, і ви не знаєте, як вони використовуються в решті програми. Якщо вам не подобаються цифри, які я вибрав як 50, виберіть щось на кшталт 50 за секунду. Якщо цього недостатньо, виберіть 1000 за секунду. Я просто намагався навести приклади. Або ти не вважаєш, що в цьому контексті у нього буде стільки команд?
DXM

Наприклад, точна структура розглядається тут weblogs.asp.net/shijuvarghese/archive/2011/10/18 / ... . І ніде там не сказано, що ви сказали. Щодо швидкості, проблема полягає в тому, що ви використовували аргумент 'performance' без профілювання. Якщо у вас є вимоги до такої потужності, ви не збираєтеся використовувати загальну архітектуру, а будуєте щось більш спеціалізоване.
Ейфорія

1
Дозвольте мені побачити, чи це був ваш останній пункт: ОП запитав приклади, і я повинен був би сказати, наприклад, спочатку ви спроектуєте простий спосіб, і ваша програма працює, потім ви збільшуєте масштаб і розширюєте місця, де використовуєте шаблон команди, потім ви йдете в прямому ефірі, і ви отримуєте 10 000 машин, що розмовляють з вашим сервером, і ваш сервер все ще використовує вашу оригінальну архітектуру, потім ви профілюєте і ідентифікуєте проблему, а потім ви зможете відокремити дані команд від обробки команд, але лише після того, як ви профіліруєте. Чи справді ви зробили б вас щасливішими, якби я все це включив у відповідь? Він попросив приклад, я дав йому.
DXM

... тож я просто переглянув цю публікацію в блозі, яку ви опублікували, і, схоже, узгоджується з тим, що я написав: розділіть їх, якщо ваша команда повинна пройти деяку відстань. У блозі він, мабуть, посилається на командну шину, яка в основному є лише іншою трубкою, сокетом, чергою повідомлень, esb ... тощо
DXM

2

Звичайна командна схема - це наявність даних та поведінки в одному класі. Цей тип «команди команд / обробника» дещо відрізняється. Єдиною перевагою порівняно зі звичайним шаблоном є додаткова перевага відсутності вашої команди залежно від фреймів. Наприклад, вашій команді може знадобитися доступ до БД, тому він повинен мати якийсь контекст або сеанс БД, тобто це залежить від фреймворків. Але ця команда може бути частиною вашого домену, тому ви не хочете, щоб вона залежала від фреймворків відповідно до принципу інверсії залежності . Відокремлення цього може відокремити вхідні та вихідні параметри від поведінки та мати певного диспетчера для їх підключення.

З іншого боку, ви втратите перевагу як успадкування, так і складу команд. Я думаю, що це справжня сила.

Також мінорний нітпік. Тільки тому, що він має Command в імені, це не входить до CQRS. Йдеться про щось набагато більш фундаментальне. Така структура може слугувати як командою, так і як запит навіть одночасно.


Я бачив посилання weblogs.asp.net/shijuvarghese/archive/2011/10/18/…, яку ви вказали, але я не бачу жодних ознак шини в архівному коді S # arp. Тож, мабуть, таке розділення у моєму випадку лише поширює класи та бризки логіки.
rgripper


Хм, спасибі за вказівку. Тоді мій випадок ще трохи гірший, тому що в коді у мене ICommandProcessor є IOC'ed і вирішується CommandProcessor (який сам робить IOC для обробників команд) - каламутна композиція. І, здається, у проекті не існує жодних ділових випадків для більш ніж одного акаунта для команди.
rgripper
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.