Pull to refresh

Comments 18

Деньги это не просто число, это число (сумма) + валюта

Ну да, есть такой паттерн. Только вот допустим, если посмотреть количество скачиваний Nuget-пакета для c# — можно удивиться какая низкая популярность. Видимо, большинство фигачит через decimal.


Представим, что мы создаем сайт онлайн-казино "Три топора"

А вы не хотите напомнить читателям, что игорная деятельность в рунете запрещена законом? Вот буквально сегодня прошла новость "ФНС нашла способ закрыть крупнейшее нелегальное интернет-казино".


Не знаю какая позиция самого издательского дома ТМ по отношению к таким статьям (вряд ли это проплаченная реклама), но вот допустим во многих новостных изданиях я вижу постоянные скобки после названий террористических организаций — мол, это запрещённая в РФ организация. В общем, прямо по краю упоминание. Можно было бы и более нейтральный пример подобрать к статье.

Касательно денег, зачастую, в базе данных, для того чтобы сохранить деньги, используются поля с плавающей запятой. Это не совсем удобно, в дальнейшем возникают ряд ошибок связанных с округлением. По факту, не может в учете указваться половина копейки. Удобнее все хранить в фракциях — копейках, центах и.т.д., использовать целочисленный тип. Валюта тоже важна, кроме собственно валюты, она показывает разрядность тех или иных фракций. Если у рубля и доллара это одна сотая, то у иены ее нет, у сатоши одна девятимиллионная. Валюты могу так же разделять тестовые и реальные платежи. Если в проекте все эти проблемы не стоят, проще использовать decimal и не усложнять свою жизнь. В руби gem 'money' достаточно популярен, 2000 звезд на GitHub хороший показатель.


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

Не хочется скатываться до холивара. Ко всему надо подходить рационально и не уходить в крайности. Давайте разберем пример.


Задача


1) Вы пишете демон для холодильника. У вас есть некий цикл который проверят открыт ли холодильник или нет, если открыт то шлем смс.


loop do
  Service::SendSms(master.phone, 'Fridge is open') if fridge.open? && fridge.open_time > 15.sec
  sleep 30
end

Отправка смс, достаточно сложная логика, лучше вынести в отдельный сервис.


2) Вы пишете демона, и холодильник у вас достаточно умный там встроен моторчик, для которого реализован какой-то драйвер, вынесенный в стороннюю библиотеку.


class FridgeEntity
  def close!
    driver.close_door
  end

  #...
end

loop do
  fridge.close! if fridge.open? && fridge.open_time > 15.sec
  sleep 30
end

Создавать отдельный сервис который инициирует работу драйвера излишне. Но с другой стороны, правила проверки (раз в 30 секунд, если уже открыта более 15 секунд) не следует хранить в Сущности, так как это внешний процесс.


Если предложенный мной пример не раскрывает сути вопроса, предложите пожалуйста свой, попробуем его разобрать.

Два заявления
Ко всему надо подходить рационально и не уходить в крайности.
и
класс Сущность не имеет никаких методов собственного изменения
не совместимы. Согласно ООП и DDD, сущности точно могут менять собственное состояние. А вы им это не даете делать в принципе — это точно ошибка.
Вы пытаетесь подменить понятие «Сущность/Агрегат» на понятие «Состояние сущности/агрегата». Сущность — это данные и поведение.
Приведите конкретный пример.

dddsample-core — это реализация на Java примеров, о которых говорит Эрик Эванс в своей книге. Они хорошо прокомментированы, так что будет легко разобраться.
Вот например реализация сущности Cargo содержит как данные (TrackingId, origin, delivery), причем доступ к ним защищен согласно принципу инкапсуляция ООП, таки поведение (specifyNewRoute, assignToRoute,...)

Хороший пример, методы которые описываются в приведенном классе, изменяют его. Это такие своеобразные, сеттеры + обработка. Но они никогда не вызываются напрямую, вся работа с ними ведется через сервисы: assignToRoute, specifyNewRoute, deriveDeliveryProgress.


Данный пример и позволил выявить точку нашего взаимонепонимания. Я ни в коем случае не приываю вынести подобные методы изменения атрибутов из класса Сущности. Я призываю к тому что бизнес операции не могу быть описаны в модели и их необходимо выносить на слой бизнес-логики. Пользователь не должен менять сущность напрямую, он должен вызвать процесс, который приведет к изменению Сущности. Есть еще некоторые особенности от типа системы которую мы пишем Запрос-Ответ или Событийная. Тут мы говорим про web и rest, т.е. Запрос-Ответ.


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


class Foo
  arr_accessor :bar
end

# это по факту:

class AnotherFoo
  def bar=(val)
    @bar = val
  end

  def bar
    @bar
  end
end

На хабре есть кнопка Ответить, под каждым комментарием, чтобы не создавать новую ветвь дискуссии.

Согласно концепции DDD отделять доменную логику от агрегата в сервис (Domain Service) нужно в том случае, если ее нельзя отнести к какой-то конкретной сущности или объекту-значению.


  • Операция не принадлежит ни одному из объектов предметной области
  • Операция выполняется над различными объектами предметной области

Злоупотребление приводит к «анемичной модели предметной области», как я говори ранее. Так что оставьте возможность реализации доменной логики в агрегате, если не хотите расстраивать Мартина и Эрика :)

Чтобы расстроить Мартина, надо понять что они имеют ввиду в этой статье как раз делается вывод, что к Entity нет доступа напрямую.


Сервисы добавляются потому, что есть разные сценарии использования _Сущности.


Сценарий "Включение телевизора": У нас есть девочка, которая включает телевизор. Она может включить его кнопкой, а может с пульта. Причем кнопкой она может и не дотянуться, т.к. у нее маленький рост.


Проверка роста, точно, не должна находится в модели телевизора — выносим в сервис. Поиск пульта в другой сервис. Метод включения для Сущности Телевизор который включает светодиод и матрицу — ок.


Методы изменения в сущности — ок. Сценарии использования — не ок. И сущность не может изменить себя сама.

В вашем примере как минимум три сущности: девочка, пульт и телевизор. Именно девочка выполняет действие «включить телевизор», в рамках которого она понимает, что не может достать до кнопки (зная свой рост), она «ищет пульт» (еще одно действие девочки), и нажимает на нем кнопку. Пульт посылает сигнал телевизору. Телевизор включается, в ответ на сигнал пульта. Все операции — это поведение сущностей, а не сервисов.
Не соглашусь. Во-первых такая схема работает ровно до того момента, пока не появится мальчик — или дублирования кода, или миксины или множественное наследование.
Во-вторых девочка возможно имеет методы use, take, age, но точно она не должна знать как включать телевизор, это явный RDM.

В таком случае вы не до конца понимаете концепции DDD: единый язык, изолированные контексты, сущности и агрегаты — это части предметной области (домена), которые должны отражать моделируемые объекты из реального мира и одинаково трактоваться и бизнес экспертами и аналитиками и разработчиками ("девочка" — это сущность, а не сервис) и находить соответствующее отражение в коде.


А вы все сводите только сервисам и "голым" моделям. Это точно не DDD и не ООП, а возврат к процедурному программирование.


Посмотрите как Верон моделирует домен с помощью Event Storming. Он выделяет события, агрегаты и команды. Агрегаты исполняют команды (реализуют поведение), "выбрасывая" события. Сервисы вторичны в этой истории, и используются для моделирования процессов над агрегатами (бизнес логики вне агрегатов).

Да, именно это! Агрегаты исполняют команды (реализуют поведение), «выбрасывая» события. Наконец мы друг друга поняли.
Взаимодействие девочки с окружающими её системами, будь оно заключено в классе девочки так как в этом примере, экспоненциально увеличивает сопряженность системы.
Как будет изменяться ответственность девочки когда у телевизора появится «переключить канал», «вставить кассету», «настроить звук», а далее девочке надо будет управлять чайником, автомобилем, хахалем? Сколько связей будет у этого класса?
Связей будет столько — сколько есть в вашем бизнес домене. Для упрощения реализации используйте разбиение домена на изолированные контексты (в каждом из них будет своя девочка со связями нужными только в этом конексте, а значит их будет меньше). А вы для упрощения решения задачи предлагаете не разбивать домен на изолированные контексты, а создавать кучу разных сервисов в одном контексте. Это не DDD!

Не заменяйте сущности предметной области сервисами для упрощения разработки — это «коверкает» единый язык контекста и противоречит самой концепции DDD, где мы пытаемся моделировать предметную область на одном языке между программистами и экспертами.
И соглашусь, что последний абзац стоит переписать.
Sign up to leave a comment.

Articles

Change theme settings