1

Тема: Rich Model – проблемы концепта

Вот уже пять дней я "бьюсь головой о стену", пытаясь определить для себя наиболее целостный концепт Entity как бизнес-объекта.

Для первоначального понимания, о чем речь, рекомендую следующие ресурсы:
1. Доклад Кирилла Чебунина "Rich model and layered architecture in Symfony2 application". Слайды этого же доклада.
2. Весьма интересная статья Бенджамина Еберлея "Building an Object Model: No setters allowed".

Суть проблемы хорошо освещена в докладе, приведу основные требования к бизнес-объекту:
1. Объект всегда должен иметь валидное состояние.
2. Объект должен быть самодостаточным, т.е. всю бизнес-логику, относящуюся непосредственно к нему, выполнять самостоятельно.
3. Объект должен быть по возможности простым и читабельным, соответственно разные трюки с рефлексией и прочие извращения в реализации нежелательны.

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

Основные грабли:
1. Философия второй Доктрины дает нам вместо одного супер-умного объекта два: глупый Entity и управляющий им EntityManager. Здесь заключена первая концептуальная проблема - при попытке внедрить в Entity бизнес-логику и сделать его умнее мы неизбежно сталкиваемся с ситуацией, когда два разных объекта занимаются по сути одной задачей - и энтити, и ее менеджер управляют этой самой энтити. Соответственно вместо одной точки входа мы получаем две - мы можем создать объект как напрямую через new Entity, так и через EntityManager::create(). При этом, указать каким-либо адекватным способом, что нужно использовать именно менеджер и нельзя new мы не можем. Аналогичная проблема с любыми другими бизнес-методами.

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

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

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

Если у кого возникнет желание поломать себе мозг и/или светлые мысли на эту тему - вэлкам в комментарии.

2

Re: Rich Model – проблемы концепта

О почему ты пляшешь от доктрины? Энтити в рамках доктрины к бизне-логики вообще отношения иметь не должна (в идеальном мире конечно). Все же энтити ближе к персистентному хранилищу.
Мне нравиться подход, который реализован в FOSUserBundle. У них модель это модель и благодаря этому они легко прикрутили доктрину, пропел и ДоктринаМонгоДБ, но так же можно легко прикрутить и любое другое хранилище.
Если и проектировать по уму то начинать с модели и уже в конце добавлять хранилище, тогда связь будет минимальной. Как это сделать красиво уже вопрос другой и думаю будет индивидуально решаться в каждом отдельном случае, так как бизнес-логику довольно индивидуальная штука.

Я люблю то, что делаю и делаю то, что люблю.

3

Re: Rich Model – проблемы концепта

IgorN пишет:

О почему ты пляшешь от доктрины?

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

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

IgorN пишет:

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

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

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

IgorN пишет:

Все же энтити ближе к персистентному хранилищу.

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

Re: Rich Model – проблемы концепта

В чём проблемы:

1. Entity - это не бизнес-объект. Это запись в абстрактной базе данных. В какой - это определяет репозиторий.

2. EntityManager - это Data Gateway. В нём может быть единственная логика: извлечь Entity из базы или положить обратно плюс всевозможные поиски по базе.

3. Бизнес-объектом должна являться Model. В эту Model может подаваться Entity как описание состояния модели и так же через Entity состояние модели может записываться в базу.

Корень же проблемы в том, что обычно при построении проекта на Symfony отходят от MVC в сторону EVC: Entity - View - Controller. Этому способствует Doctrine, умея создавать внутри Entity ссылки и коллекции связанных сущностей - что очень начинает напоминать Model. В итоге, когда проект становится хоть сколько-нибудь сложным, логику приходится помещать в Controller (многим уже понятно, что это неправильно) или в Entity (продвинутым кажется, что они вышли на новый уровень понимания ООП). Запихивая логику в Entity, люди сталкиваются с серьёзными проблемами - Entity внезапно оказывается под это совершенно не приспособлен, хотя в Doctrine выглядит очень похоже на Model.

Что делать, если в проекте такой удобный антипаттерн EVC, а где-то нужно добавить бизнес-логику?

Самый простой вариант: создавать в нужных местах Model и через Controller помещать туда Entity, дальше же работать с Model. После работы забирать Entity из Model и класть в EntityManager. В большинстве случаев этого хватает.

Когда Model становится сложной, есть смысл пойти дальше - использовать SOLID-принцыпы совсем на полную, то есть разбить Model на много объектов, каждый из которых выполняет только один тип работы. Интересная статья. В той же статье из примеров видно, почему так популярен в Symfony упрощённый MVC в виде EVC - кода в разы меньше, а значит за раз можно взглядом охратить почти всю логику своего приложения: от записи в MySQL до отображения этой записи конечному пользователю.

5

Re: Rich Model – проблемы концепта

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

6 Отредактировано smilesrg (2014-01-16 21:33:26)

Re: Rich Model – проблемы концепта

Вставлю свои 5 копеек. Тоже над этими вопросами заморачивался. Так вот, оказывается, модель - это не класс, а целый слой. Тогда получается, что модель - это Entity, EntityRepository, Сервисы, и даже:

много объектов, каждый из которых выполняет только один тип работы.

7

Re: Rich Model – проблемы концепта

smilesrg пишет:

Так вот, оказывается, модель - это не класс, а целый слой

Модель — это конечно слой, но речь в теме вообще-то шла про бизнес-объекты, и под словом "модель" соответственно в данном случае имеет смысл понимать именно бизнес-объект. Так уж вышло, что и то и другое принято называть моделью.
Тем более что в контексте "модель = слой" дискутировать особо не о чем.