[Перевод] Анемичная модель предметной области — не анти-шаблон, а архитектура по принципам SOLID

От переводчика: На проекте, где я работаю, сейчас идет активное переписывание логики, ранее реализованной в виде богатой модели предметной области (с использованием Active Record и Unit of Work). Новый подход включает в себя классы сущностей без поведения и служб без состояния, взаимодействующих посредством интерфейсов — фактически, он представляет собой анемичную модель, с перспективой перехода в дальнейшем на микросервисную архитектуру. Наблюдая в режиме реального времени, как «макаронный монстр» из примерно полутора миллионов LOC постепенно обретает форму, как упрощаются тестирование, масштабирование и кастомизация системы под нуждый различных заказчиков, я был весьма удивлен, узнав, что такой подход часто рассматривается как архитектурный анти-шаблон. Пытаясь разобраться в причинах этого, я наткнулся на данную статью и размещаю здесь ее перевод, чтобы обсудить с сообществом плюсы и минусы подхода.


Оригинал: The Anaemic Domain Model is no Anti-Pattern, it’s a SOLID design


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


Говоря об объектно-ориентированной разработке программного обеспечения, под шаблонами проектирования понимают повторяющиеся и эффективные способы решения часто возникающих проблем. Благодаря формализации и описанию таких шаблонов разработчики получают набор «проверенных в бою» архитектурных решений для определенных классов проблем, а также общий словарь для их описания, понятный другим разработчикам. Первым этот термин ввел Эрих Гамма в своей книге «Приемы объектно-ориентированного проектирования. Паттерны проектирования» [5], где он описал несколько часто применяемых шаблонов. По мере того как новое понятие набирало популярность, словарь шаблонов проектирования пополнялся ([6], [17]).


Вслед за ростом популярности концепции паттернов проектирования в обиход была введена идея «анти-шаблонов» ([7], [8]). Как ясно из самого названия, анти-шаблон — это противоположность шаблона. Он тоже описывает повторяющийся способ решения часто возникающей проблемы, однако, как правило, это решение нерабочее или неэффективное, оказывающее негативное влияние на «здоровье» системы (в плане простоты поддержки, расширяемости, надежности и т.д.). Анти-шаблоны служат тем же целям, что и шаблоны: при описании анти-шаблона показывают типичные варианты реализации, раскрывают контекст, в котором он применяется, и объясняют, к каким проблемам в разрабатываемом ПО это приводит.


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


Я убежден, что одним из таких незаслуженно отвергаемых анти-шаблонов является Анемичная модель предметной области (АМПО, Anaemic Domain Model), описанная Мартином Фаулером [1] и Эриком Эвансом [2]. Оба автора описывают этот шаблон как неспособность смоделировать предметную область в объектно-ориентированном стиле, из-за чего бизнес-логика описывается в процедурном стиле. Такой подход противопоставляется Богатой модели предметной области (БМПО, Rich Domain Model) [1], [20] — в ней классы, представляющие сущности предметной области, содержат в себе и данные, и всю бизнес-логику. Да, анемичная модель может быть неудачным выбором для некоторых систем, но совершенно не факт, что то же самое справедливо для любых систем. В этой статье я рассматриваю аргументы, выдвигаемые против анемичной модели, и обосновываю, почему в ряде сценариев АМПО выглядит разумным выбором с точки зрения соответствия принципам SOLID, сформулированным Робертом Мартином ([3], [4]), — принципам, в которых заключены рекомендации по достижению баланса между простотой, масштабируемостью и надежностью при разработке ПО. Решая гипотетическую проблему и сравнивая анемичную и богатую модели, я намерен показать, что АМПО лучше соответствует приципам SOLID. Тем самым я хочу оспорить категоричное мнение об этом подходе, навязанное авторитетами, и показать, что использование АМПО — на самом деле, годное архитектурное решение.


Почему анемичную модель предметной области считают анти-шаблоном?


Фаулер [1] и Эванс [2] описывали АМПО как совокупность классов без поведения, содержащих данные, необходимые для моделирования предметной области. В этих классах практически нет (или нет вовсе) логики по валидации данных на соответствие бизнес-правилам. Вместо этого, бизнес-логика заключена в слое служб, который состоит из типов и функций, обрабатывающих элементы модели в соответствии с бизнес-правилами. Основной аргумент против такого подхода состоит в том, что данные и способы их обработки оказываются разделены, что нарушает один из фундаментальных принципов объектно-ориентированного подхода, т.к. не позволяет модели обеспечивать собственные инварианты. В противоположность этому, хотя БМПО и состоит из того же набора типов, содержащих данные о предметной области, — но вся бизнес-логика также заключена в этих сущностях, будучи реализованной в виде методов классов. Таким образом, БМПО хорошо согласуется с принципами инкапсуляции и сокрытия информации. Как было отмечено Майклом Скоттом в [9]: «Благодаря инкапсуляции, разработчики могут объединять данные и операции по их обработке в одном месте, а также скрывать ненужные детали от пользователей обобщенной модели».


В БМПО слой служб чрезвычайно тонок, а иногда и вовсе отсутствует [20], и все правила, относящиеся к предметной области, реализуются посредством модели. Тем самым утверждается, что сущности предметной области способны полностью самостоятельно обеспечивать свои инварианты, что делает такую модель полноценной с точки зрения объектно-ориентированного подхода.


Не нужно забывать, однако, что способность модели обеспечивать выполнение определенных ограничений, налагаемых на данные, — это лишь одно из множества свойств, которыми должна обладать система. Пусть АМПО жертвует возможностью валидации на уровне отдельных бизнес-сущностей, но взамен она дает невероятную гибкость и простоту поддержки системы в целом, благодаря тому, что реализация логики вынесена в узкоспециализированные классы, а доступ к ним осуществляется через интерфейсы. Эти преимущества имеют особенно большое значение в языках со статической типизацией, таких как Java или C# (в которых поведение класса не может быть изменено во время исполнения программы), т.к. улучшают тестируемость системы путем введения явных «швов» ([10], [11]) с целью устранения чрезмерной связанности.


Простой пример


Давайте представим серверную часть интернет-магазина, где клиент может как покупать товары, так и выставлять на продажу товары для других клиентов со всего земного шара. Приобретение товара приводит к уменьшению средств на счету покупателя. Подумаем, как можно реализовать процесс размещения клиентом заказа на приобретение товара. Согласно требованиям, клиент может разместить заказ, если у него а) достаточно средств на счету, и б) товар доступен в регионе клиента. При использовании БМПО, класс Customer будет описывать сущность «Клиент»; он будет включать все свойства клиента и такие методы как PurchaseItem(Item item) (Купить товар). Аналогично, классы Item и Order представляют модели предметной области, описывающие сущности Товар и Заказ, соответственно. Реализация класса Customer (на псевдо-C#) может быть примерно такой:


/*** КОД С ИСПОЛЬЗОВАНИЕ БМПО ***/

class Customer : DomainEntity // Базовый класс, предоставляющий CRUD-операции
{
    // Опускаем объявление закрытых членов класса

    public bool IsItemPurchasable(Item item) 
    {
        bool shippable = item.ShipsToRegion(this.Region);
        return this.Funds >= item.Cost && shippable;
    }

    public void PurchaseItem(Item item)
    {
        if (IsItemPurchasable(item))
        {
            Order order = new Order(this, item);
            order.Update();
            this.Funds -= item.Cost;
            this.Update();
        }
    }
}

/*** КОНЕЦ КОДА С ИСПОЛЬЗОВАНИЕ БМПО  ***/

Сущности предметной области реализуются с использованием шаблона Active Record [17], в котором используются методы Create/Read/Update/Delete (реализованные на уровне фреймворка или базового класса), позволяющие изменять записи в слое хранения данных (например, в базе данных). Предполагается, что метод PurchaseItem вызывается в рамках транзакции, совершаемой над хранилищем данных и управляемой извне (например, она может открываться в обработчике HTTP-запроса, который извлекает информацию о клиенте и товаре непосредственно из переданных в запросе параметров). Получается, что в нашей БМПО роль сущности «Клиент» состоит 1) в представлении модели данных, 2) реализации бизнес-правил, 3) создании сущности «Заказ» для совершения покупки и 4) взаимодействии со слоем хранения данных посредством методов, определенных для Active Record. Воистину, «богатству» такой модели позавидовал бы царь Крез, а мы ведь рассматривали довольно простой вариант использования.


Следующий пример иллюстрирует, как та же логика могла бы быть выражена средствами АМПО, в тех же условиях:


/*** КОД С ИСПОЛЬЗОВАНИЕМ АМПО ***/

class Customer { /* Some public properties */ }
class Item { /* Some public properties */ }

class IsItemPurchasableService : IIsItemPurchasableService
{
    IItemShippingRegionService shipsToRegionService;

    public bool IsItemPurchasable(Customer customer, Item item)
    {
        bool shippable = shipsToRegionService.ShipsToRegion(item);
        return customer.Funds >= item.Cost && shippable;
    }
}

class PurchaseService : IPurchaseService
{
    ICustomerRepository customers;
    IOrderFactory orderFactory;
    IOrderRepository orders;
    IIsItemPurchasableService isItemPurchasableService;

    // Конкретные экземпляры инициализируются в конструкторе

    public void PurchaseItem(Customer customer, Item item)
    {
        if (isItemPurchasableService.IsItemPurchasable(customer, item))
        {
            Order order = orderFactory.CreateOrder(customer, item);
            orders.Insert(order);
            customer.Balance -= item.Cost;
            customers.Update(customer);
        }
    }
}

/*** КОНЕЦ КОДА С ИСПОЛЬЗОВАНИЕМ АМПО  ***/

Сравнение примеров реализации с точки зрения соответствия принципам SOLID


На первый взгляд, АМПО явно проигрывает БМПО. В ее реализации использовано больше классов, а логика размазана по двум доменным службам (IPurchaseService и IItemPurchasableService) и ряду служб приложения (IOrderFactory, ICustomerRepository и IOrderRepository), вместо того чтобы располагаться в пределах модели предметной области. Классы предметной области теперь не содержат никакого поведения, а всего лишь хранят данные и допускают изменение своего состояния вне рамок наложенных ограничений (и — о ужас! — утрачивают способность обеспечивать собственные инварианты). Учитывая все эти явные недостатки, как вообще можно рассматривать такую модель как конкурента куда более объектно-ориентированной БМПО?


Причины, по которым АМПО является превосходным выбором для данного сценария, проистекают из рассмотрения принципов SOLID и их наложения на обе рассматриваемые архитектуры [12]. «S» означает «Принцип единственной ответственности» (Single Responsibility Pronciple, [13]), который гласит, что класс должен делать только что-то одно — но делать это хорошо. В частности, класс должен реализовывать лишь одну абстракцию. «O» — «Принцип открытости/закрытости» (Open/Closed Principle, [14]), постулат о том, что класс должен быть «открытым для расширения, но закрытым для изменения». Это означает, что при разработке класса надо максимально стремиться к тому, чтобы реализацию не пришлось изменять в будущем, тем самым сводя к минимуму последствия вносимых изменений.


Казалось бы, класс Customer в БМПО реализует единственную абстракцию «Клиент», но на самом деле этот класс отвечает за множество вещей. Этот класс моделирует и данные, и логику в рамках одной и той же абстракции, несмотря на то, что бизнес-логика имеет обыкновение меняться куда чаще, чем структура данных. Этот же класс создает и инициализирует сущности «Заказ» в момент совершения покупки, и даже содержит логику, определяющую, может ли клиент совершить покупку. А предоставляя базовые CRUD-операции, определенные в базовом классе, сущность предметной области «Клиент» оказывается еще и связанной с той моделью хранилища данных, которая поддерживается базовым классом. Стоило нам перечислить все эти обязанности, как стало очевидным, что сущность Customer в БМПО являет собой пример слабого разделения ответственности.


Анемичная модель наоборот разделяет зоны ответственности таким образом, что каждый компонент представляет единственную абстракцию. Данные из предметной области представлены в виде «плоских» структур данных [18], тогда как бизнес-правила и чисто инфраструктурные задачи (сохранение, создание новых экземпляров объектов и т.п.) заключены в отдельных службах (и доступны посредством абстрактных интерфейсов). Как следствие, связанность классов уменьшается.


Сравнение гибкости решений на базе богатой и анемичной моделей предметной области


Рассмотрим примеры сценариев, при которых нам пришлось бы изменять класс Customer в БМПО.


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

Теперь рассмотрим сценарии, в которых нам необходимо изменить типы, описанные в АМПО. Классы бизнес-сущностей, чье предназначение состоит в моделировании предметной области, подлежат изменению тогда и только тогда, когда изменяются требования к составу данных. В случае усложнения правил, по которым определяется возможность приобретения того или иного товара (например, для товара указывается минимально допустимый «рейтинг доверия» клиента, которому этот товар может быть продан), изменению подлежит только реализация IIsItemPurchasableService, в то время как при использовании БМПО нам пришлось бы соответствующим образом изменять класс Customer. Если меняются требования к хранилищу данных — в АМПО задача решается путем передачи в PurchaseService из вышестоящего класса служб приложения новой реализации существующего интерфейса репозитория [17], [19], не требуя модификации существующего кода; в БМПО так легко не отделаться, модификация базового класса затронет все классы бизнес-сущностей, унаследованных от него. В случае, когда для создания экземпляра класса Order необходимо передать дополнительный параметр, реализация IOrderFactory может оказаться в состоянии обеспечить это изменение, не оказывая влияния на PurchaseService. В анемичной модели у каждого класса единственная ответственность, и вносить изменения в класс придется только при изменении соответствующего требования в предметной области (или связанной инфраструктуре).


А сейчас представим, что для реализации нового бизнес-требования мы должны обеспечить возможность возврата средств, если клиент не удовлетворен покупкой. В богатой модели это можно было бы реализовать путем добавления метода RefundItem к сущности «Клиент», аргументируя это тем, что вся логика, относящаяся к клиенту, оказывается заключенной в сущности Customer. Однако процедура возврата денежных средств сильно отличается от процедуры совершения покупки, ответственность за которую ранее была возложена на класс Customer, и в результате мы получаем еще большее смешение ответственностей в пределах одного типа. Выходит, в классах богатой модели могут накапливаться слабо связанные элементы бизнес-логики, повышая сложность их структуры. В анемичной модели механизм возврата денежных средств можно реализовать путем создания нового класса RefundService, который будет реализовывать только логику, непосредственно относящуюся к возвратам. Этот класс может зависеть от нескольких других абстракций (т.е. интерфейсов других доменных и инфраструктурных служб), необходимых для выполнения им своих обязанностей. Обращение к методам класса RefundService может происходить из вышележащих уровней (в ответ на запрос об осуществлении возврата денежных средств), и выходит, что реализацию нового сценария удалось выполнить безо всякого влияния на ранее разработанную функциональность.


В рассмотренном примере проблема закрепления за одним классом не связанных между собой ответственностей, с которой мы столкнулись в БМПО, эффективно решается в анемичной модели при помощи букв I и D из аббревиатуры SOLID. Это, я напомню, «Принцип разделения интерфейса» (Interface Segregation Principle, [15]) и «Принцип инверсии зависимостей» (Dependency Inversion Principle, [16]). Они утверждают, что интерфейсы должны представлять собой наборы сильно сцепленных методов, и что интерфейсы должны использоваться для соединения частей системы воедино (в случае АМПО — соединение служб доменного слоя между собой). Следование принципу разделения интерфейса, как правило, дает в результате небольшие, узкоспециализированные интерфейсы — такие как IItemShippingRegionService и IIsItemPurchasableService из нашего примера, или интерфейс абстрактного репозитория. Принцип инверсии зависимостей заставляет нас опираться на эти интерфейсы, чтобы одна служба не зависела от деталей реализации другой.


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


Более гибкая и податливая структура приложения, а также следование вышеупомянутым принципам, позволяют анемичной модели проявить свои преимущества над БМПО в упрощении автоматизированного тестирования. Сильно сцепленные, но слабо связанные между собой компоненты общаются посредством интерфейсов и собираются воедино посредством внедрения зависимостей, что позволяет без особого труда подменять зависимости «пустышками», mock-объектами. Отсюда, в АМПО несложно реализовывать такие сценарии для автоматизированного тестирования, которые было бы гораздо труднее реализовать в рамках БМПО, тем самым улучшается простота поддержки автоматизированных тестов. При снижении «стоимости» автоматизированных тестов разработчики более охотно создают и поддерживают их в актуальном состоянии. В качестве иллюстрации, попробуем разработать модульный тест для метода IsItemPurchasable.


Согласно предъявленным требованиям, товар считается доступным для покупки, если у клиента достаточно средств на счету, и он находится в регионе, куда этот товар может быть доставлен. Положим, мы пишем тест, проверяющий, что если у клиента достаточно средств на счету, но он не находится в регионе, куда осуществляется доставка данного товара, то этот товар недоступен для покупки. В БМПО такой тест, вероятно, включал бы создание экземпляров Клиент (Customer) и Товар (Item), настройку Клиента таким образом, чтобы средства на его счету превышали стоимость Товара, и чтобы его регион не входил в перечень регионов, куда этот товар доставляется. После чего мы должны были бы убедиться, что customer.IsItemPurchasable(item) возвращает значение false. Однако метод IsItemPurchasable зависит от деталей реализации метода ShipsToRegion класса Item. Изменение бизнес-логики, относящейся к товару, приведет к изменению результатов этого теста. Такой эффект нежелателен, так как данный тест должен проверять исключительно логику, заключенную в классе Customer, а логика метода ShipsToRegion, заключенная в сущности «Товар», должна покрываться отдельным тестом. Поскольку бизнес-логика заключена в сущностях, описывающих предметную область и предоставляющих открытый интерфейс для доступа к заключенной в них логике, классы оказываются сильно связанными, что приводит к лавинообразному эффекту при внесении изменений, из-за чего автоматизированные тесты становятся хрупкими.


С другой стороны, в АМПО логика метода IsItemPurchasable вынесена в отдельную специализированную службу, которая зависит от абстрактных интерфейсов (метод IItemShippingRegionService.ShipsToRegion). Для рассматриваемого теста мы можем попросту создать заглушку для IItemShippingRegionService, в которой будет реализован метод ShipsToRegion, всегда возвращающий false. Разделив бизнес-логику по изолированным модулям, мы защитили каждую часть от изменений деталей реализации в других частях. На практике это означает, что небольшое изменение логики скорее всего приведет к «падению» лишь тех тестов, которые непосредственно проверяют поведение того кода, в который были внесены изменения, что можно использовать для проверки правильности нашего представления об изменяемом коде.


Рефакторинг БМПО с целью соблюдения принципов SOLID приводит к «анемии» модели


Сторонники архитектуры, использующей БМПО, могут возразить, что описанный гипотетический пример не соответствует «истинной» богатой модели. Они скажут, что в правильно реализованной богатой модели нельзя смешивать сущности предметной области с задачами по их записи в хранилище — вместо этого предпочтительнее использовать объекты передачи данных (DTO, Data Transfer Object, [17], [18]), посредством которых происходит обмен со слоем хранения данных. Они разнесут в пух и прах идею прямого вызова конструктора класса Order непосредственно из логики класса Customer — разумеется, ни в одной вменяемой реализации сущности предметной области не будут вызывать конструктор напрямую, здравый смысл заставляет использовать фабрику [5]! Но по мне, это выглядит как попытка применять мощь принципов SOLID к инфраструктурным службам, при полном их игнорировании в приложении к модели предметной области. Если нашу гипотетическую БМПО рефакторить для соответствия принципам SOLID, будут выделены более мелкие сущности: из сущности Клиент могут быть выделены сущности «Покупка клиента» (CustomerPurchase) и «Возврат ден.средств клиента» (CustomerRefund). Но может статься, что и новые модели будут по-прежнему зависеть от элементарных бизнес-правил, изменяемых независимо друг от друга, а от них, в свою очередь, будут зависеть другие сущности. Во избежание дублирования логики и сильной связанности классов эти правила придется и дальше рефакторить, выделяя их в отдельные модули, доступ к которым осуществляется посредством интерфейсов. В итоге, богатая модель, отрефакторенная до полного соответствия принципам SOLID, стремится к состоянию анемичной модели!


Заключение


Исследовав реализацию простого примера, мы пришли к выводу, что анемичная модель предметной области ближе соответствует принципам SOLID, чем богатая модель. Мы увидели преимущества, которые дает соответствие принципам SOLID: слабую связанность и сильную сцепленность, повышающие гибкость архитектуры приложения. Свидетельством возросшей гибкости явилось улучшение тестируемости приложения из-за легкости реализации «заглушек» для зависимостей. Рассматривая пути достижения этих же качеств в рамках БМПО, мы обнаружили, что рефакторинг богатой модели закономерно приводит к ее «анемичности».


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


Ссылки


Развернуть

[1] Fowler, Martin. Anaemic Domain Model. http://www.martinfowler.com/bliki/AnemicDomainModel.html, 2003.


[2] Evans, Eric. Domain-driven design: tackling complexity in the heart of software. Addison-Wesley Professional, 2004.


[3] Martin, Robert C. The Principles of Object-Oriented Design. http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod, 2005.


[4] Martin, Robert C. Design principles and design patterns. Object Mentor, 2000: 1-34.


[5] Erich, Gamma, et al. Design patterns: elements of reusable object-oriented software. Addison Wesley Publishing Company, 1994.


[6] Wolfgang, Pree. Design patterns for object-oriented software development. Addison-Wesley, 1994.


[7] Rising, Linda. The patterns handbook: techniques, strategies, and applications. Vol. 13. Cambridge University Press, 1998.


[8] Budgen, David. Software design. Pearson Education, 2003.


[9] Scott, Michael L. Programming language pragmatics. Morgan Kaufmann, 2000.


[10] Hevery, Miško. Writing Testable Code. http://googletesting.blogspot.co.uk/2008/08/by-miko-hevery-so-you-decided-to.html, Google Testing Blog, 2008.


[11] Osherove, Roy. The Art of Unit Testing: With Examples in. Net. Manning Publications Co., 2009.


[12] Martin, Robert C. Agile software development: principles, patterns, and practices. Prentice Hall PTR, 2003.


[13] Martin, Robert C. SRP: The Single Responsibility Principle. http://www.objectmentor.com/resources/articles/srp.pdf, Object Mentor, 1996.


[14] Martin, Robert C. The Open-Closed Principle. http://www.objectmentor.com/resources/articles/ocp.pdf, Object Mentor, 1996.


[15] Martin, Robert C. The Interface Segregation Principle. http://www.objectmentor.com/resources/articles/isp.pdf, Object Mentor, 1996.


[16] Martin, Robert C. The Dependency Inversion Principle, http://www.objectmentor.com/resources/articles/dip.pdf, Object Mentor, 1996.


[17] Fowler, Martin. Patterns of enterprise application architecture. Addison-Wesley Longman Publishing Co., Inc., 2002.


[18] Fowler, Martin. Data Transfer Object. http://martinfowler.com/eaaCatalog/dataTransferObject.html, Martin Fowler site, 2002.


[19] Fowler, Martin. Repository. http://martinfowler.com/eaaCatalog/repository.html, Martin Fowler site, 2002.


[20] Fowler, Martin. Domain Model. http://martinfowler.com/eaaCatalog/domainModel.html, Martin Fowler site, 2002.

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 196

    +1

    Более того, мне даже кажется, что за анемичной моделью будущее. Все приложения, по большому счету, создаются для обработки данных. И представление разработчика о предметной области (модель) — это всего лишь одно из множества возможных представлений (моделей). Эту мысль замечательно со всех сторон показывает коллега maxstroy в цикле своих статей.
    Мы никоим образом не сможем избежать хранения данных, но мы можем избежать связывания данных с правилами их обработки. Ничего не имею против ООП, но я также ничего не имею и против ФП. Просто, чем сложнее проектируемая предметная область, тем выше шанс, что придется один и тот же набор данных рассматривать с точки зрения разных моделей. И чем раньше отделить данные (то, что хранится в базе, грубо говоря) от обработчиков (то, что существует в программном коде), там меньше вероятность применить имеющуюся модель (объект = данные + поведение) в непредназначенных для этой модели (поведения) условиях.

      +2
      Все (бизнес)приложения создаются для получения результатов моделирования предметной области. Обработка данных по заданным правилам — это способ, которым эти результаты получаются. Отделять данные от правил в общем случае нужды нет, равно как и использовать одни данные в разных моделях по разным правилам. Иногда это хороший вариант, но в общем случае он вводит паразитные зависимости.
      +2
      Не надо переводить pattern как шаблон, С++ программисты не одобрят. Эти термины давно переводятся(читаются) как есть — паттерн и антипаттерн.
        0
        Да, пожалуй, стремясь избавиться от англицизмов я несколько перегнул палку.
          +3
          Посмею заявить, что не С++ программисты не имеют ничего против слов «шаблон» и «анти-шаблон».

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

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

          P.S. не подумайте что я с Вами спорю — просто хотелось изложить свою точку зрения и наблюдения.
            0
            Но известнейшая книга Гамма (приведена в статье) «Приемы объектно-ориентированного проектирования. Паттерны проектирования». Да и в самой статье то паттерны, то шаблоны употребляется.
              +1
              Возможно данная книга сыграла важную роль в популяризации «паттернов». Сложно сейчас рассуждать об этом.
              Но во когда смешивают «паттерны» и «шаблоны» в одном контексте — вот это я считаю плохим тоном.
              Я предпочитаю использовать или английский термин или перевод, но не все вместе — считаю что от этого получается какая-то каша.
                0
                В одном интервью он сказал, что сейчас очень многое написал бы по-другому. Особенно главку про Singleton.
                  0
                  В плюсах, например, согласно C++ Core Guidelines очень много теперь по другому. И синглтонов надо избегать. Всё меняется-то.
            +4
            На первый взгляд выглядит, что богатая модель искусственно перегружена ответственностями — оба метода слабо относятся к сущности Customer, по-моему, особенно метод покупки. А ActiveRecord многими сам по себе считается антипаттерном.

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

            /me не понял, почему нельзя классы, обеспечивающие инварианты, сделать как обёртку к "анемичным"? Вроде и волки сыты, и овцы целы, и пастуху вечная память — модель, "богатая" снаружи и "анемичная" внутри.

              0

              Дык вроде автор по факту и показывает, что если богатую модель вести в SOLID, ровно то что вы пишете и выйдет)

              +1
              это по сути функциональный подход, где данные и методы разделены
              мне (и не только) кажется тут более будет понятен в контексте ООП такой принцип — чем меньше у метода параметров/зависимостей (в том числе неявных в виде полей и методов классов) — тем лучше
              плюс в идеале у метода не должно быть ненужных параметров
              пример БМПО в таком случае очевидно хуже так как каждый метод принимающий Item неявно зависит от логики purchase, а в примере АМПО такая зависимость будет явно указана через PurchaseService
                +2

                Автор статьи забыл сказать с т.з. чего нечто является антипаттерном.


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


                Анемичность же — явный признак не ООП, а его "соседа" ADT. Читаем для примера http://www.cs.utexas.edu/~wcook/papers/OOPvsADT/CookOOPvsADT90.pdf


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


                Любая красивая, гармоничная и качественно спроектированная система не даётся с ходу. Это либо опыт проектировщика, либо банально эволюция.

                  +1
                  Все еще проще. Классическое ООП = «Rich Data Model» = антипаттерн. А «Anemic Data Model» — это способ адептов ООП сказать «не пишите на ООП, берите ФП», не признаваясь что вся концепция «объект сам с собой что-то делает» — провалилась.
                    0
                    Я с Вами не соглашусь.
                    Классическое ООП = «Rich Data Model» = антипаттерн.

                    Это заявление выглядит как типичное «ваше ООП отстой, а вот ФП — это круто».

                    Говнокодище пишут и на ООП языках и на ФП — проблема, в основном, в людях, а не в ООП или ФП.

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

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

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

                        0
                        VolCh Вы естественно правы — я обобщенно говорил.

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

                        Для примера и привел ActiveRecord т.к. с ним очень часто допускают ошибки и, как Вы и сказали, начинают делать лапшу из бизнес и логики хранения. В итоге получаются классы на 1500 — 4000 тысячи строк где код на все случаи жизни с переплетением бизнес правил и логики ОРМ.
                          0
                          99% подобных проблем лечится даже не архитектурой а стайлгайдом.
                          Не приниать на код-ревью классы где в методах больше 20 строк а классы больше 200 строк. Ну не строго конечно, но превышения должны быть единичны и иметь вескую причину.
                          Когда они начнут их дробить на слои создавая из лапши лазанью наследования, у них хоть чуток начнут шестеренки крутиться на тему структуры. Но даже если лазанья останется, а не появится желание разделять сущности, то все равно с таким макаронным монстром жить будет проще. Разумеется если соблюдены хоть какие-то нормы именования и компановка хоть какую-то логику имеет.
                            0

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

                              0
                              VolCh я с вами согласен.

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

                              Но добавлю, что порой даже те, кто начал варить лапшу, понимают что они наделали, но винят в этом ActiveRecord, framework и т.п. (сам грешен — так делал в прошлом)

                              А насчет Вашего описания проблемы с использованием ActiveRecord — я об этом и думал, но не расписал более детально как Вы.
                                0

                                Не понимаю, как можно винить паттерн или фреймворк, если ты сам решил его применять, особенно понимая, что применяешь ты его неправильно :)

                                  0
                                  Это как раз легко.
                                  1. Решение могло быть принято давно и не тобой, а ты теперь разгребаешь последствия нескольких лет его правильного и неправильного применения. При этом вторые обращают на себя куда больше внимания по понятным причинам, из-за чего может казаться, что они составляют 90% общего объема.
                                  2. Решение могло быть принято тобой, но неверно понято рядом последователей. И поскольку некоторые подходы позволяют отстрелить себе ноги легче, чем другие, — есть основания «винить» (ну, точнее, признавать недостатки) фреймворки, которые в большей мере допускают неверное их использование.
                                    0
                                    добавлю к коментарию pankraty

                                    представим что есть Junior или Middle разработчик. Ему дают задачу и он ее рализует основываясь на подходах принятых в текущем framework'е. Потом идет следующая задача, за ней следующая и в какой-то момент приложение разрастается, а подходы продолжают применяться как для более простого варианта и со временем все скатывается в помойку.

                                    Разработчик делает вывод, что виноват framework.

                                      0

                                      В том, что его не предупредили вовремя, что пора менять фреймворк или, хотя бы, подходы?

                                        0
                                        думаю можно и так сказать.

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

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

                                        К примеру, фирма конвеером клепает простенькие заказы на Yii2 и вдруг заходит что-то серьезное. Скорее всего начнут делать как для конвеера и наберут кучу проблем т.к. надо было отказаться сразу от Yii2, а скорее всего и от PHP. (это так, пример из пальца чтобы передать суть)
                                          0

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

                            –1
                            Валить все проблемы на глупость людей — не конструктивно. Если люди глупые — значит их учат плохо. А учат их ООП-шники во главе с Фаулерами и ДядьБобами. Сугубо гуманитарными методами, в стиле «белочка и зайка есть животные по признаку наличия четырёх лапок».

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

                            Начать заканчивать можно с того, что прекратить эти километровые споры про «Anemic vs. Rich Data Model»
                              0
                              Не мешай нам холиварить!
                                0
                                А учат их ООП-шники во главе с Фаулерами и ДядьБобами. Сугубо гуманитарными методами, в стиле «белочка и зайка есть животные по признаку наличия четырёх лапок».

                                Я вот каждый раз, когда это слышу, спрашиваю (и ни разу не получил еще ответа): а что же гуманитарного в том, как учат "ООП-шники" вообще и Фаулер и Мартин в частности?

                                  0
                                  Отсутствие научного метода.
                                    0

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

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

                                      Это ну примерно как быть Стивеном Хокингом, который придумал некую теорию, которую надо проверить на каком-нибудь ускорителе, и быть оператором этого ускорителя. Где-то посередине — те, кто построил ускоритель (создали вашу ОС, средства разработки в ней, тп).
                                        +1
                                        Использование научных достижений и их, скажем так, созидание — суть разные вещи. Вы пользуетесь булевой логикой, что есть результат науки, но вы не создаете эту булеву логику с нуля.

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

                                          0
                                          Я скорее просто подвел итог вашим комментариям выше :-) Примеры научного метода в разработке я сходу не могу даже придумать — наука может быть прикладной областью в разработке, то есть областью, куда (где) применяется (прикладывается) в итоге программный продукт. Но сама его разработка — не знаю.
                                      +3

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

                            –3
                            архитектура по принципам SOLID

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

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

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

                            Например, с чего вы вообще решили что архитектура которая соотвествует принципам SOLID — это хорошая архитектура?
                              0
                              его придумали университетские теоретики

                              Вообще-то нет. Вполне себе практики и придумали.


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

                              Вы говорите о big upfront design. Ваши эти "теоретики" обычно пишут в своих чисто теоритических книжках что это не очень хорошая идея, и хоть проектирование и важно, следует решать проблемы по мере их поступления а не просто так. Особенно с учетом того что принципы в духе Open/Close вообще не достижимы на практике (это как получить систему вообще без связанности, что по определению невозможно).


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

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


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


                              Например, с чего вы вообще решили что архитектура которая соотвествует принципам SOLID — это хорошая архитектура?

                              Цель SOLID — уменьшить каскад изменений, уменьшить сложность системы. Если вы "соблюдаете SOLID" но у вас этого нет — то возможно вы их не соблюдаете на самом деле. Это вполне частое явление, как никак концепт весьма сложный.


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


                              А не заучивать что означают буковки в аббривеатуре SOLID.

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


                              Вот еще вопрос — есть такое понятие как coupling и cohesion. Должен ли разработчик знать что это? Должен ли понимать что coupling должен быть низким а cohesion высоким и что это дает? И если да, осознаете ли вы что SRP ~= cohesion а OCP ~= coupling?

                                –1
                                его придумали университетские теоретики

                                Вообще-то нет. Вполне себе практики и придумали.

                                Как раз таки теоретики. Я бы даже уточнил — популисты. Есть теория управляемости, которая говорит, что у человека в голове помещается от 5 до 9 сущностей, так вот SRP этому явно противоречит.
                                  +1
                                  Есть теория управляемости, которая говорит, что у человека в голове помещается от 5 до 9 сущностей, так вот SRP этому явно противоречит.

                                  Интересно, каким образом?
                                    +1
                                    Разве это не очевидно? Если поведение из одного класса перенести в новый класс, то классов становится больше. На игрушечных примерах это красиво, потому что проблемы игрушечные, а в реальной жизни получается, что никто в проекте не может запомнить где находится та или иная функциональность.
                                      +2
                                      Разве это не очевидно?

                                      Нет, не очевидно. Если классов будет 5 вместо 25, но функциональность будет той же, удержать их в голове будет ничуть не легче, т.к. единицей внимания все-таки является не класс сам по себе, а единица функциональности.
                                      Кроме того, я не привязывался бы к конкретным цифрам 5-9, поскольку «удерживать внимание на N сущностей одновременно» это не то же самое, что «помнить и понимать устройство N сущностей» (второе, на мой взгляд, имеет куда большее значение в практической деятельности).
                                      В случае большого количества мелких сущностей помнить и понимать их устройство, как ни удивильно, проще, т.к. они естественным образом группируются по подобию. Например, «это валидаторы, по одному на каждый тип сущности, они делают то-то и то-то», «а это репозитории, они устроены примерно так-то и так-то, вызываются обычно из таких-то мест», «вот эта пачка классов соответствует различным стратегиям, используются там-то, выбор стратегии зависит от того-то» и т.п. В случае больших классов с многочисленными методами и внутренними взаимосвязями когнитивная нагрузка выше, как результат, помнить и понимать их устройство — сложнее.
                                        0
                                        Часть ответа написал ниже. Категорически не согласен с вашими выводами. Абстракции на то и абстракции, чтобы можно было не знать их устройство, но вы всегда знаете где это устройство посмотреть в случае необходимости. С взаимосвязями сущностей ситуация обратная, необходимо помнить, как они между взаимодействуют. На самом деле я не призывают писать god объекты, как может показаться. Должен быть баланс между количеством сущностей и их функционалом, вот для меня нахождение этого баланса и видится искусством.
                                          0

                                          Это вопрос декомпозиции/модульности системы, как организовать поведение так, что бы с этим легко было работать. Что бы модули были логически целостной единицей (SRP тот же как раз про это), и что бы связи между отдельными модулями были минимальны (это по сути то, о чем говорит нам и information hiding и подобные концепции).


                                          Относительно God Object — очень просто разбить модули на маленькие, гораздо сложнее разбить их так, что бы количество связей между ними было минимальным. Но анемичные модели (предмет дискуссии статьи) это как раз таки приводит к большей связанности между модулями и понижает кохижен модулей. Что в свою очередь приводит к большой когнетивной нагрузке.

                                            0
                                            SRP ничего не знает про логическую целостность. Логическая целостность это качество абстракций и инкапсуляция.
                                              +1
                                              SRP ничего не знает про логическую целостность.

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

                                          0
                                          «а это репозитории, они устроены примерно так-то и так-то, вызываются обычно из таких-то мест»

                                          а это logical cohesion и он является плохой практикой. Модули группируются по функционалу а не по подобию. В случае репозитория у вас в модулях представляющих разные элементы предметной области будет у каждого свой интерфейс репозитория. Реализации оных уже можно сгруппировать в один модуль.


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


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

                                            0
                                            а это logical cohesion и он является плохой практикой. Модули группируются по функционалу а не по подобию.

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

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

                                            +1
                                            В общем случае функциональность сущностью не является. Как пример можно привести класс String, в котором 3 тысячи строк, но я не помню чтобы кто то на него жаловался. И все точно знают где искать операции для работы со строками. Можно представить и обратную ситуацию, если вынести функциональность в отдельные сервисы, то собрать в голове будет гораздо сложнее.
                                              +3
                                              В общем случае функциональность сущностью не является.

                                              Сущностью в понимании DDD — не является. Сущностью в понимании "надо удерживать в голове" — является (как и метод, скажем).


                                              Как пример можно привести класс String, в котором 3 тысячи строк, но я не помню чтобы кто то на него жаловался.

                                              … как пример highly-cohesive-класса, да?


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

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


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

                                              Можно представить, конечно. Вообще, любую операцию над кодом можно представить с негативным результатом. Но это никак не значит, что SRP противоречит принципу "от 5 до 9 сущностей".

                                                0
                                                Сергей, это неожиданно, но фактически, вы отрицаете преимущества ООП.
                                                Пример с культурой неудачный, когда возникают две взаимодействующих сущности, то возникает вопрос в какой из них должна располагаться логика взаимодействия.
                                                Каким образом, по вашему мнению, SRP говорит что не надо разбивать класс string на сервисы?
                                                  0
                                                  Сергей, это неожиданно, но фактически, вы отрицаете преимущества ООП.

                                                  Нет, не отрицаю. Как вы сделали такой вывод из моих слов?


                                                  Пример с культурой неудачный

                                                  Жизненный зато.


                                                  когда возникают две взаимодействующих сущности

                                                  Ну то есть в реальной программной жизни — в подавляющем большинстве случаев.


                                                  Каким образом, по вашему мнению, SRP говорит что не надо разбивать класс string на сервисы?

                                                  SRP — примененный разумно — не говорит, что string надо разбивать на сервисы.

                                                    0
                                                    Нет, не отрицаю. Как вы сделали такой вывод из моих слов?

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

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

                                                      Во-первых, я не вижу нарушения SRP. Во-вторых, я не вижу, каким образом то, что я сказал, отрицает преимущества ООП.


                                                      скорее разумный выбор не применять SRP

                                                      Почему же? В string SRP применен, string не отвечает за операции, не имеющие отншения к строкам.


                                                      Что для меня показатель слабой состоятельность SRP как принципа проектирования.

                                                      все принципы проектирования опираются на разумное применение.

                                                        0
                                                        В string SRP применен, string не отвечает за операции, не имеющие отншения к строкам.

                                                        вы противоречите SRP
                                                          0
                                                          вы противоречите SRP

                                                          Нет. Я, возможно, противоречу вашему пониманию SRP, но почему это должно меня волновать?

                                                            0
                                                            13к комментариев говорят об обратном.
                                                            Изменение алгоритма является основанием для изменения класса?
                                                              0
                                                              Изменение алгоритма является основанием для изменения класса?

                                                              Того, в котором реализован этот алгоритм — да.

                                                                0
                                                                А если в классе два разных алгоритма, то это две причины для изменения?
                                                                  0

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

                                                                    –1
                                                                    Ваш SRP это топор из сказки каша из топора. Берем СРП много здравого смысла и опыта и получаем результат. Только СРП в данном случае лишний компонент. Вам он не нужен, тому кому он нужен он не поможет.
                                                                      +1
                                                                      Берем СРП много здравого смысла и опыта и получаем результат.

                                                                      С программированием вообще так, вы не поверите. Без опыта и здравого смысла не работает.


                                                                      Вам он не нужен

                                                                      Может, я сам разберусь, что мне нужно, а что — нет?

                                                                        –1
                                                                        Может, я сам разберусь, что мне нужно, а что — нет?

                                                                        Вы спрашиваете у меня разрешения?
                                                                          0

                                                                          Нет, я намекаю вам, что судить, кому что нужно — опасно.

                                                                    0

                                                                    нет, все еще одна — вам нужно как-то по другому со строками работать.


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


                                                                    p.s. если для вас сложны роли и зоны ответственности и все это такой ужас вызывает — не смотрите в сторону DDD.

                                                                      0
                                                                      Не поверите, я сторонник DDD. Не надо ставить SRP на одну полку с DDD. Уровень этих концепций несопоставим. Если вы не понимаете, как вообще можно не ценить такую вещь как SRP, то уверяю вас, я не один такой. Вот тут нагуглил еще одного недовольного, причем не только SRP.
                                                                        0
                                                                        Вот тут нагуглил еще одного недовольного, причем не только SRP.

                                                                        … у которого в рассуждениях есть как минимум одна фактическая ошибка (он путает dependency inversion с dependency injection).

                                                                          0

                                                                          Ну Дэн вообще клевый чувак, и он в этом докладе неплохо так набрасывает. И целью ставит не отказ от SOLID, а что бы слушатель начал задавать правильные вопросы.


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


                                                                          Что до "знаний будущего" — SOLID в книге Боба идет уже после рефакторинга, так что… я думаю ответ тут довольно простой.

                                                                            0
                                                                            Ну Дэн вообще клевый чувак, и он в этом докладе неплохо так набрасывает.

                                                                            Я просто не люблю подобные набросы. Утомили.


                                                                            Однако как по мне — он заменяет абстрактные термины (вроде единой ответственности) еще более абстрактным "пиши хороший код".

                                                                            Яп-яп.

                                                                              0
                                                                              еще более абстрактным «пиши хороший код»

                                                                              в отношении SRP он был достаточно конкретен «can easily do several related things»
                                                                              По сути, можно опровергнуть все его аргументы

                                                                              Этож не тесты на быстродействие, опровержение будет очень субъективным.
                                                                                0
                                                                                в отношении SRP он был достаточно конкретен «can easily do several related things»

                                                                                а как определить что они related? Вон у людей так logical cohesion выходит и для них они related.


                                                                                Этож не тесты на быстродействие, опровержение будет очень субъективным.

                                                                                Так он ничего более объективного не предлагает.


                                                                                Словом, дискуссия всеравно зайдет в тупик. Можете на GRASP посмотреть, они чуть менее расплывчато сформулированы.

                                                                                  0
                                                                                  все очень субъективно, если автор считает, что related, значит related. Если при эксплуатации дизайна выяснится, что это неудобно, значит надо будет разнести. То, что Дэн не предлагает другого формального подхода еще не делает SRP правильным. Наоборот, нет никакого формального подхода. Хорошая модель должна помещаться в голове, но SRP об этом ничего не знает. На практике я вижу, что с одной стороны тестопригодность растет и размер класса уменьшается, но с другой увеличивается количество синтетических абстракций и нарушается инкапсуляция, что не идет на пользу качеству модели.
                                                                                    +1
                                                                                    что это неудобно, значит надо будет разнести.

                                                                                    но это еще более субъективно. Я знаю разработчиков которым удобно глобальный стэйт и процедуры. Просто потому что они так привыкли за 10-15 лет.


                                                                                    Хорошая модель должна помещаться в голове

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


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

                                                                                    Каким образом она нарушается? Что до синтетических абстракций — pure fabrication то есть, вы же их вводите для того что бы было удобнее, а не просто так.


                                                                                    p.s. я уважаю мнение Дэна, но как по мне конкретно этот наброс так себе. В набросе про dependencies as a code smell намного больше пользы.

                                                                                      0
                                                                                      но это еще более субъективно.

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

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

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


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


                                                                                        потому что SRP.

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

                                                                            0
                                                                            Уровень этих концепций несопоставим.

                                                                            Bounded Context как по мне про SRP (просто на более высоком уровне модулей). Вы же надеюсь понимаете что эти принципы работают не только для методов классов.


                                                                            То есть SOLID в среднем проще DDD.

                                                              0
                                                              это то что нарушает SRP

                                                              SRP про кохижен. OCP про каплинг и инкапсуляцию. Вы должны учитывать оба принципа.

                                                                0
                                                                кохижен и каплинг это метрики кода. инкапсуляция это принцип проектирования. srp это тоже метрика, но ни как не принцип.
                                                                  +1
                                                                  srp это тоже метрика, но ни как не принцип.

                                                                  Определение с вами не согласно.

                                                                    0
                                                                    Определение писал дядя Боб, я считаю его популистом. Поэтому мне не столь важно что он написал в определении, как то что он написал в обоснование своей теории
                                                                      +1

                                                                      Вот я и говорю: вас не устраивает ваше же понимание SRP, о котором вы выносите какие-то суждения. Ну да, сколько угодно, но к SRP, который входит в SOLID, и который люди применяют на практике, это никакого отношения не имеет.

                                                                        0

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


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

                                                                      +2

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

                                                                        0
                                                                        кохижен и каплинг это метрики кода

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


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

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

                                                                            Да? Покажете, пожалуйста, алгоритм расчета cohesion на уровне класса.

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

                                                                                lac of cohesion все же немного "не то".

                                                                                  0
                                                                                  LCOM это уж слишком примитивно. Я бы рассматривал взвешенные связи, например распределяя вес по всему AST по типу как гугл распределял PR. Ну и по остальным моментам тоже самое. Но «очень легко» тут не будет, а потянет на солидную работу. Которая на выходе все равно сделает нейронку генерящую порно.
                                                                                0
                                                                                Именно что легко, количество непересекающихся в обработке, наборов данных класса.
                                                                                  0
                                                                                  количество непересекающихся в обработке, наборов данных класса.

                                                                                  Что такое "непересекающиеся в обработке наборы данных"?

                                                                                    0
                                                                                    Это когда часть методов оперируют на данных A и B, а другая часть на данных C и D.
                                                                                      0

                                                                                      Каков cohesion у статических методов класса string? А у Enumerable.*?

                                                                                        0
                                                                                        Понятия не имею, мне достаточно того что в методе стринг достаточная инкапсуляция и как абстракция он тоже оправдан. Enumerable вообще про другое.
                                                                                          0
                                                                                          Понятия не имею

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

                                                                                            0
                                                                                            я вот тут habrahabr.ru/post/346016/#comment_10612066 писал как надо применять метрики
                                                                                              0
                                                                                              некий показатель, глядя на который уже надо думать надо ли что то с этим делать

                                                                                              Ну то есть ровно то же самое, в чем вы критикуете SRP.

                                                                                                0
                                                                                                Верно. Некоторое преимущество только в том, что она чуть более формальная и никто не пытается с ее помощью проектировать ООП софт.
                                                                                                  0
                                                                                                  никто не пытается с ее помощью проектировать ООП софт

                                                                                                  Если вы не пытаетесь, то это еще не значит, что никто не пытается.

                                                                    0
                                                                    то возникает вопрос в какой из них должна располагаться логика взаимодействия.

                                                                    Да, и на этот вопрос нужно дать ответ. Для того что бы примерно прикинуть правильно ли вы дали ответ и придумали целую кучу принципов (SRP, high cohesion, low coupling, information hiding, protected variations)


                                                                    Каким образом, по вашему мнению, SRP говорит что не надо разбивать класс string на сервисы?

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

                                                                      0
                                                                      Да, и на этот вопрос нужно дать ответ.

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

                                                                      Это какая?
                                                                        0
                                                                        но SRP дает неправильный ответ.

                                                                        SRP дает вопрос, а не ответ. А уже ответ на него вы предоставляете. Так что не стоит обвинять в ваших решениях принцип, который предлагает вам лишь способ как проверить правильность вашего ответа.


                                                                        Это какая?

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


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


                                                                        SRP намного более сложный принцип нежели "у всех классов должен быть один метод".

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

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


                                                                            То что принцип "сложный" не значит что с ним что-то не так. Скажем LSP по каким-то причинам считают сложным принципом, хотя из всех что есть в SOLID это пожалуй самый простой.


                                                                            Преобразование строки с учетом культуры это тоже работа со строками?

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

                                                                              +1
                                                                              Да что вы докопались до этих строк?
                                                                              Это библиотека. Библиотека для использования широким кругом лиц. Библиотека большая, разноплановая.
                                                                              Здесь явно напрашивается отделение внутренней архитектуры от внешнего интерфейса.
                                                                              Использовать строки удобнее когда все методы в одном классе. А вот писать и поддерживать — когда оно разделено на слабосвязанные сущности.
                                                                              Соответственно пишем код «как должно быть», а потом закрываем это тонким фасадом где каждый метод буквально состоит из одного вызова реального класса.
                                                                              Ну а внутренняя архитектура?
                                                                              В первой итерации я бы набросал пачку интерфейсов. Интерфейс сравнений, интерфейс поиска, интерфейс манипуляций, интерфейс расчлененки и т.п.
                                                                              Сделал бы несколько базовых классов строк (явно напрашиваются раздельно односимвольные строки и многосимвольные, плюс в зависимости от общей архитектуры возможно еще два разделения — с длиной в начале или с нулевым байтом в конце), расширил бы их врапером который реализует все эти интерфейсы.
                                                                              Ну и отдельно уже реализацию под все это.
                                                                              Архитектуру реализации уже строим под реализацию. В первом приближении бьем по классическому вопросу «что будет меняться вместе?».
                                                                              Потом набутстрапить соответствующие классы и частично их внутренние методы, когда структура уже «на бумаге», то отрефакторить первоначальное разбиение, за ним отрефакторить изначальную структуру интерфейсов. На этом этапе мы уже довольно четко видим возможные различия в функционале разных реализаций, так что можем выстроить структуру интерфейсов которые будут использоваться потом в клиентском коде. (Именно на этом этапе, поскольку напрашивается какой-то условный IString, но в процессе может оказаться что мы решим что-то делать только для однобайтовых строк, или к примеру мы окажемся настолько ленивы что для строк с нулем на конце мы не будем делать методы связанные с длиной, так что лучше такие вещи чуть отложить до тех пор пока не будет скелет готов).
                                                                              Дальше по мере развития кода мы правим только соответствующие классы. Разбиваем их на слои, или просто разбиваем на более мелкие сущности, объединяем, переписываем, покрываем тестами — все как у людей.
                                                                              При этом интерфейсы и фасад меняются редко, так что вполне можно сказать что они у нас являются отдельной сущностью, так что и по архитектуре тоже верно что мы его отделили от всего остального.

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

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

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

                                                                    нет.

                                                                      –1
                                                                      убедительно
                                                                        0

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

                                                                          0
                                                                          Как раз я его трактую буквально.
                                                                            0

                                                                            Что значит "буквально"? Как вы определяете нарушает ли что-то этот принцип? как вы выделяете зоны ответственности?


                                                                            Скажем если мы обратимся к первоисточнику (книга Agile Software Development: Principles, Patterns, and Practices дяди Боба), там есть следующий абзац:


                                                                            If, on the other hand, the application is not changing in ways that cause the two responsibilities to
                                                                            change at different times, there is no need to separate them. Indeed, separating them would smell of
                                                                            needless complexity.

                                                                            То есть, для определения зоны ответственности нам уже нужно смотреть на несколько вещей:


                                                                            • источник изменений. Для чего они вносятся? С какой целью и кем?
                                                                            • Затрагивают ли изменения весь модуль или лишь его части?
                                                                            • Как изменения модуля затрагивают клиентский код? (тут еще про ISP нужно помнить)

                                                                            То есть все уже не так тривиально.

                                                                      +1

                                                                      SRP как раз провоцирует на уменьшение количества сущностей, которые нужно держать в голове одновременно. Вот если взять ActiveRecord, который по определению имеет две отвественности — при работе с сущностью (в широком смысле слова) нам всегда нужно помнить, какой метод сохраняет состояние, какой его изменяет, а какой делает и то, и другое. В случае его "конкурента" DataMapper мы не лезем в сущность, когда нас интересует её персистентность, и не лезем в ORM, когда нас интересует бизнес-логика.

                                                                        0
                                                                        вы неправильно считаете сущности, до тех пор как мне не понадобится изучить детали реализации, сущность у меня будет ровно одна, в случае же с маппером появляется дополнительная сущность — маппер.
                                                                        Если сохранение будет добавляться в разные методы работы с сущностями то тут никакой ОРМ не поможет.
                                                                          +1

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


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


                                                                          class Order {
                                                                            public void cancel() {
                                                                              this.status = 'canceled';
                                                                              this.save();
                                                                            }
                                                                          }
                                                                            0
                                                                            Он применяется для того, чтобы иметь возможность писать методы типа

                                                                            Ну я бы так не сказал, это совсем необязательно. Создали снаружи и сохраняем снаружи. Можно еще unit of work сделать и сохранять их в одной транзакции.

                                                                              0

                                                                              Необязательно, но чаще применяется именно так. Если использование методов типа save() или isDirty() запрещено внутри бизнес-методов, то это лишь говорит о том, что SRP переносится с уровня объекта на уровень метода. Ну и в целом не очень тогда понятна цель использования именно ActiveRecord, если не рассматривать случай "#жричтодали". Единственное объяснение, приходящее в голову, чисто техническое — методы логики хранения имеют прямой доступ к защищенному состоянию бизнес-объекта, что упрощает код хранения, без вываливания наружу всех кишок. Но опять же на практике с ActiveRecord защищенное состояние редко встречается, максимум приватные свойства сущности оборачиваются в геттеры/сеттеры на уровне базового класса, а то и просто публичные свойства.

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

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

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


                                                                                  В целом же, да, можно придумать ситуацию, где он будет оправдан и бизнес-логика с логикой хранения смешиваться не будет. Вот только на практике сплошь и рядом за вызовом order.cancel() следует вызов order.save(), а потом кто-то это замечает и переносит save в cancel, даже не создав новый метод cancelAndSave.


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

                                                                                    0
                                                                                    order.save() это нормально, ненормально делать это сайдэффектом. Транзакционность можно делать на уровне приложения собрав все сущности с флагом isDirty. Важно именно то что вызов save() осуществляется явно и на уровне приложения.
                                                                                  0
                                                                                  ActiveRecord удобен тем, что отдельная работа с хранилищем убирается снаружи и делается более явной внутри. Это хорошо заметно со связями. Можно отдельно обратиться к свойству user_id, к объекту User, к запросу на получение User, загрузить его вместе с родительской сущностью по требованию (решение проблемы N+1). Я с Доктриной мало работал, но кажется там для этого используются какие-то магические прокси, которые подменяют что-то на лету.
                                                                                    +1

                                                                                    Прокси там для опциональной ленивой загрузки.

                                                                            0
                                                                            Совершенно верно. Главный императив разработки ПО — управление сложностью. А кто не согласен, тот льстит себе, и думает что у него неограниченный размер черепа (если немного перефразировать известное выражение Дейкстры). Вот главный фундамент качественной архитектуры: число семь.
                                                              +9

                                                              Комментирование к оригинальной статье закрыто, но это не означает что перед распространением этой статьи не нужно было ознакомиться с комментариями к ней. В этих комментариях ясно и лаконично определено какие именно ошибки в проектировании (да, именно ошибки), привели автора к этой идее. Автор оригинальной статьи не способен смоделировать предметную область и ошибки в этом моделировании валит на RDM. Автор оригинальной статьи явно не знаком с трудами Роберта Мартина в полной мере. Сам Дядя Боб неоднократно высказывался против применения Simple Data Structures как основного элемента программ. Его примеры и обьяснения принципов изобилируют применением инстансов, которые несут операции, а не данные. Да, он сам говорил что для внутренней реализации могут быть использованны данные, но контракт юнита не должен давать к ним прямого доступа.
                                                              Далее: автор оригинальной статьи использует ActiveRecord, наследует сущьность и говорит что это из-за RDM он не может легко заменить хранилище на то, которое не поддерживается его базовым классом. Тем самым нарушая рекомендации (см. пункт Persistence) Uncle Bob'а и снова сваливая всю ответственность на RDM.
                                                              Тестируемость: Uncle Bob, как большой поклонник и пропагондист TDD, разумеется посвятил этой теме не мало материала, с короым автор оригинальной статьи так же не ознакомился перед её написанием. Тестироваться, прежде всего, должен контракт, а не внутренняя структура (детали реализации) т.к. детали реализации могут меняться, но контракт должен выполняться, о чём как Роберт Мартин так и Мартин Фаулер и Стив Макконелл неоднократно писали. Автор же, нарушая essence тестирования и покрывая функционал тестом "лишь бы тесты были" принципиально не правильно применяет тестирование получая сложность test maintain и, как и везде, обвиняя в этом RDM, а не себя.
                                                              Предложеный в статье рефакторинг, который бы привёл к состоянию близкому к ADM так же не является оптимальным. К таким последствиям может привести использование в приложении структуры данных идентичной к той в которой данные хранятся в постоянном хранилище, что не является обязательным. Структура, в которой данные хранятся (напр. таблица и row в ней), не должна влиять на дизайн самого приложения.

                                                                +2
                                                                Как по мне, то в коментариях к оригинальной статье верно подметили:

                                                                1. В примере БМПО связан уровень доступа к данным и уровень бизнес-логики
                                                                2. БМПО спроектировано плохо:

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

                                                                Оригинал:

                                                                blog.inf.ed.ac.uk/sapm/2014/02/04/the-anaemic-domain-model-is-no-anti-pattern-its-a-solid-design/#comment-205
                                                                  –4
                                                                  Простите, но это полная ерунда. Анимичная модель, это прежде всего модель, которая не управляет парадам-приложением. При анимичных моделях вся логика, как правило аккумулируется в контроллерах,
                                                                  что приводит к размазыванию, что в свою очередь мешает переносимости-переиспользованию.

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

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

                                                                  Так же, говоря о моделях, очень важно обозначать архитектуру, так как анимичная модель не может существовать в mvc или mvvm, а другие в mvp. Другими словами, модель обуславливает саму архитектуру и смешивать все в одну кучу не верно.
                                                                    +2
                                                                    Логика в контроллерах? Оооок
                                                                      +1
                                                                      Молодой человек, конечно, далековат от истины. Но Вы, Naglec, схватили его суждения не за тот хвост). Потому что именно здесь он имеет кое-какие шансы так говорить.

                                                                      Вообще говоря, существует логика приложения, и бизнес-логика, которая, в свою очередь, разделяется на application-specific(dependent) бизнес-логику и, собственно, предметную бизнес-логику (извиняюсь за тавтологию).

                                                                      Предметная бизнес-логика — это логика уровня предметных (Domain) моделей. В данной статье предлагается не разделять подвиды бизнес-логики, и все засовывать в сервисы.

                                                                      А в таком случае, срабатывает принцип, изложенный Мартином Фаулером:

                                                                      Гораздо легче ответить на вопрос, когда слой служб не нужно использовать. Скорее всего, вам не понадобится слой служб, если у логики приложения есть только одна категория клиентов, например пользовательский интерфейс, отклики которого на варианты использования не охватывают несколько ресурсов транзакций. В этом случае управление транзакциями и выбор откликов можно возложить на контроллеры страниц (Page Controller, 350), которые будут обращаться непосредственно к слою источника данных. Тем не менее, как только у вас появится вторая категория клиентов или начнет использоваться второй ресурс транзакции, вам неизбежно придется ввести слой служб, что потребует полной переработки приложения.

                                                                      The easier question to answer is probably when not to use it. You probably don’t need a Service Layer if your application’s business logic will only have one kind of client say, a user interface and its use case responses don’t involve multiple transactional resources. In this case your Page Controllers can manually control transactions and coordinate whatever response is required, perhaps delegating directly to the Data Source layer. But as soon as you envision a second kind of client, or a second transactional resource in use case responses, it pays to design in a Service Layer from the beginning. («Patterns of Enterprise Application Architecture» Martin Fowler)


                                                                      Тут, правда, есть нюансы, но я не хочу перегружать этот комментарий, если интересно, то после данной цитаты я их приводил в этой статье по проектированию Сервисного Слоя.
                                                                    0
                                                                    Для каждого паттерна можно подобрать проект в котором тот станет антипаттерном. По моему опыту толстыми моделями приятно пользоваться, особенно если они написаны грамотно. Все в одном месте, не нужно искать хэлпер для форматирования какого-то поля объекта, не нужно выдергивать зависимости для выполнения каких-то действий над объектом.
                                                                      +5
                                                                      > не нужно искать хэлпер для форматирования какого-то поля объекта

                                                                      Форматтеры в модели, это как раз пример плохой богатой модели. Из-за таких моделей появляются подобные статьи. Модель богатая, потому что в ней описана вся ее бизнес логика, а не потому что в ней описано вообще все-все, что к ней относится. Форматирование, этой слой UI.
                                                                        0

                                                                        Это зависит, мне кажется. Например, если обращение к пользователю по имени различается в зависимости от каких-то факторов (скажем, до 18 лет — ФИ, 18+ — ФИО) — это может и с натяжкой, но бизнес-логика.


                                                                        И мне лично в таких случаях было бы комфортнее писать person.Name.OurSpeciallyFormattedName, а не new PersonNameFormatter().FormatOurSpeciallyFormattedName(person).
                                                                        Сущность Person это не особо забивает, потому что из нее торчит не простыня форматов имени на все случаи жизни, а только что-то вроде public PersonName Name => new PersonName(this), и именно этот PersonName инкапсулирует всю логику, связанную с разными форматами имени. Вроде как такой торчащий из сущности PersonName — это пример Value Object, хотя тут я могу заблуждаться.


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

                                                                          0

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


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

                                                                            0

                                                                            Закон Деметры. Статья 1, п. 1.

                                                                              0

                                                                              С большой натяжкой. И, главное, это провоцирует добавление этой самой простыни для всех случаев жизни. Вы сами может и не добавите, а кто-то другой, увидев, что вы добавили OurSpeciallyFormattedName, добавит для другого случая, например для отчётов в налоговую FiscalFormattedName, где ФИО надо выводить полностью. А потом пенсионный попросит не ФИО, а ФИ.О… И это в рамках одной юрисдикции, при неизменяющихся требованиях собственно бизнеса.


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

                                                                                0
                                                                                Для описанных случаев я бы ни VO ни хелпер не использховал.

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

                                                                                Давайте разделим простые примеры и более сложные.

                                                                                За простой пример давайте возьмем ситуацию, когда Person у нас не интернализируется, в отчетах не учавствует и собственоо правила форматирования имени у него всегда одни. Для такого случая, я думаю применение обычного свойства было бы уже достаточно:
                                                                                public class Person
                                                                                {
                                                                                    private string firstName;
                                                                                    private string lastName;
                                                                                   
                                                                                    public Person(string first, string last)
                                                                                    {
                                                                                        firstName = first;
                                                                                        lastName = last;
                                                                                    }
                                                                                
                                                                                    public string Name => $"{firstName} {lastName}";   
                                                                                }
                                                                                

                                                                                Другое дело, если, к примеру, нам нужно будет показывать имя Person на сайте и в отчете, применяя разные методы форматирования.
                                                                                Тогда можно обратиться к адаптерам/моделям отображения или т.п., но никак не к VO и не добавлять логику для отображения и форматирования для каждого места использования в сущность. Получится что-то наподобие:
                                                                                public class Person
                                                                                {
                                                                                    public string FirstName 
                                                                                    { get; set; }
                                                                                    public string LastName 
                                                                                    { get; set; }
                                                                                   
                                                                                    public Person(string first, string last)
                                                                                    {
                                                                                        FirstName = first;
                                                                                        LastName = last;
                                                                                    }
                                                                                }
                                                                                
                                                                                public class WebPerson
                                                                                {
                                                                                    private Person person;
                                                                                   
                                                                                    public Person(Person personEntity)
                                                                                    {
                                                                                        person = personEntity;
                                                                                    }
                                                                                
                                                                                    public string Name => $"{personEntity.FirstName} {personEntity.LastName}";   
                                                                                }
                                                                                
                                                                                public class ReportPerson
                                                                                {
                                                                                    private Person person;
                                                                                   
                                                                                    public Person(Person personEntity)
                                                                                    {
                                                                                        person = personEntity;
                                                                                    }
                                                                                
                                                                                    public string Name => $"First Name: {personEntity.FirstName} | Last Name: {personEntity.LastName}";     
                                                                                }
                                                                                


                                                                                А если добавится интернализация, что-то наподобие:
                                                                                
                                                                                public class InternationalPerson
                                                                                {
                                                                                    private Person person;
                                                                                   
                                                                                    public Person(Person personEntity)
                                                                                    {
                                                                                        person = personEntity;
                                                                                    }
                                                                                
                                                                                    public string Name => PersonNameFormatter.format(person);     
                                                                                }
                                                                                


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

                                                                                Я хочу сказать, что операции над данными сущности не обязательно должны быть в самом классе сущности но это и не значит, что их все надо выносить в сервисные классы.
                                                                                Как уже сказал VolCh, сущности не должны отвечать за свое отображение.
                                                                                В сущности должен находиться код, который работает с данными сущности в рамках бизнес логики.
                                                                                К примеру, подобные методы вполне могут быть в сущности или VO сущности:
                                                                                person.isActive();
                                                                                person.Status.IsActive();
                                                                                person.Activate();
                                                                                person.IsAdult()
                                                                                


                                                                                Надеюсь мой посыл понятен. Если нет, я приведу примеры получше.
                                                                                  0

                                                                                  Выделить Name в value object свойство объекта Person с одним стандартным преобразование в строку и, возможно, методом типа format(string format) в целом хорошая идея, по-моему, как по смыслу предметной области "физическое лицо", так и по частым юзкейсам в различных системах. Случаев когда нам нужно только имя или только фамилия, не для целей презентации или поиска значительно меньше, чем случаев, когда мы оперируем им как одним целым и сравниваем разные имена по полному совпадению имени. Банальные даты и то чаще нужно по частям анализировать, чем имена, а представление их как единого целого (VO с возможностью получить день, месяц или год или просто строка единого формата) можно сказать стандарт де-факто.

                                                                                    0
                                                                                    согласен.

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

                                                                                    Насчет Name как value object с одним стандартным преобразование в строку, да это хороший вариант, для некоторых случаев избыточен но если говорить обобщенно, то я был бы рад иметь Name, Date, Status и т.п. как VO по умолчанию.

                                                                                    Огорчает только то, что далеко не все фраемворки (в частности ORM) дают возможности для удобной работы с VO и далеко не во всех ОО языках VO распространены и нормально поддерживатся.
                                                                          0
                                                                          Либо я невнимательно читал, либо автор подобрал неудачные примеры. В первый раз вижу, чтобы в классах-сущностях находилась какая-то бизнес-логика.
                                                                            +2
                                                                            Классы сущностей — одно из двух основных мест для размещения бизнес-логики. Второе — сервисы (доменные). Грубое правило: то, что изменяет одну сущность, в неё и помещается, что несколько — в сервис. Больше особо и помещать некуда, остальное, по идее, или инфраструктура, или логика приложения.
                                                                              0
                                                                              Если часть логики поместить в сущности, а часть в сервисы, можно запутаться. Удобнее когда вся логика одного уровня находится в одном месте — в сервисах. В сущностях видел только что-то очень простое, типа валидации. Сам пишу на java, но сомневаюсь, что в других ОО языках это будет сильно отличаться.
                                                                                +1
                                                                                Похоже, вы тоже используете подход с анемичной моделью, особо не задумываясь об этом. Удивлены, что это, оказывается, «антипаттерн»?
                                                                                  0
                                                                                  Удивлён. А ещё удивлён, что здесь вообще используются понятия «паттерн» и «антипаттерн». В моём понимании паттерн — готовое решение для какой-то проблемы, которое можно вписать в любую архитектуру, решение более высокого уровня. Так что впору вводить понятие «антиархитектура»)
                                                                                    0

                                                                                    Паттерн — хорошее готовое решение, антипаттерн — плохое :)

                                                                                      +1

                                                                                      Такого не бывает. GOTO и глобальные переменные это чаще всего плохо, но изредка это хорошо. Всё это хорошо/плохо — исключительно вопрос зрелости.


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


                                                                                      Но это не делает их действительно антипаттерном или плохим решением в принципе, в отрыве от конкретной ситуации.

                                                                                        +2

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

                                                                                  0
                                                                                  Если часть логики поместить в сущности, а часть в сервисы, можно запутаться

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


                                                                                  Вся проблема с тем, насколько плохо люди анализируют два параметра модулей при проектировании: cohesion и coupling. Для анемичной модели, сервисов менеджеров и т.д. есть свое определение силы cohesion и влияние на coupling. И размещение логики в сущностях/объектах значениях как раз таки один из способов снизить связанность и повысить кохижен модуля.

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

                                                                                    Наружу нужно выносить логику взаимодействия объектов, не укладывающихся в композицию, в has-a. За своё собственное состояние и состояние своих "детей" объект должен отвечать сам. Сложные взаимосвязи изменений можно реализовать разными способами, простыми сценарными скриптами, например, или доменными событиями, обработчики которых преобразуют события в последовательность вызовов разных методов разных объектов модели.

                                                                                      0
                                                                                      Если граф обьекта сложный, то понятие «детей» усложняется. Доведу пример с детьми до крайности. Если ребёнок вдруг закашлял, что с этим делать решат мама, папа, бабушка, дедушка, воспитательница детсада — в зависимости в какой иерархии (контексте) он находится. Принятие решения также зависит от времени дня, окружения (один или вокруг другие дети) и т.д. Поэтому в реальных системах подобные решения выводятся в сторонние сервисы. А им для принятия решения нужны только данные.
                                                                                      В первом приближении моё правило звучит так: чем сложнее иерархия или структура обьекта, тем меньше логики на его нижних уровнях.
                                                                                        0

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

                                                                                          0

                                                                                          Хороший пример :) Единственное, ещё они взаимодействуют с ним путём реакции на его публичные события, как бы подписываясь на них, если ребёнок рядом с ними. А так же, когда ребёнок дергает их публичные методы.

                                                                                            0
                                                                                            Речь не о ребёнке. Если надо имплементировать что-то подобное. Где должен находиться метод, выдающий решение, что с ним (ребёнком) в данной ситуации делать?
                                                                                            Догматически — в классах мамы, папы, соседа, прохожего или в их общем super (в терминологии Java).
                                                                                            Прагматически — во внешнем сервисе.
                                                                                            Но это моё личное мнение. Я Вам его не навязываю.
                                                                                              0

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

                                                                                                +1
                                                                                                Хочется верить, что решения мамы и папы будут одинаковы или не очень зависить от их собственного состояния.
                                                                                                Ну а если перестать терзать ребёнка с этой аналогией и несколько абстрагироваться. Что мы имеем:
                                                                                                1. Имеется сложный граф обьектов, узлы которого как-то можно разбить на уровни.
                                                                                                2. В этом графе один узел должен изменить состояние другого узла с помошью его публичных методоа (в терминах Java)
                                                                                                3. Для проведения изменения узлу требуется информация об изменяемом обьекте, собственном состоянии и состоянии некоторых других узлов.
                                                                                                Вопрос: должна ли вся логика определения, как изменить узел (в нашем примере — ребёнка) быть сосредоточена в изменяющем узле.
                                                                                                Ваша позиция — должна всегда.
                                                                                                Моя позиция — не всегда. Чем больше информации от других узлов требуется, тем полезнее выносить её за пределы изменяющего узла.
                                                                                                Я правильно сформулировал суть различия наших мнений?
                                                                                                  0

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


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


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


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

                                                                                  +2
                                                                                  Мое ИМХО основанное на опыте. Анимичная модель отлично справляется там, где архитектура подвержена изменениям. Можно иметь несколько вариантов сервисов, причем переключаться между ними несколько раз на дню. Если же приложение — монолит, где происходит в основном фикс багов, то да удобнее держать логику и данные в одном месте.
                                                                                    +1
                                                                                    там, где архитектура подвержена изменениям

                                                                                    Замечательное замечание! Именно поэтому я и говорю, что за анемичной моделью будущее — по крайней мере, в web-приложениях, где я обитаю. Там изменения всё время как из рога изобилия.

                                                                                      0
                                                                                      Как по мне, то анемичная модель или богатая — это часть архитектуры, причём важная часть. А если говорить о бизнес-логике, то как бы и не самая важная.
                                                                                        +3
                                                                                        Соглашусь. Я бы еще дополнил таким соображением: если «приложение» — не самодостаточная система, а некая библиотека, решающая прикладную задачу с более менее четко очерченными границами (например, работа с XLS-файлами), то для нее использовать богатую модель с продуманной разветвленной иерархией классов является вполне оправданным. Благодаря тому, что границы намечены заранее, вполне реально запроектировать систему достаточно гибкой, чтобы адаптироваться под новые требования, при этом возникновение таких требований, которые поломают всю структуру, довольно маловероятно.
                                                                                        На противоположном полюсе я бы поставил энтерпрайз системы, особенно «полукоробочного» плана, когда компании продается готовый продукт, подвергаемый серьезной модификации под нужды конкретного заказчика. Заранее предусмотреть все требования даже одного заказчика — архисложная задача. А уж сделать так, чтобы будущие требования будущих клиентов без проблем укладывались в существующий продукт (который бы при этом не был «голым» фреймворком) — еще сложнее. Рано или поздно обязательно окажется, что допущения, справедливые для 10 клиентов, не соответствуют бизнес-модели 11-го. Для таких продуктов, как мне кажется, анемичная модель подходит весьма хорошо. В коробочном продукте реализуется базовая логика, отвечающая потребностям большинства заказчиков, а там, где логику надо переопределить, сделать это легко — достаточно подменить соответствующую службу, никак не нарушая работы остального кода.

                                                                                        Для других приложений может быть найдено место на этой оси где-то между двумя полюсами. Они оба имеют право на существование. Но когда один называют «паттерном» (_«Так делать правильно!»_), а второй — «анти-паттерном» (_«Дети, не делайте так!»_), то, ИМХО, второй подход незаслуженно маргинализируется.

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

                                                                                        Мне вот каждый раз любопытно: а чем же, концептуально, подход "анемичная модель + сервисы" отличается от, собственно, функционального программирования (ну, за исключением мутабельности)?

                                                                                          0

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


                                                                                          (Не вам) Так что не надо анемичные модели оправдывать крутостью ФП. Анемичные модели — это неправильное использование ООП, и не более того.

                                                                                            0
                                                                                            А у вас в веб-приложения прям таки «бохатая модель» везде? Я хотел бы на это посмотреть.
                                                                                            У меня лично (может я и горе-проектировщик, конечно) в контексте больших постоянно развивающихся ASP.NET MVC приложений нормально уживаются именно сервисы с логикой + анемичная модель.
                                                                                            В небольших приложениях (особенно НЕ веб) с ограниченной, заранее ясной бизнес-логикой хорошо взлетает «бохатая модель»/DDD/SOLID/ООП и прочие радости.

                                                                                            Научите меня как строить крупные ынтырпрайз веб приложения с постоянно меняющейся бизнес-логикой на основе Rich Data (Domain?) Model. Книжки, статьи, примеры. Спасибо.
                                                                                              +1

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


                                                                                              Для простого бложика сойдет любая парадигма, потому что мало кода можно распределить как угодно. Active Record, только end-to-end тесты.


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


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


                                                                                              Короче, выделяйте то, что меняется, и держите вместе то, что меняется вместе.


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


                                                                                              И научитесь как бы находиться в двух режимах — программирование на уровне объектов и программирование самих объектов.

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

                                                                                                Т.е. вот у нас есть Order. Бизнес начинает придумывать разные хитрые правила изменения состояния заказа в зависимости от погоды, дня недели и цвета чулок Марьиванны. Куда мы пихнем эти правила? Куда-то наверх. При этом модель будет анемичной, IOrderProcessingService будет являться фасадом, а внутрях у ней неонка с фабриками, стратегиями, репозиториями марьиванн, хитрыми пайплайнами и прочими классными шаблонами.

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

                                                                                                  Правила (условия) изменения состояния сущностей можно хранить в спецификациях, проверять их в сервисах, а собственно изменения производить в сущностях, вызывая их методы (не тупо сеттеры, а с семантикой бизнес-процессов, не order.setStatus(STATUS_CANCEL), а order.cancel()) из этих сервисов, при условии соответствия сущности спецификации.


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

                                                                                                    0
                                                                                                    Правила (условия) изменения состояния сущностей можно хранить в спецификациях, проверять их в сервисах, а собственно изменения производить в сущностях, вызывая их методы (не тупо сеттеры, а с семантикой бизнес-процессов, не order.setStatus(STATUS_CANCEL), а order.cancel()) из этих сервисов, при условии соответствия сущности спецификации.


                                                                                                    С этим подходом согласен
                                                                                              0
                                                                                              Концептуально — наличием переменных, операции присваивания, императивностью.

                                                                                              Это как раз не "концептуально", потому что на уровне "объекты и сервисы" всего этого нет.

                                                                                                0

                                                                                                Как это нет? А с чем тогда работают сервисы, как не с переменными?


                                                                                                Анемичная модель — это процедуры и переменные-структуры. А вот в ФП вообще нет доступного программисту напрямую состояния.

                                                                                                  0
                                                                                                  Я не очень согласен в вашим определением «сервисов + анемичной модели».
                                                                                                  Мне это все видится скорее как:
                                                                                                  Сервисы (фасады) -> некоторая объектная модель с логикой ->
                                                                                                  анемичная модель (сущности) как способ хранить состояние. Возможно, с примитивной логикой в рамках одной сущности

                                                                                                  Это то, как лично у меня получается жить в современном ынтырпрайзе с ORM/DDD и прочими штуками. Да, это не кошерное ООП, но оно работает в вебе. There is no silver bullet, все дела.
                                                                                                    0
                                                                                                    Я не очень согласен в вашим определением

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

                                                                                                      0
                                                                                                      Хмм а что мешает иметь еще слои абстракции над анемичной моделью, но под «сервисами»? Все же, я лично воспринимаю «сервисы» как фасады, реализующие некоторый контракт. Ничто не мешает иметь под капотом ООП по госту.
                                                                                                    +1
                                                                                                    А с чем тогда работают сервисы, как не с переменными?

                                                                                                    С пришедшими данными.


                                                                                                    Анемичная модель — это процедуры и переменные-структуры.

                                                                                                    Совершенно не обязательно. Анемичная модель — это модель, лишенная логики. Ничто не мешает ей быть immutable.