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;
}
}
А для чего брать "ученика" и все "классы" (что бы оповестить?) и что в данном контексте будет сводным корнем (aggregation root, корень агрегации)?1) Берется ученик и список всех классов
$gradeId = new Domain\ValueObject\GradeID;
$pupil->changeGrade($gradeId); // Возможно этого вполне достаточно?
$oldGrade = $grdeRepository->get();
$newGrade = $grdeRepository->get();
$oldGrade->removePupil($pupil);
$newGrade->addPupil($pupil);
$grdeRepository->save($oldGrade);
$grdeRepository->save($newGrade);
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;
}
}
Это сегодня отмечают день вольных интерпретаций? Просто праздник абсурда какой-то.Сводный корень .... это service layer in domain context.
Да, действительно меня этот момент с репозиториями смущал несколько. Сейчас подумал, по идее, единственным случаем явного вызова Repository::save($entity) наверное может быть только создание сущности (начало её жизненного цикла).И никакие репозы снаружи не нужны, потому что addPupil после add сам делает save
sub-domain или Bounded Context2) Предположим, что к системе с классами мы хотим добавить учет, скажем, комнат для проживания . Что, если после всего мы заходим управлять корпусами для проживания, которые содержат комнаты, в которых живут студенты? (будешь ли ты дублировать entities или воспользуешься aggregate root)
Мне кажется, это всё-же сущность. Дети приходят из детского сада и формируется первый "А" (жизненный цикл сущности начинается), далее ежегодно (и в течение года) изменяется состояние - меняется индекс, возможно происходят изменения в составе класса, формируются новые расписания и т.д. А с выпускным жизненный цикл сущности оканчивается и информация об nA попадает в архив.зы. Хотя очень хочется Grade в ValueObject засунуть
В том-то и дело, что lazy loading должен быть прозрачен и модели не должно быть известно lazy там или нет, пример я уже приводил: http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities1) Что такое lazy load и как оно реализуется (кто лезет в репо и когда, не entity ли? Так знает-ли entity о repo?
Я не могу сказать при чём тут aggregate root вообще. Для меня это метамодель, которая может быть полезна в классическом примере с Order и OrderLines, но мне трудно это применить в рассматриваемых примерах.2) Предположим, что к системе с классами мы хотим добавить учет, скажем, комнат для проживания . Что, если после всего мы заходим управлять корпусами для проживания, которые содержат комнаты, в которых живут студенты? (будешь ли ты дублировать entities или воспользуешься aggregate root).
Замечательно, 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.
Замечательно, а теперь откроем Фаулера, который особо на эту тему ничего не писал, но ты на него сам ссылаешься: http://martinfowler.com/bliki/DDD_Aggregate.htmlИ никакие репозы снаружи не нужны, потому что addPupil после add сам делает save, потому что это service layer in domain context.
Как так получается, что ты собираешься делать save какой-то одной части aggregate? Ты либо весь целиком его сохраняешь, либо нет, в противном случае ты можешь получить inconsistent state.Aggregates are the basic element of transfer of data storage - you request to load or save whole aggregates. Transactions should not cross aggregate boundaries.
Ты привел отличный пример не domain-driven дизайна. Ты сам понимаешь что здесь что-то не так, потому что тебе хочется чего-то иного.Вурдалак, whirlwind, можно я задам пару вопросов?
В теории все замечательно раскладывается на сводные корни... давайте на практике - по полочкам - покажите свое виденье проблемы.
Ubiquitous Language
Enity Grade - year, letter
Entity Pupil - internalID, name, Grade
Как это все разрулить?
зы. Хотя очень хочется Grade в ValueObject засунуть
еще как может, тот же адрес, написан у тебя в паспорте, это один из идентификаторов, пусть и не уникальный, но все же ... почему ИНН атрибут, а адрес нет?Может ли быть атрибутом pupil комната, в которой он живет? Нет. Может быть атрибутом pupil является его машина? Нет. Адрес? Нет.
Наверное всё от контекста зависит? В данном случае ИНН и адрес как раз будут скорее ValueObject, чем сущностнями (впрочем условия слишком общие, для того что бы решить однозначно "ху из ху"). А в описанном тобой примере выше сами бизнес правила так и простят сущность "класс" в качестве сводного корня для реализации манипуляций с подсущностями "учеников", на мой взгляд. "Ученик" не является просто абстрактным "учеником", а членом конкретного "класса" из которого его могут перевести и т.п.еще как может, тот же адрес, написан у тебя в паспорте, это один из идентификаторов, пусть и не уникальный, но все же ... почему ИНН атрибут, а адрес нет?
Ты путаешь связь и владение. Адрес не моя часть. Я могу прожить без адреса, а адрес может прожить без меня. Связывая меня с адресом через атрибут, ты делаешь эту связь известной всей системе. Ты вынуждаешь другие подсистемы знать о понятии адрес, даже если в этом нет никакой необходимости.еще как может, тот же адрес, написан у тебя в паспорте, это один из идентификаторов, пусть и не уникальный, но все же ... почему ИНН атрибут, а адрес нет?
Речь даже не про «плохо инжектить», а про persistence ignorance. Ты же несёшь какую-то чушь про репозитории (в DDD-смысле) для реализации lazy loading. Lazy loading — это чисто инфраструктура и как раз да, «для потребителя это не имеет никакого значения».Тоже самое касается lazy load. Разговор сразу перешел как это плохо инжектить и все такое прочее. Но если смотреть на entity снаружи, то для потребителей ленивая загрузка сокрыта внутри entity. С точки зрения дизайна, lazy load является частью конкретного класса модели. Не важно как это сделано внутри, через декоратор или специальный ссылочный атрибут. Для потребителя это не имеет никакого значения.