Pull to refresh

Comments 367

Геттеры нужны для того, чтобы получить некоторое состояние текущего объекта.

Не обязательно. Еще геттеры нужны для того, чтобы получить атрибут сущности.


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

Вы никогда не просматривали список документов (реальных, бумажных) по заголовкам в поисках нужного? Вот ровно для этого нужен доступ к атрибуту "заголовок" сущности "документ".

Зачем заголовок документа в бизнес-процессе этого документа? Он у него есть внутри самой себя.
Получение заголовка некоторого документа снаружи выглядит как операция чтения, и для нее сойдут DTO документов. Нет?
Зачем заголовок документа в бизнес-процессе этого документа?

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


для нее сойдут DTO документов

А зачем добавлять DTO документов там, где можно обойтись самими документами?

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

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

Если вспомнить SRP, то у нас появляется прична дополнительная менять сущность — теперь не только для изменения логики работы документа, а еще для вывода в формочку
проведению операций с данными сущности отдельно от сущности (проверки разного рода).

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


в сущности документа есть некоторый заголовок, который подходит где-то снаружи — некоторый частный случай и везение

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


Если вспомнить SRP, то у нас появляется прична дополнительная менять сущность — теперь не только для изменения логики работы документа, а еще для вывода в формочку

Ну так "формочка" может быть основным местом "логики работы документа". И если она меняется, вам сущность все равно придется менять.

Фак мой мозг. Хотел понять вашу речь, но не могу. Не хватает части слов, правильных спряжений и прочей связанности. Заминусуйте меня.
туше, жаль нельзя поправить комментарии — писал на эмоциях :)
в минусе тут только мне быть, не переживайте на этот счет
Если я хочу отфильтровать документы по части заголовка, то имея доступ к заголовку я могу сделать просто:
documents.filter{ ...condition... }.toList()

или как-то так. А если работать через dto'шки, то как? Сначала получить все dto представляющие заголовки для всех документов, фильтрануть указанным образом, а потом из оставшихся dto переходить обратно к документам? Так себе идея, как мне кажется.
ровно для этого нужен доступ к атрибуту «заголовок» сущности «документ»

Ну так это старый добрый Anemic Vs Rich?
var headers[] = DocumentService.GetHeaders(document);

vs
var headers[] = document.GetHeaders();

Не похоже. Если бы отсутствие атрибутов было бы признаком анемичной модели, то у сущности вообще не было бы ничего — не поведения, ни состояния. Фактически, сущность в этом случае вырождается в идентификатор.

UFO just landed and posted this here
this.queueSize = this.DB.requestSize();

Может тогда имеет смысл вообще не использовать слово get, типа size(), как в коллекциях используется count() как часть языка
Раз это абстрактное и не всегда есть по месту…

Хотя, признаюсь, ваш пример ломает некоторые мои доводы :)
Может тогда имеет смысл вообще не использовать слово get

… а вы знаете, что в геттерах в .net слово get не используется? Просто queue.Length?

А-та-та

И в чём конкретно должна заключаться проблема такой конвенции?
Можно неожиданно для себя заюзать в каком-нибудь цикле метод, когда думал, что обращаешься к полю, и неожиданно увеличить алгоритмическую сложность на 4 порядка.
Можно неожиданно для себя заюзать в каком-нибудь цикле метод, когда думал, что обращаешься к полю

Эта проблема, кстати, решается отсутствием публичных полей. Что с точки зрения контракта очень полезно.


А то ведь и с методами можно влететь неслабо так.

А-та-та

Почему это вдруг?

То есть проблема в слове get?..
Нет, ну если это крестовый поход против слова «get», то тут и а-та-та можно, о чем речь? Но если проблема все же не в слове, а в возможности получить внутреннее состояние объекта, то хотелось бы все же знать, почему получить его у самого объекта а-та-та, а через dto уже нормально? При этом это dto до этого свойства же должно как-то добраться, а при отсутствии геттера, сдается мне, чуть ли не единственный способ это сделать — заставить порождать эти dto'шки сам объект…
Несколько вопросов:
  1. Зачем нужно получать getSize()? Чтобы узнать пустая ли она или чтобы узнать не переполнилась ли она? Такие срезы состояний вполне укладываются в поведение например isEmpty() или может быть даже в некоторые методы для работы, которую вы хотите выполнить с ней. Такой геттер просто толкает нас на нарушенеи инкапсуляции: данные тут, но поработает там где-нибудь и объект не может этого контроллировать...
  2. Зачем прятать настоящее поведение? На одном из проектов коллега сделал все через геттеры даже внутри одного класса, буквально все цепочки вызово шли через геттеры — подготовка запроса, отправка, трансформация ответа — все было через getAny() и потому на ревью углядели кучу проблем.
UFO just landed and posted this here
Зачем нужно получать getSize()?

Чтобы знать, какого размера пул потоков выделить для обработки.

зачем это знать? Это логика чтения? Или состояние для некоторой логики?
зачем это знать?

Затем, что объект, что-то потребляющий из очереди, хочет оптимизировать свою работу. Так что да, это "состояние для логики". Логики потребителя.

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

    public class MyStopwatch
    {
        private readonly DateTime _startTime;

        public MyStopwatch()
        {
            _startTime = DateTime.UtcNow;
        }

        public TimeSpan Elapsed
        {
            get { return DateTime.UtcNow - _startTime; }
        }
    }
Я писал только о том, что геттеры — отдельная ответственность для разного рода ДТО

Вот прямо для вас нашел пример с хорошим дизайном (на др языке только) и понятными абстракциями:


в самом посту я написал:
«Мама, дай мне пирог» не содержит в себе «Мама, купи муку, пожарь пирог и наконеееец дай мне пирог»

у вас именно этот случай
у вас именно этот случай

Неа. У него нет остановки, при каждом обращении возвращается время, прошедшее со старта.


Вот прямо для вас нашел пример с хорошим дизайном

А в этом примере, случайно, нет еще и метода getEvent(), который позволяет получить тот же самый event?

Извините, но это уже какая-то вкусовщина. Под капотом геттер — это самый обыкновенный метод, делающий ровно то же, что и $stopwatch->lap('foo') в вашем примере. Разве что он приведен к вырожденному случаю один объект — один event.
Я сам неоднократно бил джунов по рукам, когда они пытались печь пироги в геттерах. Например напрямую из геттера лезть в интернет или базу данных с тяжёлым запросом. Это плохо.
Но MyStopwatch.Elapsed из моего примера не печёт пироги — максимум — делает шаг к столу, на котором стоит тарелка с пирогом. Не будет же мама носить пирог в кармане на случай, если он внезапно понадобится сыну.
ну значит и контроллеры не нужны, а если и нужны, то не нужны модели…
у вас слишком простой пример, и под капотом не геттер… геттер = дай
а если «сваргань тонну всего и дай результат», это не геттер
у вас слишком простой пример

Жизненный зато. Когда с ним разберемся, можно будет сложные рассматривать.


и под капотом не геттер… геттер = дай

Там специально написано ключевое слово get. Как же это не геттер?

Откуда "геттер = дай", кстати? Почему не "получить"?

Я уже не помню где именно, но давным-давно в гайдах какой-то крупной компании я прочитал рекомендацию: никогда не прячьте в геттеры сложную логику.

Геттер правильно использовать в двух случаях:
1. Значение вообще не вычисляется, а просто получается как есть. То есть геттер есть посредник для доступа к приватному свойству.
2. Значение вычисляется какой-то простой формулой, не вызывающей сколько-нибудь значительной вычислительной нагрузки — просто чтобы не хранить копию данных. Например, у нас есть поля firstName и lastName, а fullName это тупо конкатенация. Или из года рождения вычислить возраст человека — простая арифметическая операция.

Если же для получения значения нужно провести объемные вычисления, особенно с сайд-эффектами, и тем более сходить в БД — используйте функции вида getSomeValue().

Причина: человек, читающий ваш код, интуитивно ожидает, что геттер это что-то быстрое и легкое, не влияющее на производительность, его можно применять без ограничений. А функция с глаголом get это процесс, который может быть тяжелым. Мне видится это логичным, я стараюсь придерживаться этого принципа. К сеттерам, кстати, тоже применимо.
Проблема только в том что походе с точки зрения автора поста(и не только его одного) ваша «функция с глаголом get» тоже является геттером.
Знаю, на эту тему можно развести терминологическую дискуссию.
Лично я в таких случаях говорю «функция-геттер». А если просто «геттер», то это свойство (под капотом, конечно, всё равно функция, но мимикрирует под свойство). И разница тут даже не в слове get, а в скобочках, которые явно показывают вызов.

Тут проблема не столько в терминологической дискуссии ("что такое геттер"), а в том, чтобы выяснить, что же конкретно автор статьи считает плохим, и почему.

Тут от ЯП сильно зависит. Далеко не во всех мимикрировать можно. А в некоторых где можно, это не сильно популярно — JS например.

К таким рекомендациям всегда есть вопрос: что конкретно считается геттером?


Потому что вот тут неподалеко, наоборот, если в методе есть бизнес-логика — это уже не геттер. Т.е. не "не кладите в геттер сложную логику," а "если там есть логика, это больше не геттер".

См. комментарий чуть выше.

Тут в .Net хабе пару дней назад появилась статья Неудачная статья про ускорение рефлексии, после которой я таки, в первый раз в своей жизни, нашел практическое применение сеттерам. Дело в том, что помощью сеттера мы можем присвоение значения выразить в виде выражения (где оно скрывается под вызовом функции), что позволяет нам создавать новый код во время выполнения используя более-менее простые Linq Expression вместо трудоемкого ML Emit. Но если вы на 100% не знаете зачем вам нужны геттеры и сеттеры, то на мой взгляд, лучше их не использовать, т.к. они, в лучшем случае, бесполезны.

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

… а что вредного в обычных таких property в .net?

Ужаса -Ужаса в таких property обычно нет, как, впрочем, и пользы от них. Они, скорее, напоминают "народные приметы" такие как "постучать по дереву" или "сплюнуть через левое плечо". Необходимость в них может, внезапно, возникнуть при необходимости сериализции, с использованием не самых продвинутых сериализаторов (привет newtonsoft), что опять же, скорее всего, связанно с Linq Expressions, дающими очень удобную оболочку над ML Emit.

Ужаса в таких property обычно нет, как, впрочем, и пользы от них.

Подождите, вы же только что написали "в лучшем случае бесполезны". Значит, не в лучшем — вредны. Что вредного-то?


Они, скорее, напоминают "народные приметы" такие как "постучать по дереву" или "сплюнуть через левое плечо".

Ну то есть стабильный публичный интерфейс или возможность контроля инвариантов — это народные приметы?

Чувствую пора подключать "авторитетные источники" )) У Рихтера в Главе 10 “Свойства” есть целый раздел “Осторожный подход к определению свойств (стр. 268)” в котором он объясняет почему ему эта возможность языка не нравится, и в целом, я разделяю его мнение.

Чувствую пора подключать "авторитетные источники"

Nope. Отсылка к авторитетам как ко мнению (в отличие от фактов) меня мало интересует. Там специально написано "Personally, I don’t like properties". Что занятнее, в качестве мотивации он там пишет, что свойства выглядят как поля, и это приводит к путанице. Лично я считаю, что лучше бы не было публичных полей, тогда не было бы и путаницы. Поля не могут заменить свойства в качестве публичного интерфейса. Методы — могут, да, но синтаксис с методами слишком многословный.

MVVM на .Net без публичных свойств — увы, не сделать.
Возможно есть какие-то костыльные фреймворки, которые прячут всё это в IL, но на чистом .Net — никак. Только свойства.
как тогда контролировать состояние?

Есть одно ключевое слово которое очень хорошо контролирует состояние — readonly (или final). Если же возникает необходимость сделать некоторое стояние изменяемым, то опять же лучше это сделать с помощью метода который явно дает понять вызывающему, что этот вызов повлечет "побочный эффект".

немного переформулирую вопрос — как верифицировать нужное состояние в таком случае?
То есть быть уверенным, что в нужном инварианте объект

Не совсем понял про "объект в инварианте" но если вы про ситуацию:


class Article {
     private String name

     public function getName(): String {
          return this.name
     }

     public doSomeMagic(): void{
         //Magic...
         this.name = "magic value";
     }
}

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


Мой изначальный комментарий был про:


     public function getName(): String {
          return this.name
     }

     public function setName(value: String): void {
          this.name = value;
     }
Мой изначальный комментарий был про:
public function setName(value: String): void {
this.name = value;
}

Да, я на эту тему и дискутирую…

Мы можем засетить любое значение, которое допустимо системой типов, а то, что это значение не возможно для текущего состояния бизнес-системы — это контролируется на других уровнях программы… В этом и минус, но это тема моего отдельного поста
Мой изначальный комментарий был про:

В такой ситуации пишите просто
public string Name {get;set;} 

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

В такой ситуации пишите просто


public string Name;

Преимущество в том, что если вам вдруг надо будет добавить геттер-сеттер (чего обычно не происходит), то вы их добавите.


Саму же логику в пропертях разбирали уже много раз с момента выхода еще самого первого C#.

то вы их добавите

Нет, в некоторых случаях это breaking change и придется переделать/перекомпилировать внешний код использующий такие поля классов.
Так например,C# позволяет с полями классов проделывать следующие:
var myObject = new MyObject();
var result = Calc(out myObject.MyField)

Если вы добавите геттер и сеттер (просто сделаете MiField пропертью) — тогда компиляция сломается.

Добавление логики в свойства это и есть breaking change который может привести к серьезным последствиям, ведь никто от ваших свойств не ожидал побочных эффектов и обращался к ним в цикле на миллион итераций из разных потоков.

Добавление логики в свойства это и есть breaking change

Вообще-то нет. И в этом как бы и смысл инкапсуляции: внутренняя реализация от вас скрыта и не должна вас интересовать.

Да и вообще иначе получается что любое изменение в коде это «breaking change». Потому что любое изменение может потенциально привести к серьезным последствиям и побочным эффектам.

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


внутренняя реализация от вас скрыта и не должна вас интересовать.

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


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


try
{
      var name = obj.Name
}
catch(Exception)
{
...
}
От свойств же, я побочных эффектов не ожидаю

… от геттеров. Потому что это написано в design guidelines. А вот от сеттеров должны ожидать.

Вы не находите такой код странным?


try
{
    var obj = new MyClass{Name = "John Smith"};
}
catch(SqlException)
{
...
}

А вот тут, вроде, становится понятно, что происходит:


var obj = new MyClass();
try
{
    var obj = obj.Init(name: "John Smith");
}
catch(SqlException)
{
...
}

а вот в этом случае, сеттеры вообще не смогут выполнить задуманное:


var obj = new MyClass();
try
{
    var obj = await obj.InitAsync(name: "John Smith");
}
catch(Exception)
{
...
}
Вы не находите такой код странным?

Нахожу, но не потому, что вы ловите исключение, а потому, что вы ловите исключение SQL. Здравствуй, протекшая абстракция.


А вот тут, вроде, становится понятно, что происходит:

Да, но куда делась обработка исключения в конструкторе?


(и, опять же, личное предпочтение: если за конструктором следует Init, почему бы не запихнуть Init в конструктор? И это, кстати, пример того, что не понятно: объект между конструктором и Init консистентен или нет?)


а вот в этом случае, сеттеры вообще не смогут выполнить задуманное:

И это прекрасно, потому что здесь внимание акцентируется на том, что операция (потенциально) длительная и с потерей контекста.

Есть множество доводов в пользу того, что бы не помещать

Ни один из которых не отменяет непонимания, является ли объект после конструктора, но до Init, консистентным.

Зависит от того что вы вкладываете в понятие "консистентный". Как-минимум он должен быть консистентным настолько, что бы можно было вызвать метод Init


Например, будет ли объект типа SqlConnection консистентым перед вызовом Open()? (рассмотрим вариант, что connectionString указан)?

Зависит от того что вы вкладываете в понятие "консистентный".

Соответствие бизнес-правилам.


Как-минимум он должен быть консистентным настолько, что бы можно было вызвать метод Init

Ну вот если на нем можно вызвать только Init, мне это кажется очень странным дизайном.


Например, будет ли объект типа SqlConnection консистентым перед вызовом Open()?

У него можно вызвать не только Open после создания.

С умом надо просто помещать.
public string Name
{
  get => _name;
 set
   {
     if(string.IsNullOrWhiteSpace(value))
          throw new ApplicationException("Укажите имя!");
       _name = value;
    }
}


А с SQL в геттер это банально нарушение S из SOLID обычно.

> Но все приведенные выше примеры использования явно этим критериям не удовлетворяют

А вот пример, [удовлетворяющий этим требованиям](https://github.com/dotnet/runtime/issues/34648#issuecomment-627541970). Суть в том, что у класса GC был метод GetGCMemoryInfo, который возвращал структуру GCMemoryInfo с какими-то свойствами, совсем без какой-либо логики внутри. Теперь в .NET 5 хотят добавить новые диагностики о последнем GC. Логично было бы поместить их на этом самом GCMemoryInfo, чтобы не плодить новых API на самом System.GC. Но GCMemoryInfo — структура, добавить ещё 60 байт полей к ней — не самая лучшая идея. Если бы изначальные данные на GCMemoryInfo были полями, то любое решение этой проблемы было бы неприглядным (два API / свойства и поля на одном и том же типе). Тот факт, что это свойства, позволил просто сделать GCMemoryInfo тонкой обёрткой над внутренним классом.

Обращение к полю из разных потоков, как правило, является такой же ошибкой.


А с циклом из миллиона итераций всё просто: надо писать такие методы доступа, которые допустимо использовать в цикле на миллион итераций.

В такой ситуации пишите просто

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

Преимущество в том, что если вам вдруг надо будет добавить геттер-сеттер, то вы их добавите.

А теперь представьте себе что ваш «Name» используется в 100500 мест. Вы решили поменять его на геттер с кэшем и на сеттер с логгингом. По вашей логике надо делать методы. Сделали методы «SetNameWithLogging(string name)» и «GetNameWithCache()» и теперь вам надо поменять эти самые 100500 использований с «Name» на новые методы.

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

чего обычно не происходит

Если этого у вас не происходит, то это не значит что этого ни у кого не происходит.

Саму же логику в пропертях разбирали уже много раз с момента выхода еще самого первого C#.

И до чего доразбирались? Кто-то например придумал как те же байндинги в каком-нибудь MVVM без «логики в пропертях» делать?

Вообще ветка началась с фразы "Но если вы на 100% не знаете зачем вам нужны геттеры и сеттеры, то на мой взгляд, лучше их не использовать" в MVVM, я полагаю, вы знаете зачем вам геттеры и сеттеры.


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

Вообще ветка началась с фразы «Но если вы на 100% не знаете зачем вам нужны геттеры и сеттеры, то на мой взгляд, лучше их не использовать»

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

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

Это вообще отдельный вопрос и он не имеет никакого отношения к тому использовать геттеры/сеттеры или методы.

С конвенциями вообще есть очень показательный пример — раньше можно было встретить рекомендацию всегда копировать ивент в локальную переменную и только после этого проверять её на null:


public event EventHandler MyEvent;

protected OnMyEvent()
{
    var myEvent = MyEvent;
    if(myEvent != null){
         myEvent(this, EventArgs.Empty);
    }
}

Мол таким образом мы можем быть уверенны, что от свойства не отписались в другом потоке. Но тот факт, что компилятор просто убирал эту переменную в Release никого не смущал. Дошло дело до того, что Microsoft поменяла компилятор для распознавания этого шаблона.

Компилятор не мог "просто убирать" эту переменную в Release, потому что цель его оптимизаций — в ускорении кода, а не в его замедлении.

Можно ссылку на авторитетный источник?

CLR via C# Джефри Рихтер 3-е русское издание Глава 11. События (стр. 277)

Спасибо. У меня нет под рукой третьего издания, у меня четвертое. И там написано следующее:


However, what a lot of developers don’t realize is that this code could be optimized by the compiler to remove the local temp variable entirely.

Не "просто убирал", а мог убрать. И это, на самом деле, поправили еще в .net 2.0, причем не "распознаванием шаблона", а усилением модели памяти.


https://faithlife.codes/blog/2008/11/events_and_threads_part_4/

Если бы я знал, что дело дойдет до ссылок "на авторитетные источники" я бы, конечно, комментарий написал бы более аккуратно. Но на самом деле я просто хотел показать, что люди (в том числе и программисты) часто следуют ритуалам не понимая причин их появления, и даже не пытаясь поставить под сомнение их разумность.

Но на самом деле я просто хотел показать, что люди (в том числе и программисты) часто следуют ритуалам не понимая причин их появления, и даже не пытаясь поставить под сомнение их разумность.

Но выбрали для этого плохой пример. Потому что обычно, когда показывают этот конкретный шаблон, объясняют, откуда он взялся (=причины появления), а как только его разумность встала под вопрос из-за неочевидного поведения рантайма, поправили рантайм, чтобы разумность сохранялась.

Пример хороший, поскольку люди применяли этот подход и настойчиво рекомендовали другим, еще в то время когда он реально не работал, не смотря на вроде бы логичное обоснование. Это исправление рантайма, согласно тому же Рихтору (сам не проверял, может это уже устарело), так и не задокументировали, так что теоретически volatile read тут нужен

Пример хороший, поскольку люди применяли этот подход и настойчиво рекомендовали другим, еще в то время когда он реально не работал

Это когда он "реально не работал"? Хотя бы когда это могло не работать?


Это исправление рантайма, согласно тому же Рихтору (сам не проверял, может это уже устарело), так и не задокументировали

Задокументировали в октябре 2005-го года, "Strong Model 2: .NET Framework 2.0":


Reads and writes cannot be introduced.

Другое дело, что это касается только рантаймов MS.

Преимущество в том, что если вам вдруг надо будет добавить геттер-сеттер (чего обычно не происходит), то вы их добавите.

Неа, это изменение публичного контракта.

Как мне кажется, проблема преувеличина: не любая работа с данными сущности нарушает инкапсуляцию. Сущности обычно не сами по себе существуют, а их данные нужны для принятия решений в каких-то юзкейсах. И вполне нормально дёрнуть какой то getStatus() для этого.

getStatus плох не сам по себе, а как часть сравнения на основании которого меняется состояние. Логичнее сравнение статусов и закрытие засунуть внутрь объекта, что-то вроде order.CloseByStatus(input.Status) Это инкапсулирует и геттер, и проверку, и setClosed.

Я именно про внешнюю логику. Типа реджекта попытки создания пользователем нового заказа, если у него есть незакрытые. Можно, конечно, на каждый статус сделать isClosed/isDraft/isCheckingOut/isApproving/isApproved/ и ещё 100500, но как-то оно не очень, когда реально статусов 100500

То есть нужно связать пользователя и заказ?
Может в рамках логики заказа пользователь — несколько иной объект, нежели пользователь в рамках логики работы с пользователем?
class Order
{
    public function __construct(Order/User $user) 
    {
         // верифицируем -- можем ли мы создать данный заказ
         // при том данный $user не выглядит как широкий интерфейс под все
         $user->canCreate();
    }
}
А если это зависит не юзера, a например от того сколько открытых заказов и сколько свободных слотов на данный момемнт существует в системе?
сколько свободных слотов на данный момемнт существует в системе?

Кажется, такую информацию должны принести курьеры дата-объекты, у которых понятное дело будут геттеры
Как с вами сложно. Ну забудьте про слоты и возьмите вот так: «a например от того сколько открытых заказов на данный момент существует в системе».

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

это зависит не юзера, a например от того сколько открытых заказов и сколько свободных слотов на данный момемнт существует в системе?

Есть сущность Заказ и не совсем понятно, какая именно сущность мне принесет эти данные. Дополнительно не ясно — зачем это должна быть сущность, а не какой-то dto/readModel?
Хэндлер, который будет создавать заказ и для этого когда ему нужны будут данные для принятия решения — возьмет их из слоя приложения и спокойно сделает свою работу. Либо создаст, либо ругнется. Не совсем понятно, зачем связывать это с другим бизнес-доменом и надувать тот самый домен под этот кейс и под другие в других доменах, чем намертво свяжет эти домены.
Вопросы я задаю не из-за того, что я вас не понимаю, а для того чтобы вести вас по вашей. и по своей же логике, чтобы показать плюсы и минусы :)

Не, вы просто «плодите сущности». Конечно всегда можно к примеру без особого контекста добавить что-то, что сделает этот пример абсурдным. Но смысл?

Есть сущность Заказ и не совсем понятно, какая именно сущность мне принесет эти данные.

Ну так это зависит от кучи факторов и конкретной ситуации. И есть ситуации когда проще и логичнее приделать к заказу геттер на этот самый статус. И естественно есть и ситуации когда это будет не самой удачной идеей.

Да и вообще может у вас этот самый статус это enum-флаг. Или вам его надо где-то протоколировать или показывать пользователю. Или вам надо где-то показывать количество заказов с определённым статусом. Или…
UFO just landed and posted this here

Я не про изменение самой сущности, а про принятия решений в других местах, типа только один может быть notClosed

UFO just landed and posted this here

Так или иначе этому OrderList нужен будет order.getStatus()

UFO just landed and posted this here
getStatus() === 'some status' равносильно can* / is* методу. Так зачем вам getStatus?

Ну например потому что не всегда нужно только такое сравнение и ничего больше. Например сам статус может быть enum-flag'ом или даже сам иметь какие-то проперти и/или методы.
Или скажем вам будет нужна сортировка/группировка/фильтрация по статусу.
UFO just landed and posted this here

Даже сложный статус — обычно не сущность, а ValueObject и его вполне нормально отдавать наружу.

UFO just landed and posted this here

Если "отдавать N наружу" заменить на "связать через N с другим доменом" — так мне, кажется, получится донести мою мысль

UFO just landed and posted this here
Если вы завернете «только такое» сравнение в метод, то вы всегда с легкостью его расширите

Это если вам его одинаково надо будет «расширять» для всех мест где оно используется.

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

Но и не значит что ненужно. Зависит от ситуации.

Вообще плохо, когда агрегат отдает свои внутренние сущности наружу.

С чего вы взяли что эти «сущности» обязательно «внутренние»?

Сортировка, группировка и фильтрация — дело вью модели.

Вот вообще не согласен. Часто чем раньше это делается, тем лучше. Какой смысл таскать повсюду 100500 объектов если на самом деле вам нужно всего парочку из них? По хорошему это надо вообще на уровне базы данных делать.
И если у вас скажем используется какой-нибудь ORM, то фильтровать/сортировать/группировать по проперти/геттерам это самое то. Особенно если это вещи вроде «примитивного» статуса, которые могут и в базе данных хранится в виде простого типа данных.
UFO just landed and posted this here
А если не одинаково, то еще и помнить почему очень похожие ифы чуть-чуть отличаются в разных местах, вместо того, чтобы инкапсулировать логику в методе с говорящим названием.

Это уже софистика начинается. Я точно так же могу написать что проще посмотреть что делают ифы, чем смотреть на 100500 методов с не местами особо отличающимися названиями.
Право на существование имеют оба варианта. И оба варианта можно криво использовать.

Возможно в каких-то специфических случаях.

У вас это может быть так, у кого-то по другому.

Статус ордера — внутренняя сущность ордера.

Это если у вас пользователь не хочет этот самый статус видеть. А по моему опыту он это обычно хочет.

Вью модель — простые ДТО с данными для отображения.

Откуда они у вас берутся? Из воздуха? Или вы их всё-таки каким-то образом «получаете» из ваших бизнес-объектов? И зачем тогда сначала перегонять все бизнес-объекты в дто только для того чтобы потом 99% из них отсеять? Почему сначала не отсеить ненужные бизнес-объекты?

Запрос в БД — один из способов подготовить данные для отображения.

Хм, вы опыт работы с ORM имеете? С каким-нибудь интегрированными query-языками вроде того же LINQ?
Статус ордера — внутренняя сущность ордера.

Неа. Это атрибут сущности, описанный в ER-модели (или любой другой модели, описывающей структуру ваших бизнес-сущностей) и участвующий в сценариях использования. Ничего внутреннего в нем нет.

Сортировка, группировка и фильтрация — дело вью модели. Там вы работаете только с ДТОхами, которые вообще могут иметь паблик поля.

… и для каждого внешнего юз-кейсы вы пишете свой DTO и парный ему код конверсии в бизнес-сущности — ведь снаружи бизнес-сущности про эти данные никто не знает.

UFO just landed and posted this here
А распухание совсем не обязательно кстати. Да и вообще если вам что-то нужно для фильтрации/сортировки/группировки, то это опять же совсем не означает что это будет нужно и для показа в UI. То есть получается вы с таким подходом в некоторых ситуациях просто пишите абсолютно лишний код.

Более того даже если это и не так, то делая отдельные DTO для каждого use case, вы таким образом создаёте кучу редундантного кода. Или получаете сложные иерархии наследования. И я не уверен что это всегда лучше чем «распухание модели».

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

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

Как минимум, плохо то, что придётся всю эту кучу кода поддерживать. Добавляем важный атрибут в сущность и нужно менять DTO для каждого или почти каждого юзкейса. Причём, если поддерживать изоляцию слоёв, то DTO эти будут разбросаны по слою юзкейсов(сервисов приложения). Вроде изменений на пару строк, но затронуты могут быть десятки файлов.

не совсем понимаю:
  • я работаю с одним доменом, в нем свои сущности, все изолировано и понятно. Тот факт, что файлы похожие есть в другом месте меня не сильно волнует, тк изоляция
  • файлов меньше, но все сплетено и я работаю не с одним доменом, а сразу с двумя/тремя/десятью (реальность текущих проектов)


чем второй способ лучше? Меньшим числом файлов? Так правильно — переиспользование = переплетение, файлов меньше, но все мертвым клеем склееено, что побуждает и костыли городить и сложность растет проекта

я работаю с одним доменом, в нем свои сущности, все изолировано и понятно.

Если "все изолировано", то как DTO получают данные для отображения?

DTO и связанные бизнес-объекты получаются через слой приложения,
  • для создания Order не нужно идти в слой пользователя и через какой-то UserManager, который даст тебе User через UserRepository
  • нужно через свой репозиторий получить свои данные напрямую из приложения
DTO и связанные бизнес-объекты получаются через слой приложения,

Откуда в DTO для заказа (OrderViewModel) берутся данные заказа (Order) — номер, сумма, дата и так далее?


нужно через свой репозиторий получить свои данные напрямую из приложения

Подождите, подождите, я вас не понял. Что значит "через свой репозиторий"? При создании заказа данные о пользователе получаются не через бизнес-сущность пользователя?

При создании заказа данные о пользователе получаются не через бизнес-сущность пользователя?

Ну да, разве можно иначе обеспечить внешнюю НЕ связанность (coupling) иначе?
Обратный путь, используемый сейчас повсеместно — связать, что опять возвращает нас к той же проблеме высокой связанности, сложного кода, плохого maintainability и вот это все, сложное тестирвоание и т.д… геттеры и сеттеры, которы толкают делать логику снаружи, крч все то, о чем я и говорю в этой и предыдущей статье
Ну да, разве можно обеспечить внешнюю НЕ связанность (coupling) иначе?

… а ее надо обеспечивать? Почему это вдруг две сущности домена, связанные отношением в ER-модели, не должны иметь связности?


Потому что вы только что создали гигантскую проблему по обеспечению согласованности (consistency) между поведением одной и той же сущности в разных сценариях использования.


Возьмем простой, казалось бы, кейс: был у пользователя один FullName, решили разбить на First, Middle и Last. В "традиционной" схеме нужно поменять в одном месте. В том, что, как я вас понял, вы предлагаете — во всех местах использования (и не забыть сделать это одинаково).

UFO just landed and posted this here

То есть у заказа и DTO для заказа — разные хранилища?..

UFO just landed and posted this here
Да, а почему б и нет?

Потому что это усложнение, которое надо именно что оправдывать, а не рассматривать, как архитектуру по умолчанию.


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

UFO just landed and posted this here
Хранилище для ДТО апдейтится только по ивенту.

Что это меняет в написанном мной комментарии?


Но это может быть и просто другой лоадер данных из БД с маппингом на ДТО

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

UFO just landed and posted this here
Не совсем понимаю, что вы имеете ввиду через интеграцию через бд?

Когда несколько частей системы (или, что хуже, несколько систем) используют для совместного доступа к одной и той же информации БД (или другое хранилище).


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

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


Решение (1): делаем бизнес-сущность (теперь уже программную) Order, у нее свойство Comment, биндим это свойство на форму. Это, как нам неоднократно говорили в посте и комментариях, нарушение инкапсуляции этой самой сущности, потому что это показывает для нас тот факт, что у нее есть такое свойство (хотя этот факт описан в общедоступной доменной модели...).


Хорошо, берем решение (2): создаем DTO OrderViewModel, в котором есть свойство Comment, которое выводим на форме. Для того, чтобы его обновить, заводим OrderUpdateCommand, в котором тоже есть возможность задать этот комментарий (не очень важно, через свойство, через конструктор, через билдер — не суть). И это, почему-то, не нарушение инкапсуляции, хотя мы точно видим: вот есть атрибут домена, вот так он читается, вот так он пишется. Почему это не нарушение инкапсуляции? Какую информацию мы раскрыли в решении (1), которой не известно в решении (2)?


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

Дадада, продолжим с тем же комментарием. Внезапно прибегает бизнес и говорит "все срочные заказы" должны быть в интерфейсе красненьким, а в бэке обрабатываться отдельным потоком, чтобы грустно не было. А что такое "срочный"? Это тот, у которого в комментарии написано "СРОЧНО!!!".


Понятно, что правильно сделать признак IsUrgent, который расчитывать в бизнес-сущности, и дотащить его до ДТО. Но что мешает программисту, который торопится, просто написать в UI if (orderDto.Comment.Contains("СРОЧНО!!!")) color = "red"?

который расчитывать в бизнес-сущности
Почему этим бизнес-объект заниматься должен? Мне например не ясно… В реальном приложении (например на текущей работе) 500 этажей абстракций, внутри бизнес-объекты, дотащить такое поведение из бизнес-сущности до формы представляется сложным даже с геттерами
Почему этим бизнес-объект заниматься должен?

А кто должен? С точки зрения доменной модели (которая модель, а не код), "срочный" — это еще один атрибут сущности "заказ", прямо так в модели после обсуждения требований и написали.

Почему это не нарушение инкапсуляции?
Как это? Если иметь ввиду, что «инкапсуляция» — знания во всей вашей системе, то да — без ее нарушения не обойтись…
Если под инкапсуляцией понимать сокрытие поведения и данных в некоторой абстракции, выраженную в виде некоторых классов (объектов) и их поведения, то вполне инкапсуляция соблюдается и реализуется…
Как это?

Вот так. Почему?


Если под инкапсуляцией понимать сокрытие поведения и данных в некоторой абстракции, выраженную в виде некоторых классов (объектов) и их поведения, то вполне инкапсуляция соблюдается и реализуется…

Какое конкретно поведение и данные сокрыты в этом примере?

UFO just landed and posted this here
И находится он в доменной модели. В рид модели ее нет, в рид модели есть только необходимые для отображения данные, без каких-либо проверок.

Так нам необходимо для отображения знать редактируемая сущность или нет, срочная или нет. Откуда возьмутся в DTO без логики поля isUrgent и isEditable если их нет в базе, если они чистая логика в сущности?

UFO just landed and posted this here

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


Допустим, разные таблицы или вообще разные базы, одна из которых noSQL. Дублируем логику в хэндлере?

UFO just landed and posted this here
Тут есть нюанс, пишут в бд одном месте, а читают в другом (чаще всего еще и с реплики).

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


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

Это явно не случай "одно хранилище", про который я писал выше. Тут будут другие веселые проблемы. начинающиеся с консистентности.


Да, если весь ваш апдейт сводится к присваиванию строки, то смысла особо нет что-то придумывать.

Я, понимаете ли, не спрашивал о смысле что-то придумывать. Я спрашивал, почему в одном случае чтение значения — это нарушение инкапсуляции, а в другом чтение того же самого значения — не нарушение. Хотя сценарий использования полностью идентичен.


Например, что коммент не может быть изменен, если заказ в финальном статусе.

… это прекрасно делается без разделения на модель для чтения и модель для записи.


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

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


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

Хех, можно нехило так извращаться использую паттерн Memento. Сделать у сущности все поля и свойства приватные. Делаем метод CreateMemento() который возвращает просто DTO с приватными данными сущности.

Можно. Только инкапсуляция не улучшится по сравнению с тем, как если бы мы напрямую в сущность ходили.

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

Ну вот представьте себе что таких как вы 100 человек. Допустим вы все работаете с одной и той же таблицей в базе данных. Каждый для себя пишет все «слои» целиком? То есть у каждого им самим написанный доступ к базе данных, свои мэппинги из базы в бизнес-объекты, свои бизнес-объекты и так далее и тому подобное?

в том-то и дело, что если 100 человек, то домен ДОЛЖЕН быть изолирован абсолютно от всего…

я работал в компании на 40 человек, там даже в виде процессов работы были требования как именно работать, другой вопрос, что на стороне кода таких требований не было в силу и слабой инженерной подготовки и фиг его знает чего… то есть работа с изоляцией проводилась в неком виде, просто в виде управления разработкой, то есть проблема была видно, но в поле зрения не инженеров, а СТО

я же иду дальше и в коде предлагаю такое делать, кроме того все книги об этом и говорят

свои маппинги — да, свои бизнес-объекты — да
доступ к БД — нет, приложение же одно, или да — если не одно приложение
свои маппинги — да, свои бизнес-объекты — да
доступ к БД — нет, приложение же одно, или да — если не одно приложение


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

я же иду дальше и в коде предлагаю такое делать, кроме того все книги об этом и говорят

Вот прямо все-все книги? Или скажем только книги написанные сторонниками микросервисов и других аналогичных подходов? ;)

П.С. И я так понимаю опыт разработки у вас не то чтобы очень большой. Два года? Три?
П.П.С. И не сочтите за оскорбление, но если я прав(а я достаточно сильно уверен что это так), то может быть стоит всё-таки пока больше самому учиться и меньше пытаться учить других?
Вот прямо все-все книги?

Меня это утверждение тоже весьма смутило, прямо скажем.

И я так понимаю опыт разработки у вас не то чтобы очень большой. Два года? Три?
3 (почти)

может быть стоит всё-таки пока больше самому учиться и меньше пытаться учить других?

Чем метод обучения других — не самостоятельный метод обучения меня самого? Вы немного успокойтесь :)

Или скажем только книги написанные сторонниками микросервисов и других аналогичных подходов? ;)

Ну давайте к теме подойдем все же. Все книги точно про низкую внешнюю связанность (закон Деметру к примеру, если на начальном уровне). low coupling в GRASP и т.д… а если речь про ООП — то инкапсуляция, геттеры же ей следуют из определения, но семантически ее нарушают

К агрегаторам и элементам ДДД пошли в комментариях, моя тема в посту была довольно органична и касалась только геттеров:
  • которыми называют все методы
  • геттеры сущностей, задача которых — вязать, что есть нарушение инкапсуляции. Вы уж извините — это так и есть
Чем метод обучения других — не самостоятельный метод обучения меня самого?

Тем, что вы кого-то можете научить неправильно.


Все книги точно про низкую внешнюю связанность

Что такое "внешняя" связность?


low coupling в GRASP

Low coupling в GRASP точно не про то, чтобы дублировать код, выполняющий одну и ту же бизнес-задачу.


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

Кажется, вы это забыли объяснить в посте.


которыми называют все методы

Геттерами нельзя "называть все методы". Геттер — это весьма конкретная вещь. Или вы неявно переносите подходы из "вашего любимого языка" на все остальные?


геттеры сущностей, задача которых — вязать

Задача которых, простите, что?


что есть нарушение инкапсуляции. Вы уж извините — это так и есть

Не, не извиним. Я не понимаю, почему это нарушение инкапсуляции.

Что такое «внешняя» связность?

Чтобы не устраивать эквилибристику с языком: связанность и связность, давно многие используют термины внутренней и внешней, дабы было понятнее
давно многие используют термины внутренней и внешней, дабы было понятнее

Не знаю, кто такие "многие". Мне не понятно. Приведите, пожалуйста, определение.

так, этим сами займитесь :)

для вас ниже я привел английское слово coupling, которое однозначно тракуется всеми и не надо меня втягивать, чем связность отличается от связанности, мне не ясно это например (не суть связей, а именно разница русских слов, почему одно для внешних связей становится, другое про внутренности — для меня загадка)
для вас ниже я привел английское слово coupling, которое однозначно тракуется всеми

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


мне не ясно это например

… но вы продолжаете рассуждать о том, что coupling надо понижать?

в одном домене? я говорил про разные домены

Ну так пользователь, создавший заказ, и сам заказ — они в одном домене. И даже в одном bounded context, кстати.

UFO just landed and posted this here
Не обязательно.

В контексте примера — обязательно.


Для домена заказов неважно кто ваш Customer, нужен только идентификатор.

А какие сущности у вас тогда остаются в домене заказов и почему?

Low coupling в GRASP точно не про то, чтобы дублировать код, выполняющий одну и ту же бизнес-задачу.

getter несет одну задачу: связать, скостить путь, сковать сразу несколько доменов между собой и увеличить сложность

Конечно, нет. Задача геттера — предоставить информацию, которую потребитель хочет получить.


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

UFO just landed and posted this here
да уже все, «3 года опыта» увидели,
доводы по теме будут более агрессивные, простые и острые

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

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

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

Подождите, при чем тут внутреннее устройство объекта? Стрелка секундомера — это не внутреннее устройство, это публичный интерфейс. Там внутри может быть что угодно, от механики до малины.

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

С чего бы? Если другой домен уже выставил публичный интерфейс, в котором все это есть?


Сам этот интерфейс и ДТО принадлежат домену получателя.

И тогда у вас два домена начинают взаимно зависеть друг от друга.


Как и кем будет реализован этот интефейс — это уже другая история.

Не, серьезно. Вот у нас есть "домен заказов", который "объявил требование" CustomerShippingAdressProvider. И есть "домен покупателей", где эта информация содержится. Кто конкретно реализует этот интерфейс, учитывая, что "домен заказов" уже зависит от "домена покупателей" для реализации, собственно, бизнес-задачи?

UFO just landed and posted this here
как раз, чтобы не завязывать друг на друга два домена.

Ну так вам все равно придется это сделать, поскольку домены связаны.


Погуглите архитектуру портов и адаптеров.

В архитектуре портов и адаптеров, вообще-то, есть inbound ports.


Возьмем, скажем, тот же "домен покупателей". Когда другим сервисам надо создать нового покупателя, они что делают? Архитектура портов и адаптеров предполагает, что у вас есть inbound port "создать покупателя", который является публичным интерфейсом этого домена. Разве не так? Если не так, то как?


Реализация интерфейса находится вне домена. Это простой адаптер.

Угу. Вне какого домена?

UFO just landed and posted this here
Вне какого домена. Адаптеры — штука апликейшен слоя, а не доменного.

О, прекрасно. Так каким же образом адаптер, находящийся вне домена покупателей, получит информацию о покупателях?


Скорей всего, юзает его через тот самый inbound port, но это не касается домена заказов.

Ну то есть у домена покупателей все-таки есть inbound port, который позволяет получить информацию о покупателе?

В вашем стеке есть годный материал и нужные слова, чтобы ни вы, ни Kanut не обвиняли меня, что якобы «мой стек» какой-то не такой или что я горожу отсебятину


Для поддержки разделения агрегатов и сохранения четких границ между ними рекомендуется в модели предметной области DDD запретить прямой переход между агрегатами и иметь только поле внешнего ключа (FK), как реализовано в модели предметной области микрослужбы заказов в приложении eShopOnContainers. Сущность Order имеет только поле FK для покупателя, и в ней отсутствует свойство навигации EF Core, как показано в следующем коде...


docs.microsoft.com/ru-ru/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/microservice-domain-model#the-domain-entity-pattern
docs.microsoft.com/ru-ru/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/net-core-microservice-domain-model#map-fields-without-properties

А ничего, что это книга про микросервисную архитектуру?


Что, впрочем, не мешает модели из этой "годной статьи" иметь геттеры:


public class Order
{
  public Address Address { get; private set; }
  public int? GetBuyerId => _buyerId;
  public OrderStatus OrderStatus { get; private set; }
  public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;

  //бонус: Get, но не геттер
  public decimal GetTotal()
  {
    return _orderItems.Sum(o => o.GetUnits() * o.GetUnitPrice());
  }
}
1. ну оба агрегата находятся в одной кодовой базе, а значит не имеет значения и могло бы быть и в монолите, тк эти принципы в DDD, а не в микросервисах


2. да, вторая ссылка (добавил позже создания коммента) объясняет зачем они (геттеры, там в коде и сеттеры есть) и объясняет, что они стараются делать, чтобы их убрать… кортокто — проблема с гидрацией и хранением полей в БД
docs.microsoft.com/ru-ru/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/net-core-microservice-domain-model#map-fields-without-properties
ну оба агрегата находятся в одной кодовой базе, а значит не имеет значения

Нет, не значит.


тк эти принципы в DDD

… а еще не весь код пишется по DDD, внезапно.


объясняет зачем они и объясняет, что они стараются делать, чтобы их убрать…

Нет. Там написано практически обратное тому, что вы говорите:


With the feature in EF Core 1.1 or later to map columns to fields, it is also possible to not use properties. Instead, you can just map columns from a table to fields. A common use case for this is private fields for an internal state that does not need to be accessed from outside the entity. For example, in the preceding OrderAggregate code example, there are several private fields, like the _paymentMethodId field, that have no related property for either a setter or getter.

Во-первых, там нет ни слова про "стараются убрать". Во-вторых, там просто сказано, что теперь-то, наконец, можно мапить поля на БД, если свойства не нужны (и приводится пример такого поля, которое обрабатывается только внутри агрегата).


Нет ровным счетом никакой необходимости добавлять вот этот геттер:


public int? GetBuyerId => _buyerId;

...кроме как внешний доступ, потому что работа с БД происходит через поле _buyerId.


Собственно, этот геттер и есть тот самый FK, про который вы говорите, что делает весь ваш аргумент несколько пустым: да, MS рекомендует не пересекать границы агрегата, нет, из этого никак не вытекает отказ от геттеров.


или что я горожу отсебятину

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

в статье про DDD ничего нет, есть про нарушение инкапсуляции и высокой связанности программ

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

Ну так и не ссылайтесь тогда на "тк эти принципы в DDD".


есть про нарушение инкапсуляции

Угу. Про то, что вы считаете наличие у секундомера стрелки нарушением инкапсуляции (хорошо, это не в статье, а в комментах).

ну погодите, мы же концепцию рассматриваем, они, понятное, дело в чистом виде показывает определенные признаки, в том числе отделения одной логики от другой

тк ответственность, связанность и вот это все…

Ну так и не ссылайтесь тогда на «тк эти принципы в DDD».
Буду, тк принципы в DDD не появились изнутри, это все те же старые добрые принципы программирования, на которые все большие команды забили болт или не забивали…

мы же концепцию рассматриваем

Какую конкретно концепцию мы рассматриваем?


дело в чистом виде показывает определенные признаки, в том числе отделения одной логики от другой

Я не понимаю, что вы хотите сказать. Совсем.


Буду

А если будете, то я вам буду напоминать, что DDD -не единственный работающий подход в программировании.


все те же старые добрые принципы программирования

Вот, например, DRY. На который вы предлагаете забить болт, да.

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

Принцип low coupling тоже сложнее, чем вы тут пишете. И что?

что DDD -не единственный работающий подход в программировании.

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

Почему те моменты в документации описаны с упоминанием DDD — -это к маркетологам и авторам того материала, рекомендации атм отличные вне контекста какой-либо парадигмы и описаны старым добрым Дейкстрой.

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

А я и не собираюсь. Пойнт в том, что если я что-то пишу не по DDD, это вам придется доказывать мне, что принцип, описанный в DDD, применим и к тому, что я пишу, тоже.


описаны старым добрым Дейкстрой.

Вот и ссылайтесь на Дейкстру.


Я, наверное, все…

Typical. So typical. Набросаем громких утверждений, а как только к ним будут заданы вопросы, вместо аргументов — "я все".


Это, кстати, и к вопросу "чем метод обучения других — не самостоятельный метод обучения меня самого?" — тем, что вы, конечно, вправе решить, когда вам надоело учиться. Но вы, на самом деле, не очень-то вправе прекратить учить других, дав им ошибочную информацию.

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

То есть репозиторий — это всё таки домен, раз можно об этом сказать бизнесу? :)

Репозиторий (абстрактный или интерфейс) сущностей — это не application specific logiс в большинстве доменов. Они, эти домены, подразумевают, что есть множество сущностей одного типа и есть способы работать с этим множеством: получать по идентификатору, добавлять в него сущности или удалять их.

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

Если он будет себя вести как "какая-то коллекция", будут проблемы с перформансом. Собственно, на примере репозитория очень удобно изучать феномен текущей абстракции.

UFO just landed and posted this here
Да

"Да" — "да, в домене покупателей есть inbound port, который возвращает данные о покупателе"?


Так вот, теперь вернемся к исходному комменту:


Задача геттера — предоставить информацию, которую потребитель хочет получить.

Геттер — часть inbound port. Потребитель — кто-то, кто хочет получить эту информацию, напрямую (сам адаптер) или косвенно (другой домен, через адаптер).


Так что, собственно, не так?


И нет, в случае, если потребитель — адаптер, ваше утверждение


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

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


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

А разве вообще допустимо юзать в домене порты? Если в домене заказа нам нужны имя и адрес пользователя, то в домене мы реализуем один или два ValueObject, которые либо приходят в доменные сущности откуда-то из application layer параметрами, либо достаются доменом из абстрактного доменного сервиса типа репозитория, за которым в конкретных имплементациях прячутся цепочка "свой порт"-"свой адаптер"-"транспорт"-"чужой адаптер"-"чужой порт"-"чужой домен"

Хех, вообще-то Роберт Мартин в своей Clean Architecture тоже использует Порты и Адаптеры по сути. UseCase используют OutPut Port blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
Ну и судя по его картинке — Use Case это именно Application Service.
Поправьте меня если я в чем-то не прав.
Хех, вообще-то Роберт Мартин в своей Clean Architecture тоже использует Порты и Адаптеры по сути.

Он ее явно упоминает как один из вариантов, который он пытается обобщить.


Ну и судя по его картинке — Use Case это именно Application Service.

Это там явно написано, опять же.

getter как раз несёт задачу развязать внутреннее устройство и внешний контракт сущности. Иногда — лучшую формализацию контракта.

Чем метод обучения других — не самостоятельный метод обучения меня самого?

Даже не знаю что сказать…

Все книги точно про низкую внешнюю связанность

Нет. То есть это конечно тоже важно и про это пишут во многих книгах. Но это не самый главный и первоочердной фактор, который абсолютно важнее всех остальных. И этих самых «остальных факторов» цекая куча. И их все надо учитывать.

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


Ваша статья это какое-то свободное сочинение на тему «как я понимаю геттеры». Одно вот это уже вызывает у меня некий когнитивный диссонанс:
в геттере не может быть никакой логики, тк семантика слова прямолинейна. «Мама, дай мне пирог» не содержит в себе «Мама, купи муку, пожарь пирог и наконеееец дай мне пирог».


Потому что по хорошему в геттере может быть абсолютно что угодно. И если использовать вашу аналогию, то геттер действительно содержит в себе банальное «Мама, дай мне пирог». Но при этом абсолютно всё равно откуда «мама» возьмёт этот самый «пирог». Он может у неё уже лежать готовый, она может его по быстрому испечь, она может пойти и купить этот самый пирог. Вам на это должно быть абсолютно наплевать если вы этот самый пирог в итоге получите. И это и есть та самая инкапсуляция в чистом её виде.

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

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

А если у вас со всем этим всё в порядке, то вам вообще не надо ходить ни по каким «цепочкам». Вы получаете результат от вашего геттера и/или метода и вам абсолютно всё равно откуда этот результат взялся.
А если у вас со всем этим всё в порядке, то вам вообще не надо ходить ни по каким «цепочкам». Вы получаете результат от вашего геттера и/или метода и вам абсолютно всё равно откуда этот результат взялся.

Маме (в качестве повара) все равно, откуда взялась мука getIngridients(), печи абсолютно все равно откуда взялось тесто getТесто, маме-повару из печи нужно получить пирог getПирог, мне все равно, откуда взяла пирог мама getПирог, мама получила getПросьба

и вы мне будете говорить про опыт и навыки?
Маме (в качестве повара) все равно, откуда взялась мука getIngridients(), печи абсолютно все равно откуда взялось тесто getТесто, маме-повару из печи нужно получить пирог getПирог

Неправильно. Это вам всё равно откуда взялась мука. Это вам всё равно какая использовалась печь и использовалась ли она вообще. Это вам всё равно кто и как положил в печь тесто.

мне все равно, откуда взяла пирог мама getПирог

Вот именно вы обратились к «маме» и она дала вам «пирог». Это всё что интересует лично вас.
Маме (в качестве повара) все равно, откуда взялась мука getIngridients()

Нет.


Что вы сказать-то хотели?

Маме (в качестве повара) все равно, откуда взялась мука getIngridients()


Хех, на самом деле все намного веселее будет У Мама, есть навигационное свойство Квартира — это та квартира в которой Мама живет и по сути Агрегат. Точнее у Квартира есть коллекция жильцы один из которых Мама. НУ не суть. У Квартира есть коллекция Полки у полки там коллекция Предметы. Мама сначала находит First полку на которой есть предметы и из этих предметов достает First предмет который является мукой. Ну и дальше со всем остальными ингридиентами.
Маме (в качестве повара) все равно, откуда взялась мука getIngridients()

Цитату вы выбрали не удачную, это было передергивание на данный коммент:
И если использовать вашу аналогию, то геттер действительно содержит в себе банальное «Мама, дай мне пирог». Но при этом абсолютно всё равно откуда «мама» возьмёт этот самый «пирог».

Мама и жильцы не принадлежат квартире, она их не агрегирует, не состоит из них, разве что в каких-то очень специфических доменах.

Все от контекста зависит. Конкретно тут дом Агрегат которому принадлежать полки на которых есть мука. Для простоты так описал. Если по нормальному то есть Агрегат Жилец. Есть Агрегат Дом и их связывает Сущность вроде домовая книга где есть ссылка на дом и на человека который в этом доме живет Для простого примера вполне сгодится и нет смысла так усложнять. Для данного контекста вполне норм что есть Мама->Дом->Полки->Предметы.
И если у вас с этим проблемы, то вам всё равно придётся «ходить по цепочкам» неважно используете вы геттеры или просто методы/функции или ещё что-то.

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

Не очень понятно, что значит "вязать, что есть", но любые геттеры инкапсуляцию не нарушают, они могут делать бесполезным сокрытие в виде private/protected или провоцировать нарушение инкапсуляции (обрабатывать данные сущности вне неё, когда можно было бы в ней без нарушения SRP), но сами по себе инкапсуляцию не нарушают.

Я про другой немного кейс: Один домен, один контекст даже, десятки юзкейсов для какой-то сущности. Если для каждого юзкейса делать свой DTO, как бы заменяющий какую-то комбинацию геттеров, то при изменениях в самих сущностей нужно будет кроме самих юзкейсов менять и DTO.


И с изоляцией вопросы возникают: с одной стороны, логично DTO держать максимально близко к самой сущности, в одном каталоге с нею, например, а, главное в одном слое, чтобы не нарушать его инкапсуляцию. С другой, логически они прямо зависят от юзкейсов, по сути часть юзкейсов: удаляем юзкейс — удаляем DTO. Выход может быть в создании "god DTO" типа как в memento, которая в доменном слое, а в слое юзкейсов из неё создаются DTO для их нужд. Но что-то выгода сомнительна.

Чтобы не писать, например, 15+ is* методов в сущности, где и так логики хватает.

UFO just landed and posted this here

В данном конкретном случае я бы реализовал через два поля: статус и причина выставления статуса, если в целом нет разницы почему заказ отменён и если нет особых требований типа финдира, который сам лазит в базу и хочет именно canceled_by_customer. А вот если хочет, то тогда "геттер" isCanceled, который "группирует" все canceledby… и пригодится.

Мы просто просим объект нам дать что есть, само название метода нам говорит «дай»: но не «сделай», не «не отправь», не «создай», не «посчитай». Говорит дай — даем что есть. Это вполне нормально работает в разного рода дата-объектах, ответственность которых как раз давать/нести информацию.
Дай, но откуда брать — никто не говорит. Геттер может вернуть всё, что заблагорассудится объекту — значение поля, или запись из БД. И инкапсуляция говорит о том, что внешний объект не должно беспокоить, откуда это значение берётся.
Конечно, в реальности не всё так радужно — постоянные обращения к бд или другие побочные эффекты могут быть нежелательны, или невозможны в текущем состоянии программы. Но к ООП это прямого отношения не имеет. Здесь уже приходится «принюхиваться» к коду(code smelling), регламентировать поведение методов договорённостями(которые, впрочем, не могут распространяться на код внешних библиотек), исчерпывающей документацией(которая может устареть) и хорошим покрытием тестами.

P.S. По моему, с современным виденьем архитектуры программ, основанном на ООП, что-то не так. Проблемы которые решаются ООП — ложные. Инкапсуляция никак не поможет там, где нужна система эффектов.

Читаю комменты и грустно становится. Даже не знаю с чего возмущаться начать. Народ, НЕ СУЩЕСТВУЕТ ИДЕАЛЬНОЙ СИСТЕМЫ. Пока вы спорите о каком-либо утверждении в посте, компании по всему миру теряют миллионы долларов как на ошибках, возникающих на том что вы считаете правильным, так и на ошибках, которые тс считает правильным. Наша работа не в том чтобы писать код, следующий какой-то идее типа ООП, а писать код, поведение которого мы можем предсказать. Подозреваю, что половина комментящих даже не пробовала обеспечить покрытие тестами хотя бы процентов 70, иначе бы просто желания спорить не возникало. Очень хорошую мысль сказал ТС — "инкапсуляция обеспеченная не механизмами языка, а стандартами компании". Инкапсуляция, как и остальные штуки — не самоцель, а стандартные методы сделать ваш код более предсказуемым. Следуйте здравому смыслу, а не тому что вам вбивают в институте. Если ваш код отлично работает с геттерами — хорошо. Можете уменьшить связанность, отказаться от всех геттеров, перераспределить логику — тоже хорошо.


З.Ы. Чтоб этот коммент хоть чуть полезным был — лично я просто использую теги прям в документации, поясняющие особенности функции. #cache, #pure, #nonpure, #codedebt, #memoization и т.п. Все, на что стоит обращать внимание при отладке

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

Это повод не спорить о дизайне, что ли?


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

Внезапно, поведение кода, который следует общепринятым идеям, предсказывать проще.

Хоть тут принято ругать подходы Егора Бугаенко, он уже писал в своей книге Elegant Objects о том, что геттеры и сеттеры — это не ООП и зря были добавлены в Java. Книга, в первую очередь, с примерами на Java.


Советую почитать эту книгу, и тогда не только в использовании геттеров/сеттеров появится сомнение, а код станет чище и "модульней" за счет применения большинства рекомендаций

Ждал этого комментария… А автор мог бы на Егора и сослаться (или он независимо от него до этого дошел? или он не хочет будоражить общественность?))
Ссылка на Егора была в прошлом моем посте про сеттеры и геттеры (не от меня, а в комментах), там геттерам не сильно было много выделено и тут появились новые акценты, которые я решил окончательно формализовать в данной статье, чтобы было что приводить, ну например, коллегам.

У Егора тема сеттеров/геттеров тоже не совсем его, ну скажем так, она не из тех тем, которыми «он удивил» общественность.

В Java добавили геттеры и сеттеры? Это типа property в JS или С#?

Хоть тут принято ругать подходы Егора Бугаенко

Аргументация у него уж очень странная, вроде "это неправильно, недостаточно ООП, вот мы немного переименуем, повернём тут боком и будет прям ООП-ООП". Книгу не читал, только блог. После блога не возникло читать собственно книгу.

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


Цитаты неточные, интервью было АйТи Бороде, если не ошибаюсь, но искать конкретные таймкоды не берусь

не вызовут такого резонанса и не привлекут к себе внимания такого же, как «статические классы зло...»
Так ведь зло же.
Но что делать с бизнес-объектами? Зачем сущности «Документ» кому-то «давать» список своих полей?

Если это бизнес-объект, то документ может быть проведен, отклонен, проверен (в том числе по этим полям), заполнен или подтвержден.


Что делать в случае сериализации сущности «Документ»?

Я вижу здесь только два варианта:
  • оставить геттеры, конвертер сам соберёт из них ДТО;
  • использовать паттерн Memento.


Суть сводится к тому, что: как мне получить состояние сущности пригодное для дальнейшей обработки во внешних сервисах?

Использовать рефлексию можно для сериализации, в некоторых языках friendly модификаторы или модули, в некоторых замыкания на объект.


Я бы разделил на три большие группы функции геттеров: показ состояния (его части чаще) в UI, сериализация и подобные функции, использование состояния для принятия решений в других сущностях и сервисах. Вполне нормально для меня их использовать для последнего (принятия решений), очень не нравится для второго (сериализация), под вопросом первое.

Вижу в применении рефлексии конкретную такую утечку инкапсуляции, для этого придётся знать ВСЕ внутренности сущности. Ну и зачем так тогда париться, если геттеры обеспечат тоже самое? Риторический вопрос.

Утечка есть, хотя для задач типа сериализации в "компайл-тайме" как раз и не надо знать. Но она будет для строгой технической задачи, возможно сторонней либой реализована (те же ORM часто вполне успешно работают с приватными свойствами), из любого места кода инкапсуляция не нарушается.

Сущность безгеттеров и сеттеров в crud ведь практически невозможно использовать?

А в crud это и не сущность по сути, а, так, — DTO для работы с базой.

А комично выходит если думать как автор статьи)


Например есть объект с полями: jobName, jobStartTime, jobStopTime,


и есть геттеры для получения всех трёх полей: getJobName, getJobStartTime, getJobStopTime. Ну и они стандартно читают и возвращают соответствующие значения.


и внезапно, в этом объекте был создан метод getTotalTimeOfWork, который делает очевидное return jobEndTime — jobStartTime.


И что у нас получается? getJobName геттер, а getTotalTimeOfWork наверное не геттер, хоть и начинается со слова get. или getTotalTimeOfWork это ПЛОХОЙ геттер, который должен получить по попке. За то, что не просто читает и возвращает значение, а читает, а ещё вычисляет и потом возвращает значение?


Нет ну можно же назвать метод calculateTotalTimeOfWork, правда ведь? Но погодите-ка ребятки, что это у нас тут? Метод не только вычисляет, но и возвращает значение!!! Нет, но слово calculate имеет прямую семантику и не подразумевает возврат вычисленного значения!


Или есть у меня объект UserRepository, а у него метод getUser, который по id, юзера нам "даёт". А в нём может быть и кеш используется и запрос к базе делается и билдер объекта User задействуется.

Мне кажется, есть некоторая путаница в обсуждаемой теме.


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


Также надо различать понятия "сокрытие состояния объекта" и "сокрытие внутренней реализации состояния объекта".
Есть readonly свойство Status, которое возвращает значения из некоторого Enum. Это состояние объекта. Если бизнес-логика это допускает, то оно может быть доступно снаружи и использоваться в других бизнес-процессах.
А внутри оно может быть битовым полем, или целым числом, или строкой, или вычисляться по нескольким полям. Это его реализация. Ее можно менять, не меняя бизнес-логику, и вот к ней давать доступ снаружи нельзя, иначе при изменениях придется менять везде.
А если при изменениях бизнес-логики придется менять везде, это нормально, потому что тогда и в реальных бизнес-процессах логика меняется везде. Код это информационная модель реальной бизнес-логики, большие изменения в источнике порождают большие изменения в модели.

А давайте вот возьмем пример. Есть, значит, очередной e-commerce, в нем в момент того, как заказ переходит в отдел доставки, отправляется уведомление покупателю на емейл. Есть у нас простенький обработчик (потому что событийная модель, но на самом деле не важно):


class OrderReadyToDeliverHandler
{
  async Task HandleEvent(OrderReadyToDeliver ev)
  {
    //...
  }
}

И к нему событие:


class OrderReadyToDeliver 
{
    DateTimeOffset Timestamp {get;}
    Order Order {get;}
}

В нем геттеры можно, он DTO. Это понятно. Непонятно, что дальше делать. Нам надо собрать уведомление вида "Дорогой <имя покупателя>, ваш заказ номер <номер заказа> передан в службу доставки."


Откуда взять фрагменты для подстановки в уведомление?

На правах адвоката дьявола :)
Очевидно же, OrderDTO Order {get;} должно быть. Ну или


class OrderReadyToDeliver extends OrderDTO
{
    DateTimeOffset Timestamp {get;}
}

Ещё вариант


class OrderReadyToDeliver 
{
    DateTimeOffset Timestamp {get;}
    String CustomerName {get;}
    String OrderNumber  {get;}
}
Очевидно же, OrderDTO Order {get;} должно быть

Я же уже спрашивал: а в DTO откуда берутся значения?


Ещё вариант

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

Я же уже спрашивал: а в DTO откуда берутся значения?

Рабочих вариантов с сущностью с приватными свойствами, но без геттеров, навскидку (не во всех языках все доступны):


  • рефлексия
  • замыкания на объект
  • friendly классы
  • штатная сериализация (скорее с использованием механизмов рефлексии, но это не точно) с кастомной десериализацей, игнорирующей "лишнее"
  • метод в сущности типа getStateSnapshot() или getGodDTO
  • полноценная read модель, загружаемая после записи write модели сущности
  • то же на основе event sourcing :)
  • ...

Много вариантов можно придумать, если поставить целью отсутствие геттеров


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

Я по умолчанию предпочитаю в таких случаях


class OrderReadyToDeliver 
{
    DateTimeOffset Timestamp {get;}
    OrderId OrderId {get;}
}

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

полноценная read модель, загружаемая после записи write модели сущности
то же на основе event sourcing :)

именно
Рабочих вариантов с сущностью с приватными свойствами, но без геттеров

Так это ж нарушение инкапсуляции.


полноценная read модель

… содержит геттеры (и при этом не DTO, потому что она полноценная модель).


а там уже дело потребителя получить необходимые лично ему данные

Чтобы получить данные, нужно их прочитать, а у нас геттеров нет. Для целей примера нет разницы, передаем мы Order или OrderId, по которому из OrderRepository получаем Order.

Так это ж нарушение инкапсуляции.

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


Чтобы получить данные, нужно их прочитать, а у нас геттеров нет. Для целей примера нет разницы, передаем мы Order или OrderId, по которому из OrderRepository получаем Order.

Получаем OrderData из OrderDataRepository, а то и вообще из результатов SQL или MongoDB запросов

Я бы это назвал прежде всего преодолением сокрытия, изоляции средствами языка.

"Преодоление" vs "Нарушение". Гм. Не вижу смысла.


Получаем OrderData из OrderDataRepository

… ну то есть полностью минуя домен заказов? С его доменной логикой и прочей прелестью?

Принцип можно нарушить, а защиту преодолеть. Не наоборот. Но это мелочи.


Да, полностью. Прелести отработали, привели состояние сущности в консистентный вид и новое состояние зафиксировано как-то где-то в виде доступном OrderDataRepository, скорее всего с почти полной потерей доменной семантики: строка или json-документ в базе, иммутабельное событие в логе и т. п.

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

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

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

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


Я вот не вижу в наличии геттера нарушения той же инкапсуляции, независимо от того, тупой он или со сложной логикой. Я не делаю предположений о том, что скрывается за getSomething(), если на 100% не уверен, что на проекте однозначній конвеншен, что /get.+/ исключительно возвращает значение свойства $1


Другое дело, что на реальных задачах я не ставлю геттеры по умолчанию для всех приватных свойств сущности, а для задач типа сериализации стараюсь использовать решения не надеющиеся на наличие геттеров.

Это слишком жёсткая интерпретация статьи.

Она подтверждается комментариями. Например: "getter несет одну задачу: связать, скостить путь, сковать сразу несколько доменов между собой и увеличить сложность". Не "потенциально ведет к", а "несет одну задачу".

Что-то вообще непонятно, Гет пирожок, в ответ или пирожок или исключение, нет пирожка, нет дальнейшей работы программы, вроде как для этого геттер и нужен.
Ещё ведь и Find пирожок есть, если его наличие не так важно, есть так есть, нет, так Null или типа того и дальше идем себе.
Вроде как логичная задумка, убрать, не всегда удобно и что взамен тоже не очень понятно.
про DTO и ReadModel я ничего не говорил, тк это их ответственность — давать данные

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

абстракции, в них вся проблема…
  • Тот документ, что в документообороте (со статусами, действием и прочее) — это один документ
  • Тот документ, что на столе/в экране/архиве/отчете/статистике/ — другой документ, и тут именно это и есть DTO в рамках другого процесса (бизнес-процесса или нет)


У меня тоже была такая мысль, но разве на DTO ставится подпись?) Бумажный документ это скорее data storage.

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

в этом процессе пользователь — некоторый верификатор, состояние которого не обязательно в полном виде, достаточно его элемента в виде «печать» или «рука, которая может подписать», крч верификатор как ни крути, потому вязать этот процесс с целой огромной системой «Пользователь» — не самое лучшее решение, достаточно пару атрибутов

Так я не говорил про пользователя, я говорил про геттеры и модель.

вы говорите про пользователя, хотя как по итогу он будет подписывать — вы и не написали… будет ли он принимать документ для подписи?

Да черта лысого, это будет скорее всего делать (в вашей парадигме) некоторый сервис UserService/UserManager — некий чертик, которому вы скормите и пользователя и документ и как куклой марионеткой уже это неведомая штука и подпишет и документ поменяет и все-все тип-топ сделает…
Таких сервисов как у дурака махорки, которые скрепят все ваши модельки с геттерами, на том и порешаете таски свои

А начинали «пользователь подписывает документ»…

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

UFO just landed and posted this here
смешиваете две модели, рид модель и врайт модель

Наоборот. Мы используем одну модель, соответствующую домену. Бизнес не мыслит "моделью на чтение и моделью на запись" (за некоторыми, пусть и существенными, исключениями), для них сущность, которую они читают и меняют — одна.


Ведь так просто написать if (document.getStatus() == ...).

… а поверх рид-модели это написать нельзя?


Ну вот давайте простой пример. У нас есть формочка, в формочке отображается документ. Если документ "проведен" (один статус), его редактировать нельзя, во всех остальных статусах — можно. Какие модели в этом задействованы и как?

UFO just landed and posted this here
Рисуете свою форму через рид модель. Пытаетесь менять используя ее идентификатор

Прекрасно. Я говорю, мне надо сделать так, чтобы форма была редактируема только при одном статусе документа. Вот эта конкретная проверка, можно ли форму редактировать — она в рид-модели или врайт-модели?


Повторюсь, это проверка до сохранения, когда форму отрисовали.

UFO just landed and posted this here
Кто вам запрещает в рид модель добавить булево поле editable?

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


Признак? Вот так, вы имеете в виду?


class MyDocumentReadModel
{
  public bool IsEditable => {my business rule goes here}
}

Вы попробуйте смотреть на проектирование системы не через ui

Понимаете, если смотреть на проектирование не через UI. точнее, если забывать смотреть на проектирование через UI, то можно выяснить, что прекрасная архитектура вообще не работает в UI. Это из личного опыта.

UFO just landed and posted this here

Я одного не понимаю: если в рид-модели выставлено поле со статусом (а оно выставлено, потому что в форме статус надо показать), что мне мешает миновать этот признак и написать if (document.Status != ...) вместо if (document.Editable)? И, наоборот, почему геттер Editable в рид-модели — это ок, а такой же геттер во врайт-модели — не ок, и утекание бизнес-логики?

UFO just landed and posted this here
Потому, что Editable документ может быть не в одном статусе.

И что?


Вот смотрите, есть рид-модель, у нее есть два геттера, один — Editable, к нему обращение ок, второй — Status, к нему обращение для проверки, можно ли редактировать, наверное, не ок. Так?

UFO just landed and posted this here
К статусу ок обращаться, чтобы вывести статус

Да, но я говорю про обращение к статусу для проверки того, можно ли документ редактировать. И оно, если я вас правильно понимаю, не ок. Так?

UFO just landed and posted this here
Да, не ок.

Очень хорошо. Теперь давайте вернемся к оригинальному комменту:


Но нилчие геттеров способствует утеканию логики из врайт модели в сервисы и другие части системы. Ведь так просто написать if (document.getStatus() == ...).

А теперь объясните мне, какая разница, в какой модели геттеры?


Вот у вас есть рид-модель. Используем для нашей задачи геттер Editable — все хорошо, логика не утекла. Используем для нее же геттер Status с внешней проверкой — плохо, логика утекла.


Теперь смотрим на врайт-модель. Используем геттер Status — плохо, логика утекла, согласен. Но это ничем не отличается от того, как если бы мы использовали этот геттер в рид-модели. Используем геттер Editable — плохо? Какая логика куда утекла?

UFO just landed and posted this here
На простых моделях очень трудно уловить преимщества разделения рид и врайт моделей.

А я не спрашиваю о преимуществах разделения рид- и врайт-моделей. Я спрашиваю, почему геттер по статусу в рид-модели не приводит "к утеканию логики в другие части системы". Или, иными словами, почему геттер во врайт-модели хуже, чем геттер в рид-модели, если они экспонируют одно и то же.


Продолжайте пихать аксесоры на все поля в модель

Я, вроде бы, никогда не говорил, что надо пихать аксессоры на все поля. Зачем вы мне это приписываете?

UFO just landed and posted this here
Геттер на статус во врайт модели плохо, потому что статус — внутрення штука ордера.

Я не спрашиваю, почему плохо геттер на статус. Я спрашиваю, почему плохо геттер Editable.


должна быть инкапсулирована в ордере в виде методов is/can

Именно методов? Ну то есть вот так — ок:


public bool IsEditable() => _status != Status.Locked;

А вот так — не ок?


public bool IsEditable => _status != Status.Locked;

Почему? Почему потеря двух скобочек так резко портит архитектуру и нарушает инкапсуляцию?

UFO just landed and posted this here

… а что такое геттер без привязки к языку?

UFO just landed and posted this here
в общем случае — это метод возвращающий некое значение. Чаще всего просто внутреннее состояние объекта, иногда содержащий какую-то логику.

Тогда оба варианта сверху — геттеры. Но вы утверждаете, что как минимум один из них — ок.


Не сходится.

UFO just landed and posted this here
isEditable ок, потому, что он инкапсулирует логику в себе.

Это геттер или нет?

UFO just landed and posted this here
Нет, потому что он не экспоузит внутреннее состояние. Он инкапсулирует в себе бизнес-логику.

Ну то есть вот так — не геттер.


private Status _status;
public IsEditable => _status != Status.Locked;
public void Lock() => _status = Status.Locked;

А вот так — геттер.


private Status _status;
public IsEditable {get; private set;}
private RecalcEditable() => IsEditable = (_status != Status.Locked);
public void Lock()
{
  _status = Status.Locked;
  RecalcEditable();
}
UFO just landed and posted this here

Я пытаюсь показать, что ваш критерий геттера очень странен.


В любом случае, когда я говорю "геттеры — ок", я имею в виду в том числе и IsEditable => _status != Status.Locked. Потому что для меня в моей системе определений это — очевидный геттер.


(и это мы еще не перешли к объектам, которые не являются бизнес-сущностями)

UFO just landed and posted this here
Что из этих утверждений вызывает у вас непонимание?

С момента, как мы установили, что в вашем мире и в моем мире геттером называется разное — больше ничего.

Вся логика вида, «если статус такой-то, то» должна быть инкапсулирована в ордере в виде методов is/can
Это позволяет изолировать бизнес правила в одной сущности и не править потом во всех местах, где мы заюзали геттер на статус.

А это не приводит к созданию год-объектов? Ну вот, есть бизнес-правило "раз в день по всем неоплаченным заказам (статус = "ожидает оплаты") напоминать пользователю о необходимости оплаты в соответствии с выбранным им основным способом связи, если с момента подтверждения заказа прошло 3 банковских дня". Всю эту логику, включая календарь работы банков, запихивать в сущность заказа, чтобы у неё был метод canRemindAboutPayment?

UFO just landed and posted this here

А кто решает? Кто заполняет признак editable на основании статуса в рид-модели?

UFO just landed and posted this here

То есть некий хэндлер берёт райт-модель, вызывает её метод типа approve() и в рид модель записывает дополнительно editable = false?

UFO just landed and posted this here
Ведь так просто написать if (document.getStatus() == ...)

Но ведь обычный директор именно так и принимает решение? В реальности это нормально работает, почему в информационной системе не должно?

Но ведь обычный директор именно так и принимает решение? В реальности это нормально работает, почему в информационной системе не должно?

обычный директор так и принимает,

но никто не будет строить бизнес-процессы вокруг директора, тк он строит (ну или мы) бизнес-процессы, в которых он сам всего лишь верификатор части из них…

Домен для подписания документа не некий директор, а Документ и его flow с переходами между состояниями… выше я описал этот момент

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

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

Но могу предположить, что вы будете делать по традиции с неведомыми UserManager/UserService/UserHelper, UserDocumentService/… как и все «опытные» ребята

«связать и переплести» — кодекс матерого прогера
надо строить исключительно так чтобы они были похожи на примеры из умных книжек

Дружище, будьте постоянным: или отсебятину и сочинение на свободную тему я пишу или умные книжки… Будьте последовательнее

«Умную книжку» привел вам и вашему напарнику исключительно найдя материал в ходе текущей беседы
habr.com/ru/post/500416/#comment_21580920

Так одно другому не мешает. Вполне можно прочитать "умную книжку" понять её неправильно написать потом по ней отсебятину/"сочинение на свободную тему" .


Да и карго-культ как явление никто не отменял.

допустим…

Но вернемся к директору и документу.
Для чего документу геттеры? :) Чтобы директор что-либо прочитал? То есть вы будете давать документ напрямую директору? Можно поинтересоваться как? Через конструктор (а если документов 500/600?) или через метод?
Ну ладно, упростим — документ и директор в ед экземпляре…

Или с высоты «опыта» нагородите сервис, в который попадет и директор и документ и др модели, в котором уже будет не директор читать/подписывать, а будет подписываться документ сам? Посоветуйте, как строить программу!

Так ведь можно и дойти до такой крайности что пользователю будет удобно пользоваться софтом.
:) да уж
Для чего документу геттеры? :)

А для чего документу поля и методы? Какая с точки зрения бизнес-процессов разноца используете вы поле, проперти, геттер, метод или ещё что-то для доступа к каким-то конкретным данным? Какая разница с точки зрения инкапсуляции как концепта?

Вот в чём с этих точек зрения разница между:
public string name;


public string Name {get;set;}


public string getName();
public void getName(string Name);


С другой стороны у полей и/или методов есть определённые «технические» ограничения. По крайней мере в отдельных языках.

Кроме того если придерживаться определённых конвенций, то использование проперти/геттеров делает код намного более читаемым.

То есть вы будете давать документ напрямую директору?

Что значит «документ»? Что значит «напрямую»?

Или с высоты «опыта» нагородите сервис, в который попадет и директор и документ и др модели, в котором уже будет не директор читать/подписывать, а будет подписываться документ сам?

Это вы придумали, не я.

Посоветуйте, как строить программу!

Опишите конкретно вашу ситуацию. То что вам за программа нужна, зачем она нужна и что она должна делать.

Потому что нет в програмировании какой-то «серебрянной пули». Есть куча концептов, подходов, правил, best practice и так далее. И они местами коллидируют, а местами даже противоречат друг-другу. И надо каждый раз смотреть что у вас за конкретная ситуация. И уже иcxодя из неё выбирать концепты/подходы/фрэймворки/архитектуры и так далее.
Не серебряной пули, но есть проблемы довольно типичные, например высочайшая сложность софта с большой кодовой базой (последние 2 места работы — именно такие)

Через геттеры идет связывание доменов/сущностей/разделов, как угодно называйте, просто вызвать геттер — значит ты связал (высокая связанность), при условии, что это разные контексты — это плохо.

какая разница используете вы поле, проперти, геттер, метод или ещё что-то для доступа к каким-то конкретным данным? <...> Какая разница с точки зрения инкапсуляции как концепта?
С моей точки зрения ни какой! Это и пытаюсь донести, нет инкапсуляции никакой! тк будут геттеры или нет — не должны быть разные контексты связаны, ну просто никак не должны знать друг о друге, кроме как ключей… А геттеры для этого, можно сказатЬ созданы — донести детали одного до другого, чтобы тот завязался на них. По итогу и тот не завязывается, а встреча происходит где-то посередине и в сотнях мест.

Да, от этого можно отступить, чтобы «срезать» и побыстрее сделать.
Я же говорю о больших корп приложениях. Один из проектов был настолько большим, что там СТО (ну или кто-то) искусственно порезали ответственность, нарезав команды с их ответственностями… Но и каждый из «модулей» команд был большим и сплетенным с этими и геттерами и сеттерами… Абсолютно никак не контролировались инварианты бизнес-объектов кроме как «ну мы решили, что сеттить в User будет только UserManager»…

Логика платежей: у меня волосы дыбом поднимались, когда вся логика работы с сущностью происходила снаружи от самого объекта, инварианты никак не контролировались, я только успевал писать тесты высокого уровня, чтобы все проконтролировать… Сущность не работала со своим состоянием, класс просто никак его не верифицировал… Решения о том, что делать с объектом принимались снаружи, «благо» у внешнего мира всегда была возможность бесплатно прочитать состояние и этот внешний мир всегда стремился так и сделать…



От написанного мною можно и нужно отступать, но при этом должна быть осознанность, что когда ты сделал 2 (10?) сущности с геттерами, то их использование будет где-то снаружи и потерял инкапсуляцию… Для этого и есть моя статья — обратить внимание. Потому что новички прочитали «так, закон Деметры у Мартина — ну ок», и забыли, особенно когда их приправили анемичными сущностями более «опытные» программисты. 6-10 лет написания банальных сущностей и заплетание паутины кода против 1 года не имеет никаких преимуществ, разве что попутно получите навыки языков, инфрастуктурных вещей…

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

И это тоже может иметь множество разных причин и множество различных вариантов решения.

Через геттеры идет связывание доменов/сущностей/разделов, как угодно называйте, просто вызвать геттер — значит ты связал (высокая связанность), при условии, что это разные контексты — это плохо.

Вы это повторяете как мантру, но я честно говоря не понимаю в чём смысл. Плохо только связывание через геттеры, а связывание через поля-методы это хорошо?
Или плохо просто любое излишнее связывание? Но тогда причём здесь именно геттеры?

С моей точки зрения ни какой! Это и пытаюсь донести, нет инкапсуляции никакой!

Если у вас нет никакой инкапсуляции, то либо у вас проблемы с архитектурой, либо вы просто не совсем понимаете что такое инкапсуляция…

А геттеры для этого, можно сказатЬ созданы

Извините, но это чушь какая-то. Геттеры «для этого созданы» не более чем поля или методы. Ну не будете вы использовать геттеры, значит будете «связывать» полями или методами. И что это вообще за термин «связывать»?

От написанного мною можно и нужно отступать, но при этом должна быть осознанность, что когда ты сделал 2 (10?) сущности с геттерами, то их использование будет где-то снаружи и потерял инкапсуляцию…

А если «ты сделал две сущности с полями», то инкапсуляция будет? А если с методами?
Или плохо просто любое излишнее связывание? Но тогда причём здесь именно геттеры?
Любое излишнее, геттеры здесь потому, что их не замечают и это связывание происходит медленно с удушением… В этом цель статьи
Любое излишнее, геттеры здесь потому, что их не замечают и это связывание происходит медленно с удушением… В этом цель статьи

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

Ну и если даже, то какую альтернативу геттерам вы предложите для каких-нибудь ORM? Для автоматической сериализации/десериализации в какой-нибудь JSON/XML? Для автомэппинга? Или скажем как предлагаете делать банальный байндинг в UI-фреймворках?
какую альтернативу геттерам вы предложите для каких-нибудь ORM? Для автоматической сериализации/десериализации в какой-нибудь JSON/XML?
Тут от стека, я в PHP работаю, у нас Doctrine ORM работает как Hibernate — через Reflection API + Proxy Manager с кодогенерацией… Ну в Java аналогично, а вот для C# не подскажу
Доктрину обычно используют с геттерами/сеттерами, чем вызывает недовольство ее автора

Данная статья от Microsoft рассказывает о деталях такой работы в .NET Core, но не подскажу глубже
docs.microsoft.com/ru-ru/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/net-core-microservice-domain-model#encapsulate-data-in-the-domain-entities

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

UPD: Не на ту главу сослался docs.microsoft.com/ru-ru/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/net-core-microservice-domain-model#map-fields-without-properties
Тут от стека, я в PHP работаю...

То есть зло у нас геттеры или не зло уже и от стека зависит? :)

у нас Doctrine ORM работает как Hibernate — через Reflection API + Proxy Manager с кодогенерацией…

И вообще без использоваия проперти/геттеров/сеттеров? Исключительно на полях и методах?

Данная статья от Microsoft рассказывает...

Я там что-то не вижу ответа конкретно на мои вопросы. Там вообще речь идёт о микросервисах, а разработка софта микросервисам ну никак не ограничивается…
То есть зло у нас геттеры или не зло уже и от стека зависит? :)
зло конечно

И вообще без использоваия проперти/геттеров/сеттеров? Исключительно на полях и методах?
только поля, приватные…

Я там что-то не вижу ответа конкретно на мои вопросы.
дополнил др ссылкой, не на ту главу сослася, вот корректная: docs.microsoft.com/ru-ru/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/net-core-microservice-domain-model#map-fields-without-properties

Там вообще речь идёт о микросервисах, а разработка софта микросервисам ну никак не ограничивается
они отдельную тему (обсуждаемую нами) втянули в обзор микросервисов в .NET Core, ну типа учить сразу на хорошем..., но тема самостоятельная
зло конечно

Ну тогда я всё ещё жду ответа на мои вопросы.

только поля, приватные…

То есть геттеры/сеттеры это нарушение инкапсуляции, а когда какие-то внешние фреймворки/классы/методы при помощи рефлексии имеют доступ к вашим приватным полям, то это не нарушение инкапсуляции? Извините, а что тогда по вашему инкапсуляция?

дополнил др ссылкой, не на ту главу сослася, вот корректная:

И там всё ещё речь идёт о микросервисах и даже близко нет ответа на мои вопросы.

они отдельную тему (обсуждаемую нами) втянули в обзор микросервисов в .NET Core, ну типа учить сразу на хорошем.

Что значит «на хорошем»? Микросервисы вдруг стали серебрянной пулей? Или они всё-таки имеют ограниченную область применения?

они отдельную тему (обсуждаемую нами) втянули в обзор микросервисов в .NET Core, ну типа учить сразу на хорошем


То есть геттеры/сеттеры это нарушение инкапсуляции, а кога какие-то внешние фреймворки/классы/методы при помощи рефлексии имеют доступ к вашим приватным полям, то это не нарушение инкапсуляции? Извините, а что тогда по вашему инкапсуляция?

Да, дата-мапперы гидрируют данные в бизнес-объекты, но из приложения мы не может эти данные читать/менять извне сущности… (рефлексия и прочие штуки не в счет)

Инкапсуляция логики, все так…

Что значит «на хорошем»?
Что рекомендуют ограничивать доменные агрегаты… То что попутно они рассказывают как на дотнете делать свои приложения — оставим тем, кто ими интересуется… меня касается тема изоляции

Да, дата-мапперы гидрируют данные в бизнес-объекты, но из приложения мы не может эти данные читать/менять извне сущности… (рефлексия и прочие штуки не в счет)

Что значит «не в счёт»? Кто это решил? Вы лично? Как ни крути, но доступ к приватным полям/методам извне это однозначное нарушение этой самой инкапсуляции.

микросервисы — тоже своего рода решение проблемы высокой связанности

Угу. Вот только делается это в случае микросервисов за счёт создания кучи редундантного кода. Иногда это оправданно. Но далеко не всегда.
это нарушение, но происходит в сильно ограниченном случае и из приложения/бизнес-логики руками программиста это становится не возможным (если не брать в расчет рефлексию)… вы тут можете меня к стене сколь угодно прижимать, тут я уже нчиего не сделаю…

я высказался по поводу открытости объекта для приложения и программиста

тут уже все претензии к авторам ORM и фреймворков, зачем они сняли определенную долю риска писать плохие программы с программистов
это нарушение, но происходит в сильно ограниченном случае и из приложения/бизнес-логики руками программиста это становится не возможным (если не брать в расчет рефлексию)…

Что значит «невозможным руками программиста»? Если вы можете «руками программиста» мэппить любые приватные поля, то вы как минимум можете «руками программиста» читать содержание любых приватных полей.

Да и вообще если следовать вашей логике, то надо просто из всех языков программирования убрать public/protected access modifiers и всё сделать по умолчанию private.

тут уже все претензии к авторам ORM

А почему не к тем кто этими ORM пользуется? Или к тем кто использует геттеры у вас претензий нет и они у вас только к создателям? :)

Я отказываюсь брать за это ответственность… тысячи материалов про инкапсуляцию в языках: C#, PHP, Java, Kotlin пишут про инкапсуляцию… и в них есть доступ через рефлексию, потому за всех сразу вы меня тут не прижмете, увольте

Ну вообще-то рефлекcию можно использовать только на паблик поля/проперти/методы и тогда это инкапсуляцию не нарушает.

Кроме того в этих языках точно так же «пишут про геттеры/сеттеры» и тогда по вашей логике геттеры/сеттеры тоже не должны нарушать инкапсуляцию. Ведь про них пишут же :)
Как ни крути, но доступ к приватным полям/методам извне это однозначное нарушение этой самой инкапсуляции.
Я отказываюсь брать за это ответственность… тысячи материалов про инкапсуляцию в языках: C#, PHP, Java, Kotlin пишут про инкапсуляцию… и в них есть доступ через рефлексию, потому за всех сразу вы меня тут не прижмете, увольте
они отдельную тему (обсуждаемую нами) втянули в обзор микросервисов в .NET Core, ну типа учить сразу на хорошем...

Нет, это снова ваш домысел. У MS есть (и было) больше одной книжки про архитектуру, и далеко не все они про микросервисы.

не мои домыслы, работа с DDD не относится к микросервисам

почему на ее примере написали доку по микросервисам — не говорит о том, что DDD для микросервисов и просто использована (видимо со своей целью)

Это не домыслы, банальнейшая логика… Хватит заниматься словозадевательствами, вырывая все из контекст и недопонимая все…
не мои домыслы, работа с DDD не относится к микросервисам

Не относится… и?


почему на ее примере написали доку по микросервисам

А с чего вы взяли, что "на ее примере"? Написали доку по микросервисам, внутри применяя ту архитектуру, которую хочется.


не говорит о том, что DDD для микросервисов и просто использована

… правильно, ни о чем не говорит.


Это не домыслы, банальнейшая логика…

Ну так домысел — это когда вы что-то домысливаете. Вы вполне можете при этом пользоваться логикой, только это не отменяет того, что это ваш домысел. И если кто-то, пользуясь своей логикой, домыслит другое, нет никакого способа доказать, что ваш домысел правильнее.

Я вот, знаете, специально пошел и прочитал начало раздела и главу про DDD. И я там что-то не нашел объяснения, почему выбран DDD.


PS Зато есть смешное (выделение мое):


In addition, DDD approaches should be applied only if you are implementing complex microservices with significant business rules.
In addition, DDD approaches should be applied only if you are implementing complex microservices with significant business rules. Simpler responsibilities, like a CRUD service, can be managed with simpler approaches.


а вообще разговор произошел таким макаром (упрощенная версия и как ее вижу я, со своими домыслами и ощущениями):

действующие лица: ЧД1 и ЧД2 (человек докапывающийся 1 и 2 соответственно (вы второй))

ЧД1: какую альтернативу геттерам вы предложите для каких-нибудь ORM?
Я: Ну есть такие-то и такие-то в PHP решения, вот например ссылка как в .NET Core решают
ЧД1: Но ссылка дока по микросервисам
Я: И что? Архитектура для ДДД не привязана к микросервисам, по ссылке решение ORM при отсутствии геттеров
ЧД2: Это ваши домыслы, у MS есть куча архитектур
Я: И что? Архитектура для ДДД не привязана к миркосервиса
ЧД2: И? Домыслы, ваши домыслы, нашел цитату, что ДДД в конкретной документации к микросервисам советуют использовать только к сложным микросервисам
ЧД1: Вообще-то и рефлексия нарушает инкапсуляцию, значит и вы должны нарушать или не признавать рефлексию!
… занавес

Я бы тут сказал третий раз, что архитектура для ДДД не привязана к миркосервиса, да без толку, раз 2 человека (вы и Kanut )задались целью докопаться, зачем мне, взрослому человеку, что-либо доказывать

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



По итогу: Kanut и lair,
статья по-прежнему корректно выражает мои мысли, геттеры для доменных сущностей/агрегаторов ассоциируются с высоким риском проблем и нарушением инкапсуляции by design.
Для дата-объектов геттеры приветствуются (в основной статье можете увидеть это) как выражение их ответственности.

Далее доводы (если будет желание) будут комментироваться только по теме.
Я: И что? Архитектура для ДДД не привязана к микросервисам, по ссылке решение ORM при отсутствии
ЧД2: Это ваши домыслы, у MS есть куча архитектур

Упс, нет. Я отвечал на "сразу на хорошем...", а не на то, что вы цитируете.


геттеры для доменных сущностей/агрегаторов ассоциируются с высоким риском проблем и нарушением инкапсуляции by design.

Ну да, вы так говорили и продолжаете говорить. Но что делать с контраргументами? (начиная хотя бы с десять раз обсосанного IsEditable) Или хотя бы с аргументами в пользу вашей позиции кроме "ну это же нарушает инкапсуляцию by design"?


Потому что нет, by design геттеры нарушают инкапсуляцию не больше любого другого публичного метода.

IsEditable со мной не обсуждалось

Есть бизнес-сущность, ну, скажем, заказ. Ее можно менять только в определенные этапы бизнес-процесса. Понятное дело, что внутри кода, который отвечает за изменение сущности, будет проверка на это условие. Но мы хотим предоставить пользователю хороший UX, поэтому для заказов, которые редактировать нельзя, должен быть другой интерфейс (для простоты — просто все задизейблено, кнопки Save нет).


Как это сделать?


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


(только пожалуйста, если вы собираетесь ответить "UI работает через view model", сразу напишите, откуда во view model берутся данные)

ReadModel с простым необходимым для UI интерфейсом:
— isEditable()
— еще что-нужно для отображения заказа

Что значит откуда — из хранилища, в самом из простых вариантов имплементации подхода разделения read и write (CQRS) это может быть тоже самое хранилище, просто без ORM или используя ее возможности генерировать простые дата-объекты

Ну вот как пример (с таймингом): youtu.be/uyfeERk3Ta4?t=754

Более сложная организация — вне нашей темы обсуждения

Подождите, а без CQRS — никак?

а без CQRS — никак?

Термин — формальный способ отобразить некоторую совокупность вещей, некоторые знания.…

Формально, вы должны были понять термин как — разделить чтение (в простые readmodel) от записи (write), а не цепляться к самому наличию термина…

Разделение на read и write — это основа CQRS. Так что я все понял правильно. И вопрос мой именно про то, а можно ли решить задачу без разделения чтения и записи?

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

точнее можно, конечно, но чревато проблемами, описанными в посте
конечно нет

Я еще раз переспрошу. Я правильно вас понял, что вы считаете, что нельзя построить бизнес-приложение с нормальными бизнес-сущностями, не вводя в нем разделение на чтение и запись?


иначе у бизнес-сущностей остается лишняя ответственность

А как вы определяете "лишние" ответственности, по какому правилу?

Держать логику UI в бизнес-слое — считаю лишней ответственностью.

Я нигде не предлагал держать логику UI в бизнес-слое. Речь не о логике UI, а о разделении на чтение и запись. Вы на основании чего-то утверждаете, что ответственности, которые несет read-модель, "лишние" для бизнес-сущности.

Мы бы тут не написали столько комментариев, если бы в статье было бы объективное обоснование.


Процитируйте его, пожалуйста.

Тем временем, Эванс в понятно-какой-книге, описывая дизайн доменных сущностей, ничего не говорит о том, что у сущности не должно быть доступных снаружи атрибутов. Более того, на всех диаграммах эти атрибуты есть (глава 5, "A Model Expressed in Software").


В главе 6, "The Life Cycle of a Domain Object", рассуждая об агрегатах, он пишет:


An object outside the AGGREGATE boundary may reference the root, Car, or query the database for it by ID.

(это к разговору о том, что там MS пишет в своих книжках)


Там же:


Transient references to internal members can be passed out for use

(обратите внимание, не DTO, а ссылки на внутренности агрегата)


… что-то как-то фундаментальная книга по DDD не подтверждает ваши громкие утверждения про геттеры и нарушение инкапсуляции.


Кстати, про разделение обязанностей чтение-запись там тоже нет.

UFO just landed and posted this here
UFO just landed and posted this here

Да я понимаю. И?


Когда я отдаю ссылку на объект в памяти (в смысле, объект ЯП, а не бизнес-объект), которому жить осталось 100мс, что уж более transient?

Так не пускайте её гулять. Оборачивайте в DTO на границе хотя бы application service слоя

Хорошо, предположим, я с вами согласился и сделал read-модель с необходимым интерфейсом. Которую прочитал из хранилища, в котором никакого IsEditable нет, и мне понадобилось повторить логику со статусом уже в двух местах: один раз во write-модели, второй раз — в read-модели. Ладно.


Почему read-модель, в которой есть 4 публично доступных свойства, отображающих состояние документа, не нарушает инкапсуляцию этого документа как бизнес-сущности?


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

UFO just landed and posted this here

Это, знаете ли, не решает фундаментального вопроса про инкапсуляцию.


(честное слово, я мог бы тут позадавать других дополнительных вопросов, но на фоне этого — просто не хочу)

Я бы тут сказал третий раз, что архитектура для ДДД

Ну так и привeдите какой-то источник в котором это всё и написано для DDD в целом, полностью и без исключений. Без всяких микросервисов и прочей посторонней чепухи.

То есть грубо говоря где чёрным по белому написано что геттеры в DDD зло и их в DDD использовать не надо.

геттеры для доменных сущностей/агрегаторов ассоциируются с высоким риском проблем и нарушением инкапсуляции by design.

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

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


это ваши личные проблемы, также ваши доводы тоже лично ваши доводы и отныне не принимаются по вашей же логике
я привел вам источники не личные

Это какие, простите? В приведенном вами руководстве от MS нет ни слова про то, что геттеры не надо использовать.

Вопрос: какую альтернативу геттерам вы предложите для каких-нибудь ORM?
Ответ: ссылка на источник, как EF Core (начиная с версии 1.1) работает без них

Левый тип:
нет ни слова про то, что геттеры не надо использовать.

Ииии?

"Ииии" следите за комментами, на которые отвечаете:


Kanut:


Ну так и привeдите какой-то источник в котором это всё и написано для DDD в целом, полностью и без исключений. [...] То есть грубо говоря где чёрным по белому написано что геттеры в DDD зло и их в DDD использовать не надо.

Вы:


я привел вам источники не личные

Я в этом месте ожидаю, что вы привели источники, в которых написано то, что у вас спрашивают, а именно:


геттеры в DDD зло и их в DDD использовать не надо
То что что-то там работает без геттеров не доказывает что геттеры в принципе нельзя использовать.

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


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

И зачем тогда кому-то вместо способа с возможным нарушением инкапсуляции использовать способ с однозначным нарушением?
И ваша альтернатива это рефлекия с доступом к приватным полям. Что является однозначным нарушением этой самой инкапсуляции.
Тема построения инфраструктурных библиотек/фреймворков, использовании магии и способов создания закрытых объектов на уровне ORM лежит вне контекста обсуждения нарушения инкапсуляции на уровне приложений и бизнесухи
С чего вы это вдруг так решили? Если есть доступ к приватным полям, то инкапсуляция нарушена.

А он у вас есть и в бизнесухе" тоже.

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

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

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

Вы вообще о чём? Какой «баттл»? Какое «поймал»? Вы просто до сих пор не ответили на поставленный вопрос.

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

Но вы то утверждаете что они абсолютное зло и что их вообще никогда нельзя использовать. И вот доказательства этого я бы и хотел увидеть.
Но вы то утверждаете что они абсолютное зло и что их вообще никогда нельзя использовать

Это ваши домыслы, в статье основной написано, когда использовать, когда нет…
А можно тогда конкретную цитату в которой описано когда вы «разрешаете» их использовать. А то что-то я не найду. Это во первых.

А во вторых вы кроме статьи ещё и комментарии успели понаписать.
Это ваши домыслы

Неа, не домыслы. Вот ваши слова (и они не единственные):


getter несет одну задачу: связать, скостить путь, сковать сразу несколько доменов между собой и увеличить сложность
я привел вам источники не личные, но они не попадают в ваши личные критерии

Конечно не попадают. Например потому что архитектура микросервисов имеет свои особенности. И потому что это микросервисы являются подмножеством DDD, а не наоборот.

это ваши личные проблемы, также ваши доводы тоже лично ваши доводы и отныне не принимаются по вашей же логике

Ну так вы статью для кого писали? Только для себя? Просто чтобы выговориться и всё? Или вы всё таки хотели донести до других людей какую-то идею?

Вы своей статьёй выдвинули определённый тезис и по хорошему вы этот тезис должны доказать. И какие-то там ваши личные ассоциации ничего не доказывают.
И потому что это микросервисы являются подмножеством DDD

Ну все-таки нет. Микросервисы и DDD — это типичная диаграмма Венна, у них есть область пересечения. У меня в соседнем окне развесистое дерево этих самых микросервисов без единого намека на DDD.

микросервисы — тоже своего рода решение проблемы высокой связанности

Именно что "своего рода".

Тут от стека, я в PHP работаю...
То есть <...>и от стека зависит? :)


Это я отвечал на вопрос:
Ну и если даже, то какую альтернативу геттерам вы предложите для каких-нибудь ORM?

Чтобы синхронизировать знания наши, все же вы в другом стеке
С моей точки зрения ни какой!

Если нет никакой, то почему вы пишете, что не нужны геттеры, а методы не трогаете?


разные контексты связаны

Если контексты не будут связаны, между ними не будет взаимодействия. А если между ними не будет взаимодействия, то (предположительно) система не будет выполнять свою функцию.


А геттеры для этого, можно сказатЬ созданы — донести детали одного до другого, чтобы тот завязался на них

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


Решения о том, что делать с объектом принимались снаружи, «благо» у внешнего мира всегда была возможность бесплатно прочитать состояние и этот внешний мир всегда стремился так и сделать…

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

сеттеры разибирались в отдельной моей стать, там сопротивления было меньше и пришло мне на помощь больше квалифицированных людей…
тут в статье специально обратил внимание именно на геттеры

Нет, геттеры созданы не для этого. Если виденные вами применения геттеров этим ограничивались
В статье есть метафора про капрала — я не глобально против них, а против их использование у разных доменных контекстов сложного приложения

С моей точки зрения ни какой!

Если нет никакой, то почему вы пишете, что не нужны геттеры, а методы не трогаете?

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

Крч мы по кругу пошли
тут в статье специально обратил внимание именно на геттеры

… у которых пока что специфических проблем не обнаружено.


Потому что геттеры нарушают инкапсуляцию, не по определению, а по смыслу.

Нет, не нарушают. Инкапсуляцию нарушает неуместное применение геттеров, как и любого другого инструмента.

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

Для заказа например может требоваться id пользователя только,
Для доставки от товара может требоваться от заказа id, название, цена
Для платежа может требоваться только сумма заказа, и т.д…

Завязываться полностью платежу на заказ ради вызова одного поля, у заказа на пользователя — это связать систему, кроме того в промежуточных слоях доступны знания, которые провоцируют строить логику на этих знаний, сковывая всю систему…

Ну в понимании доменной сущности, не ее ответственность это

В вашем понимании доменной сущности.


Завязываться полностью платежу на заказ ради вызова одного поля, у заказа на пользователя — это связать систему, кроме того в промежуточных слоях доступны знания, которые провоцируют строить логику на этих знаний, сковывая всю систему…

Ну не завязывайте. Завяжете на segregated interface. В нем все равно будет геттер.

Через геттеры идет связывание доменов/сущностей/разделов, как угодно называйте, просто вызвать геттер — значит ты связал (высокая связанность), при условии, что это разные контексты — это плохо.

Есть вещи, которые должны быть тесно связаны получения простой в поддержке архитектуры, кодовой базы, но не та связь, которая coupling, а та, которая cohesion


тк будут геттеры или нет — не должны быть разные контексты связаны, ну просто никак не должны знать друг о друге, кроме как ключей…

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

habr.com/ru/post/500416/#comment_21580920

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

Угу, вот в одном ли только?

Ну, если уж заходить ТАК далеко, то и нужно реализовывать именно «flow с переходами между состояниями», а для этого давно придуманы движки бизнес-правил и нотация для описания процесса. Так что какие такие геттеры? Берем в руки camunda и начинаем рисовать bpmn ;)

Но даже там геттеры нужны для проверки условий

Условия можно вынести в external скрипты, и написать их на js, где несмотря на все недостатки будет одно большое преимущество — вопрос геттеров станет наименьшей из проблем!
Даже если предположить, что модель = директор, то получается так:

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

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

Правильно, все это не будет в нем, а будет:
  • либо документ со статусом «readyToVerify», и по сути уже ДТО конкретного документа, а на выходе будет список подписанных документов у директора… странная логика
  • либо будет (как это везде и делают) не директор, а ДиректорСервис это делать, который от директора только полномочия получит и получит от документа реквизиты некоторые и всю работу сделает… по итогу сшив 2-3-10 моделей (нужен же и документ и целый директор зачем-то и контракт и печать)… и директор уже не будет ничего читать, тк это делает что-то неведомое :):):)


все выше было из предположения, что модель Директор, в реальности же будет модель Документ и все хорошо работать, как и описал немного выше
Даже если предположить, что модель = директор, то получается так:

Стоп-стоп. Какая модель = директор? Модель это вся информационная система целиком. Ну или та часть, которая относится к бизнес-логике, а не инфраструктуре, если хотите.
Есть реальные процессы, есть информационная модель в виде кода и данных, которая эти процессы моделирует.


Да, в жизни он смотрит, но что будет в программе? Не слишком ли много внутри геттеров от разных моделей у этого директора?

Неважно, много их или мало. Их должно быть ровно столько же, сколько в реальной бизнес-логике.


но никто не будет строить бизнес-процессы вокруг директора, тк он строит (ну или мы) бизнес-процессы, в которых он сам всего лишь верификатор части из них…

Ну и что? Геттер-то все равно есть. И есть логика вида "if (document.getStatus() == ...)", которую нам надо смоделировать.


но никто не будет строить бизнес-процессы вокруг директора, тк он строит (ну или мы) бизнес-процессы, в которых он сам всего лишь верификатор части из них…
Но могу предположить, что вы будете делать по традиции с неведомыми UserManager/UserService/UserHelper, UserDocumentService

Ну так вы же сами это и сделали — выделили некий неведомый DocumentVerificator. Может не такой уж он и неведомый?)

Нет, это когда мы удаляем из документа геттеры, мы искусственно разбиваем модель на две :) И да, бизнес-процессы так и работают: какой-то исполнитель смотрит на документ, и если у него статус "подписан непосредственным начальником", то он ставит на нём свою подпись "принял, отгрузил, ...", а если статус "черновик (ни одной подписи)", то отправляет его в корзину.


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

Articles