Как назвать метод?

MiksIr

miksir@home:~$
Извините, а что такое entity? Половинка модели содержащая бизнес-логику, но не содержащая данных?
 

Redjik

Джедай-мастер
PHP:
class Pupil
{
    /**
    * @var Grade
    */
    private $grade;

    public function __construct($name, Grade $grade)
    {
        $this->grade = $grade;
    }

    public function changeGrade(Grade $grade)
    {
        // меняем Grade, через Observer оповещаем прежний Grade
        // Grade::removePupil($this)
        // и новый
        // Grade::addPupil($this)
    }

    /**
    * @return Grade
    */
    public function getGrade()
    {
        return $this->grade;
    }
}

class Grade
{
    /**
    * @var Pupil[]|ArrayCollection
    */
    private $pupils;

    public function __construct($name)
    {
        $this->pupils = new ArrayCollection();
    }

    public function removePupil(Pupil $pupil)
    {
        //метод только для оповещений
    }

    public function addPupil(Pupil $pupil)
    {
        //метод только для оповещений
    }

    /**
    * @return ArrayCollection|Pupil[]
    */
    public function getPupils()
    {
        return $this->pupils;
    }

}
Это как должно работать в идеале.
Опять же в том же Java это оправдано, ведь если у ученика сменился класс в котором он учится (Был 9А, стал 10Б), то эти классы надо оповестить об изменениях, чтобы при вызове всех учеников класса Grade::getPupils() информация была актуальной
+ в Java тоже есть "дружественные методы" ну или на уровне пакета (точно значю, что что-то типа этого есть, но на парктике только в C++ с таким работал), поэтому addPupil и removePupil в Grade не будут торчать наружу как публичные методы, а скрыты

Но в пыхе получается скрещивание ежа с ужом.
1) У нас публичные методы торчат наружу у Grade, и их можно всякими ухищрениями обрабатывать в Exception... но все же
2) В 99,999 процентах у нас изменение класса у ученика вызовет сохранение в репозиторий и на этом обьект умрет. На следующий запрос, обьект Grade восстановится из репозитория и в нем уже будут все связи, при условии, что репо - это реляционная базка

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

1) Берется ученик и список всех классов
2) Ученику присваивается класс и отправляется запрос
3) Ученик сохраняется с новым классом
4) Редирект
5) Берется ученик и список всех классов

То есть видно, что сохранение, и извлечение - это два разных запроса.
И это ораничения PHP и здравого смысла
 

Sufir

Я не волшебник, я только учусь
1) Берется ученик и список всех классов
А для чего брать "ученика" и все "классы" (что бы оповестить?) и что в данном контексте будет сводным корнем (aggregation root, корень агрегации)?

Как по факту, в данном случае достаточно присвоить новый класс ученику и сохранить ученика, если он является сводным корнем.
PHP:
$gradeId = new Domain\ValueObject\GradeID;
$pupil->changeGrade($gradeId); // Возможно этого вполне достаточно?

С другой стороны, если возможно то ассоциацию лучше привести к односторонней, например: в классах есть ученики, для перевода берем класс источник - убираем ученика, помещаем ученика в новый класс. Т.е. разрабатываемая модель предметной области возможно и не требуте, что бы "ученик" хранил в себе свой "класс", а может даже ссылку на него, если проектируемая модель того не требует?
PHP:
$oldGrade = $grdeRepository->get();
$newGrade = $grdeRepository->get();

$oldGrade->removePupil($pupil);
$newGrade->addPupil($pupil);

$grdeRepository->save($oldGrade);
$grdeRepository->save($newGrade);

А если операция является достаточно сложной, есть зависимости от различных сущностный и однозначно к какой-то исключительно одной сущности её свести нельзя - для этого вводится служба (Service)? В данном случае может быть что-то вроде ChangeGradeOfPupilService::doIt($pupil, $from,$ to);

Опять же, как по моему пониманию. Я с полноценным DDD не работал, да и не видел особо... Представляется так.
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
а если вырезать?
PHP:
class GradeListener{
  public function removePupil(Pupil $pupil)
  {
 //метод только для оповещений
 }

 public function addPupil(Pupil $pupil)
 {
 //метод только для оповещений
 }
}

class Grade{
 /**
 * @var Pupil[]|ArrayCollection
 */
 private $pupils;
 
 public function __construct($name)
 {
  $this->pupils = new ArrayCollection();
 }
 /**
 * @return ArrayCollection|Pupil[]
 */
 public function getPupils()
 {
  return $this->pupils;
 }

}
 

whirlwind

TDD infected, paranoid
Сводным корнем будет grade. Пользователям класса плевать кто, где и как хранится. Но сам сводный корень знает про модель. Он просто ее расширяет до более абстрактной, с точки зрения потребителя, сущности. Сводный корень это сервис модели. Но не классический, который data gateway к таблице, а который охватывает всю модель, те ее части, что нужны в контексте задачи. Я уже писал об этом.

WMix
, правильно показывает. Сводный корень дает интерфейс для потребителей, а не вообще интерфейс к элементам модели. И никакие репозы снаружи не нужны, потому что addPupil после add сам делает save, потому что это service layer in domain context.
 

Sufir

Я не волшебник, я только учусь
И никакие репозы снаружи не нужны, потому что addPupil после add сам делает save
Да, действительно меня этот момент с репозиториями смущал несколько. Сейчас подумал, по идее, единственным случаем явного вызова Repository::save($entity) наверное может быть только создание сущности (начало её жизненного цикла).
 

whirlwind

TDD infected, paranoid
Вурдалак, я задам пару вопросов, которые могут навести на правильные мысли

1) Что такое lazy load и как оно реализуется (кто лезет в репо и когда, не entity ли? Так знает-ли entity о repo? пусть не своем, но все же...)
2) Предположим, что к системе с классами мы хотим добавить учет, скажем, комнат для проживания . Что, если после всего мы заходим управлять корпусами для проживания, которые содержат комнаты, в которых живут студенты? (будешь ли ты дублировать entities или воспользуешься aggregate root)

Самый простой (и правильный) путь говорит нам, что все эти паттерны, за определение которых ты так цепляешься, являются характеризующими. Но они не определяют единственно-верную роль компонента. Это вообще всех паттернов касается, не только DDD. Как минимум потому, что для разных уровней абстракций один и тот же класс может выполнять разные роли. Например, система связи с банком сложна. Когда мы смотрим изнутри пакета, наверху у нас фасад. Но этот фасад всего-лишь один из множества data/service provider, если посмотреть на этот пакет снаружи, с точки зрения потребителя. И это нормально, потому что связь между уровнями абстракции есть в любой системе. DDD очень удобно выделяет контекст задачи (или проблемы) и позволяет работать в контексте. Ты же пытаешься положить классы по папочкам. Так не получится. Вернее получится, но не для сложной системы, где есть сильные связи между элементами предметной области. Ты можешь разложить в такой системе по папочкам только используя денормализацию, которая убъет сложный проект. А для декомпозиции придется смешивать роли.

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

Redjik

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

Завучу нужен простой инструмент для организации записей учеников.
Бизнес задачи.
1) Получить список всех учеников в классе.
2) Отчислить ученика. (Убрать его из класса). Тут видимо тупо удаление.
3) Добавить ученика. Пришел новичок, нужно занести его в базу и распределить в класс.
4) Массово перевести учеников из одного класса в другой. (На самом деле просто сменить год, был 9A, стал 10А)
5) Перевести одного ученика. (Вася учился в 7А, но дурачек и его решили перевести в 7Ж, к таким же дурочкам)

Ubiquitous Language

Enity Grade - year, letter
Entity Pupil - internalID, name, Grade

Как это все разрулить?

зы. Хотя очень хочется Grade в ValueObject засунуть
 

Redjik

Джедай-мастер
2) Предположим, что к системе с классами мы хотим добавить учет, скажем, комнат для проживания . Что, если после всего мы заходим управлять корпусами для проживания, которые содержат комнаты, в которых живут студенты? (будешь ли ты дублировать entities или воспользуешься aggregate root)
sub-domain или Bounded Context
 

Sufir

Я не волшебник, я только учусь
зы. Хотя очень хочется Grade в ValueObject засунуть
Мне кажется, это всё-же сущность. Дети приходят из детского сада и формируется первый "А" (жизненный цикл сущности начинается), далее ежегодно (и в течение года) изменяется состояние - меняется индекс, возможно происходят изменения в составе класса, формируются новые расписания и т.д. А с выпускным жизненный цикл сущности оканчивается и информация об nA попадает в архив.
 

Вурдалак

Продвинутый новичок
1) Что такое lazy load и как оно реализуется (кто лезет в репо и когда, не entity ли? Так знает-ли entity о repo?
В том-то и дело, что lazy loading должен быть прозрачен и модели не должно быть известно lazy там или нет, пример я уже приводил: http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities

2) Предположим, что к системе с классами мы хотим добавить учет, скажем, комнат для проживания . Что, если после всего мы заходим управлять корпусами для проживания, которые содержат комнаты, в которых живут студенты? (будешь ли ты дублировать entities или воспользуешься aggregate root).
Я не могу сказать при чём тут aggregate root вообще. Для меня это метамодель, которая может быть полезна в классическом примере с Order и OrderLines, но мне трудно это применить в рассматриваемых примерах.

Сводный корень это сервис модели.
Замечательно, aggregate root получается из репозитория и сохраняется туда же. Это, собственно, говорится в классической blue book Эвансом, это фигурирует везде, где только можно: http://stackoverflow.com/questions/1958621/whats-an-aggregate-root
In the context of the repository pattern, aggregate roots are the only objects your client code loads from the repository.
А теперь вопрос: откуда у твоего «сводного корня» берутся сервисы (зависимости), если это лишь сущность? Возвращаемся к началу?

И никакие репозы снаружи не нужны, потому что addPupil после add сам делает save, потому что это service layer in domain context.
Замечательно, а теперь откроем Фаулера, который особо на эту тему ничего не писал, но ты на него сам ссылаешься: http://martinfowler.com/bliki/DDD_Aggregate.html
Aggregates are the basic element of transfer of data storage - you request to load or save whole aggregates. Transactions should not cross aggregate boundaries.
Как так получается, что ты собираешься делать save какой-то одной части aggregate? Ты либо весь целиком его сохраняешь, либо нет, в противном случае ты можешь получить inconsistent state.

Вообще, когда ты говоришь, что «aggregate root — это сервис» мы приходим к классическому говнокоду с UserService
 

whirlwind

TDD infected, paranoid
Вурдалак, whirlwind, можно я задам пару вопросов?
В теории все замечательно раскладывается на сводные корни... давайте на практике - по полочкам - покажите свое виденье проблемы.

Ubiquitous Language

Enity Grade - year, letter
Entity Pupil - internalID, name, Grade

Как это все разрулить?

зы. Хотя очень хочется Grade в ValueObject засунуть
Ты привел отличный пример не domain-driven дизайна. Ты сам понимаешь что здесь что-то не так, потому что тебе хочется чего-то иного.

Связи между сущностями всегда двусторонние. Односторонней может быть связь части чего-то целого со своим владельцем. Почему ты делаешь grade атрибутом pupil? pupil содержит grade? grade является частью pupil? Нет. Но ты делаешь это. Может ли быть атрибутом pupil комната, в которой он живет? Нет. Может быть атрибутом pupil является его машина? Нет. Адрес? Нет. Но вы будете продолжать добавлять подобные атрибуты, добавляя связанности между компонентами системы и делая хрупкий дизайн в этом месте. Это та самая денормализация. Это путь в тупик. Когда вы упретесь в этот тупик зависит только от количества связей. Но давайте посмотрим насчет этих ссылочных атрибутов. Разве не может быть классный руководитель частью класса? Может. Может быт частью класса pupil-староста? Может. Значит, все же существуют места, где подобные ссылки уместны с обеих точек зрения: реализации и дизайна.

Но почему вы делаете это? Потому что вы создаете дизайн не от предметной области, а от реализации. Нет ничего проще добавить FK-атрибут в другой объект. Это так просто, но это не DDD. DDD это взгляд сверху вниз. Дизайн идет от предметной области, от задач и натуральных связей, а не оптимальных, которые созданны вами в целях реализации.

Тоже самое касается lazy load. Разговор сразу перешел как это плохо инжектить и все такое прочее. Но если смотреть на entity снаружи, то для потребителей ленивая загрузка сокрыта внутри entity. С точки зрения дизайна, lazy load является частью конкретного класса модели. Не важно как это сделано внутри, через декоратор или специальный ссылочный атрибут. Для потребителя это не имеет никакого значения. И все попытки перевести эту чисто утилитарную функцию в русло DDD не имеют особого смысла.

Пример организации связей many-to-many поможет найти правильный подход. Потому что здесь не так много вариантов для выбора. Это же самое решение подходит и для one-to-many. Нужно всего лишь установить unique constraint на один из ключевых атрибутов.
 
Последнее редактирование:

Redjik

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

Sufir

Я не волшебник, я только учусь
еще как может, тот же адрес, написан у тебя в паспорте, это один из идентификаторов, пусть и не уникальный, но все же ... почему ИНН атрибут, а адрес нет?
Наверное всё от контекста зависит? В данном случае ИНН и адрес как раз будут скорее ValueObject, чем сущностнями (впрочем условия слишком общие, для того что бы решить однозначно "ху из ху"). А в описанном тобой примере выше сами бизнес правила так и простят сущность "класс" в качестве сводного корня для реализации манипуляций с подсущностями "учеников", на мой взгляд. "Ученик" не является просто абстрактным "учеником", а членом конкретного "класса" из которого его могут перевести и т.п.
 

whirlwind

TDD infected, paranoid
еще как может, тот же адрес, написан у тебя в паспорте, это один из идентификаторов, пусть и не уникальный, но все же ... почему ИНН атрибут, а адрес нет?
Ты путаешь связь и владение. Адрес не моя часть. Я могу прожить без адреса, а адрес может прожить без меня. Связывая меня с адресом через атрибут, ты делаешь эту связь известной всей системе. Ты вынуждаешь другие подсистемы знать о понятии адрес, даже если в этом нет никакой необходимости.

ИНН и адрес это разные вещи. ИНН это просто идентификатор и он уникален для персоны. Адрес нет.
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
Тоже самое касается lazy load. Разговор сразу перешел как это плохо инжектить и все такое прочее. Но если смотреть на entity снаружи, то для потребителей ленивая загрузка сокрыта внутри entity. С точки зрения дизайна, lazy load является частью конкретного класса модели. Не важно как это сделано внутри, через декоратор или специальный ссылочный атрибут. Для потребителя это не имеет никакого значения.
Речь даже не про «плохо инжектить», а про persistence ignorance. Ты же несёшь какую-то чушь про репозитории (в DDD-смысле) для реализации lazy loading. Lazy loading — это чисто инфраструктура и как раз да, «для потребителя это не имеет никакого значения».

Так объясни, чем твой «сводный корень» (пишу именно так, потому что твой «корень» и aggregate root — разные понятия) отличается от UserService?
 
Сверху