Зачем (не)нужны геттеры?

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

    image
    Изображение из блога Фаулера: TellDontAsk


    getHumanFactory().getHuman().getDoneWork().getWorkTime()


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

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

    Само значение переменной класса может быть получено как угодно. Мы просто просим объект нам дать что есть, само название метода нам говорит «дай»: но не «сделай», не «отправь», не «создай», не «посчитай». Говорит дай — даем что есть. Это вполне нормально работает в разного рода дата-объектах, ответственность которых как раз давать/нести информацию.

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

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

    getValue() throw WhyDoYouNeedIt? {}


    Назначение дата-объекта понятно — это капрал из фильма 1917, который бежит куда-то, чтобы донести некоторое послание о том, что нужно отступать.

    Но что делать с бизнес-объектами? Зачем сущности «Документ» кому-то «давать» список своих полей?

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

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

    doEverythingEverywhere(world.getGod())


    Вернемся к бизнес-сущностям. Если оставить геттеры в сущностях, то велик соблазн использовать ее геттеры ровно везде и как угодно. Мы завязываемся на состояние некого объекта и воротим абсолютно любую логику походу. Может даже казаться, что мы не нарушили инкапсуляцию. Но как же? Состояние в одном месте, поведение в другом — классическое нарушение инкапсуляции.

    Например есть некоторая сущность:

    class Order
    {
         private Status status
         private Boolean closed
    
         public function getStatus(): Status {
              return this.status
         }
    
         public function setClosed(Boolean closed): void {
               this.closed = closed
         }
    }

    Наверняка, при такой архитектуре статус вы запросите в добром десятке/сотне мест. Это могут быть разного рода сервисы, контроллеры, другие модули. Я видел только один раз, когда искусственно были созданы ограничения для распространения этого кода некоторым набором правил для разработчиков, не кодом… Инкапсуляция была обеспечена стандартами кодирования, а не проектированием кода :).

    Если вам понадобилось сделать где-то что-то подобное:

    if(order.getStatus() == input.getStatus()) {
          order.setClosed(true)
    }

    То скорее всего сущность не содержит в себе машину состояний. Значит инварианты объекта изнутри никак не контролируются — нет верификации данных при каждой операции изнутри. Как следствие — высокая связанность, сложное функциональное тестирование, тк недостаточно проверить юнит-логику того кода, который меняет состояние сущности, нужно проверить, что состояние внешней для кода сущности допустимое. И как следствие: увеличенная сложность, вероятность багов, больше кода и сложные тесты.

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

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

    Почитать:
    М. Фаулер, TellDontAsk
    М. Фаулер, AnemicDomainModel

    Всем добра!
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 367

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

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


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

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

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

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


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

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

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

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

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

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


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

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


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

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

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

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

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

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

                +2

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

              0

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


              Например:
              Queue.getSize(), при этом сама очередь хранится не в объекте, а где-то еще, например в MySql. А MySQL лучше не дергать много раз в секунду для такой операции, поэтому в геттере вполне можно добавить логику следующего рода (на мобильной версии нет форматирования):


              Queue.getSize() {
                  static lastaccess = time();
                  if (abs(time() - lastaccess) > this.DB.allowedFrequency) {
                      lastaccess = time();
                      this.queueSize = this.DB.requestSize();
                  }
                  return this.queueSize;
              }
                0
                this.queueSize = this.DB.requestSize();

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

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

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

                    –5
                    а ну да, как в Kotlin
                    язык не знаю…

                    Спасибо!

                    Просто queue.Length?
                    А-та-та
                      +2
                      А-та-та

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

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


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

                        +3
                        А-та-та

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

                      0
                      То есть проблема в слове get?..
                        0

                        … а без него — А-та-та.

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

                        Сегодня это MySQL, завтра файл. Меняя поведение внутри одного класса не затрагиваем поведение вне его.

                        Зачем нужно получать getSize()? Чтобы узнать пустая ли она или чтобы узнать не переполнилась ли она? Такие срезы состояний вполне укладываются в поведение например isEmpty().
                        Пример выдуманный, просто показывает ситуацию, когда класс может иcпользовать property, но не являться его владельцем. Из жизни могу привести аналог с классом-оберткой для работы с устройством. Была реальная жизненная ситуация с термодатом, который нельзя было часто опрашивать на предмет температуры и там в геттере был подобный алгоритм.
                          0
                          Зачем нужно получать getSize()?

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

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

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

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

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

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


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

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

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


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

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

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

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


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

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

                                            +3

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

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

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

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

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

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

                                        0

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

                                      0

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


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

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

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

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

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

                                        –3

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

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

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


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

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

                                            0

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

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

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

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

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

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

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


                                                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;
                                                     }
                                                  0
                                                  Мой изначальный комментарий был про:
                                                  public function setName(value: String): void {
                                                  this.name = value;
                                                  }

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

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

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

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

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


                                                      public string Name;

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


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

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

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

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

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

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

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

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

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


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

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


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


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

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

                                                                  –1

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


                                                                  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)
                                                                  {
                                                                  ...
                                                                  }
                                                                    +2
                                                                    Вы не находите такой код странным?

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


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

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


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


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

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

                                                                      –4

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

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

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

                                                                          0

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


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

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

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


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

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


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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

                                                                        0

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

                                                                          0

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

                                                                            0

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

                                                                              0

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


                                                                              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/

                                                                                0

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

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

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

                                                                                    +2

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

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

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


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

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


                                                                                      Reads and writes cannot be introduced.

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

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

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

                                                        +1

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

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

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

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

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

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

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

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

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

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

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

                                                                          Да и вообще может у вас этот самый статус это enum-флаг. Или вам его надо где-то протоколировать или показывать пользователю. Или вам надо где-то показывать количество заказов с определённым статусом. Или…
                                                              +1
                                                              Вместо getStatus намного лучше использовать can*

                                                              canBeClosed(): bool
                                                              canBeApproved(): bool

                                                              и т.д

                                                              Тогда при изменении бизнес-правил вы поменяете код только в одном месте, вместо допиливания новых условий в 100500 мест использования getStatus()
                                                                0

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

                                                                  0
                                                                  Если у нас появляется правило вида «не больше N заказов в статусе Х», то у нас уже появляется новый корень агрегации OrderList с методом canAddOrder.
                                                                  Или, если вы отдаете предпочтение сервисам, то некий decision maker, который и будет использован. И вот этот decision maker по сути инкапсулирует в себе canAddOrder. Ну и как он это сделает — отдельный разговор, но итерирование через массив объектов с проверкой статусов явно не лучший вариант.
                                                                    0

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

                                                                      0
                                                                      Нет, зачем?
                                                                      1. Мы всегда можем описать это бизнес-правило в виде критерии для хранилища (да, вероятно мы захардкодим какие-то статусы в критерии, но это все равно лучше, чем getStatus по всему проекту)

                                                                      2. getStatus() === 'some status' равносильно can* / is* методу. Так зачем вам getStatus?
                                                                        +1
                                                                        getStatus() === 'some status' равносильно can* / is* методу. Так зачем вам getStatus?

                                                                        Ну например потому что не всегда нужно только такое сравнение и ничего больше. Например сам статус может быть enum-flag'ом или даже сам иметь какие-то проперти и/или методы.
                                                                        Или скажем вам будет нужна сортировка/группировка/фильтрация по статусу.
                                                                          0
                                                                          Если вы завернете «только такое» сравнение в метод, то вы всегда с легкостью его расширите, с другой стороны, расширить это сравнение по всему проекту намного более сложная задача, часто сопряженная с провтыками.

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

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

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

                                                                              0
                                                                              Зачем его отдавать? Чем проще контракт, тем легче его саппортить и рефакторить.
                                                                                0

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

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


                                                                                  Плюсанул бы, если б карма позволила.
                                                                                0
                                                                                Если вы завернете «только такое» сравнение в метод, то вы всегда с легкостью его расширите

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

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

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

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

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

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

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

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

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

                                                                                  Возможно в каких-то специфических случаях. Но явно не стоит городить набор гетеров на все поля при создании сущности.

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

                                                                                      0
                                                                                      И что в этом плохого? Желание сэкономить на похожих классиках и приводит в итоге к распуханию модели.
                                                                                        +1
                                                                                        А распухание совсем не обязательно кстати. Да и вообще если вам что-то нужно для фильтрации/сортировки/группировки, то это опять же совсем не означает что это будет нужно и для показа в UI. То есть получается вы с таким подходом в некоторых ситуациях просто пишите абсолютно лишний код.

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

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

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

                                                                                            0

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

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


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

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

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

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

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


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

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

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

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

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


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


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

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


                                                                                                        из своего хранилища
                                                                                                          0

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

                                                                                                            0
                                                                                                            Да, а почему б и нет? Иногда это очень даже оправдано
                                                                                                              +1
                                                                                                              Да, а почему б и нет?

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


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

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

                                                                                                                Но это может быть и просто другой лоадер данных из БД с маппингом на ДТО, а не на бизнес сущности.
                                                                                                                  +1
                                                                                                                  Хранилище для ДТО апдейтится только по ивенту.

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


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

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

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

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

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


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

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


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


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


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

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


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

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

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

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

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


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

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

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


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

                                                                                                                            Теперь касательно апдейта коммента в заказе. Да, если весь ваш апдейт сводится к присваиванию строки, то смысла особо нет что-то придумывать. Смысл появляется, когда есть хоть-какие-то бизнес правила. Например, что коммент не может быть изменен, если заказ в финальном статусе. Вот тут и вылазят всякие isEditable, в которых инкапсулирована бизнес-логика. И находится он в доменной модели. В рид модели ее нет, в рид модели есть только необходимые для отображения данные, без каких-либо проверок.

                                                                                                                            Тот же пример с IsUrgent. Да никто не помешает вам забить на разделение отвественности и впилить проверку в рид модель, кроме здравого смысла. Я уже раз десять писал об isEditable и сейчас напишу еще раз про IsUrgent. Вот впилили вы свою проверку if (orderDto.Comment.Contains(«СРОЧНО!!!»)). В одном месте вы цвет поменяли, в другом сортировку подхачили, в третьем вывели в какой-то дополнительный виджет эти заказы. И вот к вам приходит ваш начальник и говорит, что расширяем продажи, будут клиенты иностранцы, будут менеджеры, которые с их заказами работают. Срочно теперь будет не только «СРОЧНО!!!», но и «URGENT!!!». Что вы делаете? Да, вы идете и правите все те места, где вы логику вынесли наружу.

                                                                                                                            Еще пару дней спустя вас просят добавить галочку на форму заказа, которая за +100500 денег делает срочный заказ. И вы теперь идете во все те же места и добавляете OR orderDto.UrgentFlag. А потом еще появляется группа пользователей, у которых приоритетная доставка и все их заказы всегда срочные. И при каждом изменении логики работы со срочными заказами нужно пойти и везде поменять. Но можно же один раз описать правило в методе isUrgent в доменной модели. Один раз при измененнии доменной модели считать необходимое состояние и обновить рид модель, и юзать ее там, где это нужно, не дублируя бизнес логику на UI
                                                                                                                              0
                                                                                                                              И находится он в доменной модели. В рид модели ее нет, в рид модели есть только необходимые для отображения данные, без каких-либо проверок.

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

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

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


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

                                                                                                                                    0
                                                                                                                                    Читать рид модель с одной таблицы или с другой бд или вообще хранить в памяти — дело сугубо ваше. В хендлере не нужно дублировать логику. В домене есть бизнес правило, которое определяет срочность заказа. В доменной модели это правило реализовано в методе isUrgent(): bool;

                                                                                                                                    Хендлер вызывает этот метод и результат сейвит в рид модель.
                                                                                                                                +2
                                                                                                                                Тут есть нюанс, пишут в бд одном месте, а читают в другом (чаще всего еще и с реплики).

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


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

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


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

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


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

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


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

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


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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


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

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


                                                                                                                      low coupling в GRASP

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


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

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


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

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


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

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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


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

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



                                                                                                                                  Если потребитель хочет получить какую-то информацию из другого домена, то он должен объявить свое требование, в виде интерфейса например:
                                                                                                                                  interface OrderInfoProviderInterface
                                                                                                                                  {
                                                                                                                                  public function getOrderInfo(string $orderId): OrderInfoDTO;
                                                                                                                                  }


                                                                                                                                  и завязывать все места, где он хочет получить инфо по ордеру на этот интерфейс. Сам этот интерфейс и ДТО принадлежат домену получателя. Как и кем будет реализован этот интефейс — это уже другая история.
                                                                                                                                    –2
                                                                                                                                    да уже все, «3 года опыта» увидели,
                                                                                                                                    доводы по теме будут более агрессивные, простые и острые

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

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

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

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

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

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


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

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


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

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

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


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

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


                                                                                                                                        Реализация интерфейса находится вне домена. Это простой адаптер.
                                                                                                                                        Суть в развороте направления зависимостей, которая и приводит к тому, что домен заказов перестает зависеть от домена покупателей.
                                                                                                                                          –1
                                                                                                                                          как раз, чтобы не завязывать друг на друга два домена.

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


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

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


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


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

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

                                                                                                                                            +2
                                                                                                                                            Вне какого домена. Адаптеры — штука апликейшен слоя, а не доменного.

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

                                                                                                                                            В адаптере, который заимплементит этот интерфейс может быть что угодно, http запросы, sql, etc.
                                                                                                                                            Да, адаптер знает о домене покупателей, поскольку напрямую его юзает. Скорей всего, юзает его через тот самый inbound port, но это не касается домена заказов.
                                                                                                                                              0
                                                                                                                                              Вне какого домена. Адаптеры — штука апликейшен слоя, а не доменного.

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


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

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

                                                                                                                                                0
                                                                                                                                                В вашем стеке есть годный материал и нужные слова, чтобы ни вы, ни 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
                                                                                                                                                  0

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


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


                                                                                                                                                  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());
                                                                                                                                                    }
                                                                                                                                                  }
                                                                                                                                                    0
                                                                                                                                                    1. ну оба агрегата находятся в одной кодовой базе, а значит не имеет значения и могло бы быть и в монолите, тк эти принципы в DDD, а не в микросервисах


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

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


                                                                                                                                                      тк эти принципы в 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 рекомендует не пересекать границы агрегата, нет, из этого никак не вытекает отказ от геттеров.


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

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

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

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

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


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

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

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

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

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

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

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


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

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


                                                                                                                                                              Буду

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


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

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

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

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

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

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

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

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

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


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

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


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

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


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

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

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

                                                                                                                                                                      0
                                                                                                                                                                      Почитайте на странице 19 и дальше ce.sharif.edu/courses/97-98/2/ce418-1/resources/root/Books/Patterns%20of%20Enterprise%20Application%20Architecture%20-%20Martin%20Fowler.pdf
                                                                                                                                                                      Application Specific Logic (UseCase, ApplicationServices) это вам не Presentation и не Data Aces Layer.
                                                                                                                                                                        –1

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

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

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

                                                                                                                                                    0
                                                                                                                                                    Да, но когда вы из одного домена юзаете inbound port другого — вы их связываете. В другой домен просачивается информация, которая ему не нужна. Банальный пример, домен юзеров отдает всю инфу по юзеру, мыло, телефон, имя, адрес, социалочки и так далее. А домену заказа надо только имя и адрес.
                                                                                                                                                      +1
                                                                                                                                                      Да

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


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


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

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


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


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


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

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


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

                                                                                                                                                        –1

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

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

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


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

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

                                                                                                                                          0

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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

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

                                                                                                                                          Нет.


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

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


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

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

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

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

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