DDD: использование объектов Value внутри контроллеров?

Когда вы получаете аргументы в строковом формате из пользовательского интерфейса внутри вашего контроллера, передаете ли вы строки в службу приложения (или для команды) напрямую?

Или вы создаете объекты значения из строк внутри контроллера?

new Command(new SomeId("id"), Weight.create("80 kg"), new Date())

или

new Command("id", "80 kg", new Date())
new Command("id", "80", "kg", new Date())

Возможно, это не важно, но меня это беспокоит.

Вопрос в том, должны ли мы связывать объекты значений из домена с внутренним контроллером?

Представьте, что у вас нет веб-интерфейса между вашим уровнем приложения и уровнем представления (например, активностью orroid или swing), вы бы использовали использование объектов значения в пользовательском интерфейсе?


Другое дело, вы сериализуете /unserialize объекты значения в/из строки, как это?

Weight weight = Weight.create("80 kg"); 
weight.getValue().equals(80.0);
weight.getUnit().equals(Unit.KILOGRAMS);
weight.toString().equals("80 kg");

В случае передачи строк в команды я предпочел бы передать "80 кг" вместо "80" и "кг".

Извините, если вопрос не имеет значения или забавный.

Спасибо.


UPDATE

Я столкнулся с этим сообщением, когда искал информацию о совершенно другой теме: Объекты Value в CQRS - где использовать

Они, кажется, предпочитают примитивы или DTO, и сохраняют VO внутри домена.

Я также взглянул на книгу В. Вернона (Реализация DDD), и он говорит о (точно -_-), что в главе 14 (стр. 522)

Я заметил, что он использовал команды без каких-либо DTO.

someCommand.setId("id");
someCommand.setWeightValue("80");
someCommand.setWeightUnit("kg");
someCommand.setOtherWeight("80 kg");
someCommand.setDate("17/03/2015 17:28:35");
someCommand.setUserName("...");
someCommand.setUserAttribute("...");
someCommand.setUserOtherAttributePartA("...");
someCommand.setUserOtherAttributePartB("...");

Это объект команды, который будет отображаться контроллером. Инициализация объектов значений будет выглядеть в методе обработчика команд, и они будут бросать что-то в случае плохого значения (самооценка при инициализации).

Я думаю, что я начинаю меньше беспокоиться, но некоторые другие мнения будут приветствоваться.

Ответ 1

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

Пропустить строки или разбор?

Мои личные предпочтения здесь - разобрать все в Контроллере и отправить результаты в Службу. Для этого подхода есть две основные фазы, каждая из которых может отбросить ошибки:

1. Попытка разбора

Когда из пользовательского интерфейса входит пучок string, я думаю, что имеет смысл попытаться сразу их интерпретировать. Для простых целей, таких как int и bool s, эти преобразования тривиальны, а привязки моделей для многих веб-фреймворков обрабатывают их автоматически.

Для более сложных объектов, таких как пользовательские классы, все же имеет смысл обрабатывать его в этом местоположении, чтобы весь синтаксический анализ выполнялся в одном месте. Если вы находитесь в среде, которая обеспечивает привязку модели, большая часть этого разбора, вероятно, выполняется автоматически; если нет - или вы собираете более сложный объект для отправки в службу - вы можете сделать это вручную в контроллере.

Состояние отказа

Когда синтаксический анализ не выполняется ("hello" вводится в поле int или 7 введено для bool), довольно легко отправить обратную связь пользователю, прежде чем вам даже понадобится позвонить службе.

2. Подтвердить и зафиксировать

Несмотря на то, что синтаксический анализ удался, все равно необходимо проверить, что запись является законной, а затем зафиксировать ее. Я предпочитаю обрабатывать проверку на уровне обслуживания непосредственно перед совершением. Это оставляет Контроллер ответственным за синтаксический анализ и делает его очень ясным в коде, который выполняется валидацией для каждой части данных, которые совершаются.

При этом мы можем исключить вспомогательную ответственность из уровня сервиса. Нет необходимости, чтобы он разбирал объекты - его единственная цель - фиксировать информацию.

Состояние отказа

Когда валидация не удалась (кто-то вводит адрес на Луну или вводит дату рождения 300 лет назад), сообщение об ошибке должно быть сообщено обратно вызывающему абоненту (в этом случае Контроллер). В то время как пользователь, вероятно, не делает различий между сбоем в синтаксическом разборе и отказом для проверки, это важная разница для программного обеспечения.

Переместить объекты Value в UI?

Я бы принимал анализируемые объекты как можно ближе к стеку каждый раз. Если вы можете заставить чью-то другую фреймворк обработать этот бит преобразования, почему бы не сделать это? Кроме того, чем ближе к пользовательскому интерфейсу, чем объекты могут жить, тем легче дать хорошую, быструю обратную связь с пользователем о том, что они делают.

Примечание о связи

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

Разбор одной большой строки или компонентов?

Как правило, проще всего передать весь string в метод Parse() для сортировки. Возьмите пример "80 kg":

  • "80 kg" и "120 lbs" могут быть действительными весовыми вводами
  • Если вы проходите в string до метода Parse(), он, вероятно, делает довольно тяжелый подъем. Ожидая разбить a string на основе пространства, не будет властно.
  • Намного проще вызвать Weight.create(inputString), чем разбить inputString на " ", затем вызвать Weight.create(split[0], split[1]).
  • Легче поддерживать функцию ввода с одной строкой Parse(). Если возникает какое-то новое требование, то класс Weight должен поддерживать фунты и унции, новый допустимый ввод может быть "120 lbs 6 oz". Если вы разделяете вход, теперь вам нужно четыре аргумента. В то время как если он полностью инкапсулирован в логику Parse(), нет никакой нагрузки для внешних потребителей. Это делает код более расширяемым и гибким.

Ответ 2

Разница между DTO и VO заключается в том, что DTO не имеет поведения, это простой контейнер, предназначенный для передачи данных от компонента к компоненту. Кроме того, вам редко приходится сравнивать два DTO, и они, как правило, преходящи.

A Объект Value может иметь поведение. Два VO сравниваются по значению, а не по ссылке, что означает, например, два объекта значения Address с теми же данными, но это разные экземпляры объектов, будут равны. Это полезно, потому что они обычно сохраняются в той или иной форме, и есть больше случаев, чтобы сравнить их.

Оказывается, что в DDD-приложении VO будет объявляться и использоваться в вашем доменном слое чаще, чем нет, поскольку они принадлежат к домену Ubiquitous Language и из-за разделения проблем. Их иногда можно манипулировать на уровне приложения, но обычно они не будут отправляться между уровнем пользовательского интерфейса и приложением. Вместо этого мы используем DTO.

Конечно, это дискуссионный и сильно зависит от слоев, которые вы выбрали для создания своего приложения. Могут быть случаи, когда хруст вашей многоуровневой архитектуры до двух уровней будет полезен, и при использовании бизнес-объектов непосредственно в пользовательском интерфейсе не будет так уж плохо.