Pull to refresh
59
29
Стас Выщепан @gandjustas

Умею оптимизировать программы

Send message

Синглтон без состояния это же просто набор статических функций, не?

Сможете привести реалистичный рабочий пример?

Потому что я вижу сразу кучу проблем со скрытым состоянием объекта, которые не оправдываются вообще ничем.

  1. Его нельзя сохранить в базу с помощью EF. Чтобы можно было сохранить нужно как минимум read-only свойство

  2. У вас получается логика основанная на исключениях. Если единственный способ узнать что действие выполнить нельзя - вызвать Объект.Действие() , то вы никак не сможете скрыть\деактивировать элементы управления в UI. Даже try-pattern не поможет.

  3. Вы не сможете логику действия вынести в отдельный класс.

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

А объект.Действие() где? В сервисах?

Да, но это не имеет значения. Действие с Объектом подчиняется той же логике что и правила. Чем сложнее действие, тем хороших способов поместить его в объект.

Имеет ли валидатор и сервисы доступ к внутреннему устройству объекта?

К внутреннему - нет. Использует тот же публичный интерфейс, который используется например для вывода списка объектов на экран. В варианте Объект.Действие() при достаточно сложном действии оно будет перенесено в сервис и будет иметь такой же доступ к объекту, как и Правило, написанное вне Объекта.

Правильно ли я понимаю, что вы топите за анемичную модель?

Я топлю за то, чтобы писать меньше, а работало лучше.

Вы сейчас описали любую нетривиальную программу.

Но давайте пример проще: линейный односвязный список. Казалось бы никаких циклических ссылок. Но все равно на расте куча приседаний с борроучекером, а на python\java\C# проблем нет.

Экскаватор это не объект данных, а сервис, у которого нет наблюдаемого состояния. Так что нет семантической разницы между Копать(Эксковатор, Земля) и Эксковатор.Копать(Земля), но второй вариант лучше тестируется и имеет выше discoverability.

ООП для данной задачи в том, что Экскаватор это объект со своей структурой, декомпозицией. Земля это объект предметной области с Identity. Это означает что нельзя передать в метод невалидный ЗемляИД и, соответственно, не надо внутри метода Копать устраивать дополнительные проверки.

Снижается по сравнению с чем?

Вернемся к простому случаю. Бизнес-правило - в одном заказе не больше трех позиций.

Так как на уровне БД мы не контролируем количество позиций, то поднять из базы можем любое количество. Поэтому проверять это правило при загрузке мы не можем.

Кроме того: мы используем ORM, аля EF, который использует свойства объекта или конструктор для создания. Это значит что мы не можем проверять правила в конструкторе.

У нас остается один вариант: проверять правила при выполнении действия.

Итак у нас получается цепочка вызовов: Контроллер -> Объект.Действие -> Проверка. Естественно каждая стрелка может заключать еще 100500 вызовов, это не имеет значения

Я предлагаю заменять это на: Контроллер -> Правила.Проверка(объект).

Что это дает:

  • Меньше косвенность вызовов и меньше приседаний с инъекцией зависимостей в Объект.

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

  • Проверка может оперировать на состоянием объекта в памяти, а сделать запрос в базу.

  • Первый вариант может только выбросить исключение при непрохождении проверки, а второй вариант может возвращать true или false и использоваться для деактивации элементов интерфейса ДО выполнения действия пользователем.

Тестируемость обоих вариантов одинаковая. У второго даже выше, так как для создания Объекта ему надо передать кучу зависимостей, которые нужны не только для проверки. Формальные метрики при преобразовании первого варианта во второй не меняются (проверял). В целом если отказаться от инъекции сервисов в Domain Object можно значительно сократить объемы кода.

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

Эванс книгу выпустил в 2003, а фаулер упоминал Domain Model в POEAA в 2001. Но история началась гораздо раньше, с Гради Буча.

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

Вполне возможно для некоторых классов программ это так и есть. Например для симуляций или АСУТП - там у каждого компонента будет свое поведение.

Фаулер в POEAA описал паттрен domain model, где подход Буча применялся для корпоративных приложений. Описал он его поверхностно, не особо углубляясь в недостатки. Далее они с эвансом написали книгу про DDD, где первая глава про анализ, а остальные про DDD pattern language.

В POEAA кстати было и про стейтлесс, и про веб, и query builder, и современные БД. Так что вряд ли получится сказать, что вознесение DDD было обусловлено внешними факторами.

Буч, кстати, переобулся. В последней редакции OOAD не пишет что Чайник.Закипеть() - отличное решение. Он рассматривает реалистичные примеры приложений и иерархии классов их реализующие. И гораздо чаще на страницах книги можно встретить Экскаватор.Копать(земля), чем Земля.Копайся().

А чего особенного в связных списках? Почему для того же питона или java они проблем не представляют, а в rust это обязательно unsafe?

Имхо это как раз говорит о недостатках языка.

Вот мы и добрались до ключевого аспекта. Далеко не все "инварианты" можно выразить в системе типов. Особенно если они меняются с изменением требований.

Учитывая ваш коммент выше оказывается что даже простые "инварианты" фактически являются не инвариантами, а пред- и пост-условиями операций.

Если мы эти пред и пост условия отделим от объектов данных, то окажется, что мы сможем легко создавать условия, которые оперируют множеством сущностей. Например "заказ может содержать не более трёх позиций, если клиент не ВИП, и если позиции не содержат товары по акции".

Более того, эти пред и пост условия можно выразить в виде запросов к бд (с помощью ef) и даже не поднимать объекты в память

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

Это не ответ на вопрос. В базе может произойти что угодно.

Можно запретить часть методов, то есть поднимать можно, а процессить нельзя.

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

Интереснее другой вопрос: как написать код, чтобы не забыть вызвать логику валидации? Кто помешает добавить метод бизнес-логики, где нарушается инвариант и не вызывает проверку?

Если "антипаттерны" показывают характеристики лучше, чем идиоматичный код, то это не антипаттерны. В этом случае ваши идиомы являются антипаттернами.

А является ли инвариантом какого-то класса правило "не более трёх позиций в заказе, если клиент не ВИП"?

То есть если вы подняли из базы заказ с 4 позициями должна вылететь ошибка?

Почему должно смутить? У меня disk mark показывает разницу в 4 раза. Плюс чтение лучше оптимизируется кэшем, чем запись.

Это результаты замера на моем компьютере при работе кода из статьи.

Остаётся только вопрос что есть инвариант и является ли правило "заказ должен содержать не более трёх позиций" инвариантом класса заказа для приложения интернет-магазина.

Никто не говорил что нельзя. Просто недостатков от DDD больше, чем преимуществ.

Небольшая метафора:

Почесать ухо можно и пяткой ноги. Можно годиться этим, организовать конференцию на тему того как чесать ухо пяткой, рассмотреть все особенности чесания ушей пяткой, потом написать книгу с советами дяди боба о том как чесать ухо пяткой.

Но рукой все равно удобнее.

А вся остальная книга про то как надо делать в java.

На HDD скорость чтения почти в 10 раз превышает скорость записи.

Нет. Именно менее 2% уникальных строк, то есть текстовых элементов строк исходного файла.

Information

Rating
276-th
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity

Specialization

Software Architect, Delivery Manager
Lead
C#
.NET Core
Entity Framework
ASP.Net
Database
High-loaded systems
Designing application architecture
Git
PostgreSQL
Docker