data-mapper или active-record

Вурдалак

Продвинутый новичок
Тогда тебе придётся что-то делать с именованными конструкторами: Article::begin(). Они возвращают инстанс сущности, но при этом сама сущность уже может содержать события.

Когда тебе понадобится передать вместе с событием ещё какие-то данные, то ты не сможешь это сделать.
 

WMix

герр M:)ller
Партнер клуба
ну можно еще типа call_user_function([Article, begin])
ещё какие-то данные
можно тоже, но изврат еще тот
call_user_function_array([Article, begin],$params)
что не хочется начинать (уже недостатки превосходят желаемое), еще подумать надо
 

WMix

герр M:)ller
Партнер клуба
Зачем тут call_user_function?
PHP:
// entity
return [[Article::class, begin]];

//service причем это можно еще раз обернуть и дальше вернуть в конструктор или откуда это началось
foreach( $order->cancel as $event){
    return is_array($event) && callable($event) ? call_user_function($event, $id):new $event($id);
}
 

Вурдалак

Продвинутый новичок
PHP:
// entity
return [[Article::class, begin]];

//service причем это можно еще раз обернуть и дальше вернуть в конструктор или откуда это началось
foreach( $order->cancel as $event){
    return is_array($event) && callable($event) ? call_user_function($event, $id):new $event($id);
}
У тебя есть способность превращать простую идею в невероятно сложную.
 

WMix

герр M:)ller
Партнер клуба

halva

Новичок
@Вурдалак, всё это выглядит симпатично, но у меня возник вопрос. В своих примерах ты порождаешь события предметной области, но обрабатываешь эти события за пределами агрегата, по сути это получается отложенные события. Разве не бывает событий, которые влияют на объекты в рамках агрегата? Если бывают, то было бы интересно увидеть примеры, как ты решаешь такие задачи.
 

Adelf

Administrator
Команда форума
Разве не бывает событий, которые влияют на объекты в рамках агрегата
агрегату дали выполнить действие. и он порождает события. если ему надо в это время что-то поменять внутри себя - он меняет. ему события для этого не нужны.
 

halva

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

Вурдалак

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

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

Разве не бывает событий, которые влияют на объекты в рамках агрегата?
В случае сложных aggregate root необходимость в каком-то внутреннем механизме событий действительно может появится. Например, есть Игра, внутри есть Игроки (вложенные сущности), и действие какого-то Игрока может привести к его проигрышу. Тогда, вероятно, нам захочется удалить этого игрока из Игры. То есть, на основе внутреннего поведения вложенной сущности Игрок мы хотим также сделать изменение в Игре. Технически, решение найти можно, просто все те подходы, которые мне приходили в голову, выглядят так себе. Я не хочу сейчас это подробно расписывать, поскольку красивых решений среди них нет, а на практике я таких aggregate root не встречал.
 

WMix

герр M:)ller
Партнер клуба
сущности изолированы от внешнего мира, поэтому чтобы изменить поведение сущности, нужно изменить её код.
ну вот еще и open/closed principle :)

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

Вурдалак

Продвинутый новичок
ну вот еще и open/closed principle
Это не имеет отношения к OCP.

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

WMix

герр M:)ller
Партнер клуба
@Вурдалак

изходя из твоего сообщения
«Агрегат» — это просто сущность, в большинстве случаев она достаточно небольшая и «плоская» (нет вложенных сущностей)
возникает вопрос: имеет ли смысл "позиции заказа" вырезать из "заказа"?

или оставить позиции в заказе, что подразумевает
PHP:
/**
 * не плоская сущность
 */
class Order{
  /**
   * OrderItem[]
   */
  private $items;
}
но простой язык
PHP:
$repo->getOrderById($order_id)->addItem( new OrderItem(...));
$repo->store($order);
где эта середина? это к event sourcing отношения не имеет.
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
я предложил бы Game и Player[] вместо Order и Item[], но в Item как отдельная сущность большого смысла не несет.
 

Вурдалак

Продвинутый новичок
Last edited: Yesterday at 4:50 PM
Вурдалак, Yesterday at 4:45 PM
В оригинальном посте что-то было из предметной области (что-то про склад), это меня и смутило.

изходя из твоего сообщения
возникает вопрос: имеет ли смысл "позиции заказа" вырезать из "заказа"?
Нет, конечно, я говорил про большинство случаев, твой «заказ» просто к ним не относится.

где эта середина?
В основном, всё упирается в вычислительные ресурсы. Изменения в AR происходят через эксклюзивную блокировку и всё содержимое AR находится в памяти. Следовательно, если будет много параллельных изменений или если будет слишком много данных, то это будет неудачный AR. Например, очевидно, что если ты пилишь issue tracker а-ля Jira/redmine/trac, то делать aggregate root Project со списком всех Issue в нём — это не круто. Этот список Issue потенциально бесконечный. Это достаточно скоро просто перестанет работать. Но это вряд ли касается заказа, здесь скорее всего будет мало параллельных изменений, если они вообще будут; позиций заказа тоже вряд ли будет так много, что у тебя начнутся проблемы с нехваткой памяти.
 
Последнее редактирование:

Adelf

Administrator
Команда форума
Например, очевидно, что если ты пилишь issue tracker а-ля Jira/redmine/trac, то делать aggregate root Project со списком всех Issue в нём — это не круто. Этот список Issue потенциально бесконечный. Это достаточно скоро просто перестанет работать.
По моему, тут немного другая причина. Почти все операции будут с issues. И агрегат Project стал бы "завистливым" классом, в основном вызывающим методы у issue. Агрегат же Заказ - вполне себе важная единица и операции в основном идут с ним.
Вероятно, то же самое можно сказать и про твой агрегат Игра(там операции всегда идут с игроками, полем, и т.д.). Но тут я менее уверен.
 

Вурдалак

Продвинутый новичок
Почти все операции будут с issues. И агрегат Project стал бы "завистливым" классом, в основном вызывающим методы у issue. Агрегат же Заказ - вполне себе важная единица и операции в основном идут с ним.
Вероятно, то же самое можно сказать и про твой агрегат Игра(там операции всегда идут с игроками, полем, и т.д.). Но тут я менее уверен.
Рассуждая про технические ограничения, я забыл упомянуть самый важный момент, что в aggregate root должны быть только те данные, что необходимы для транзакционных изменений, т.е. для игры это очевидно: некоторые действия затрагивают сразу всех игроков одновременно (например, все платят какому-то игроку; или какой-то игрок проигрывает после действия другого игрока), поэтому тут требуется обеспечить транзакционную целостность действий в игре. По крайней мере, это справедливо для большинства настольных и карточных игр.

И на самом деле с Project и Issues здесь тоже можно придумать правила, которые требовали бы транзакционную целостность. К примеру, ты не можешь закрыть задачу, если задача блокируется другой задачей, которая ещё не закрыта (немного спорная логика, но она имеет место быть в некоторых issue tracker'ах). То есть, в рамках Project вроде бы существует инвариант «задача может быть закрытой тогда и только тогда, когда не существует открытых задач, блокирующих первую». Но мне кажется достаточно очевидно, что это всё равно не стоит того, чтобы делать подобный aggregate root, потому что это технически проблематично. Поэтому тут нужны альтернативные варианты. Скорее всего даже нет ничего страшного в том, что может произойти ситуация, когда задача-таки закроется, даже если будет открытая блокирующая (race condition).

А вот твой аргумент с «завистью» я не понял.
 

Вурдалак

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

завистливый - это из Рефакторинга Фаулера. https://refactoring.guru/ru/smells/feature-envy
Тут речь про поля/данные, как если бы Project напрямую их менял у Issue, т.е. речь про нарушение инкапсуляции. Но ведь Project совсем не обязан менять поля напрямую. Как это относится к данному случаю?
 
Сверху