Привет!
Пытаюсь красиво реализовать механизм отложенных событий в архитектуре с подходом DDD. Под отложенными я имею в виду те события, которые не инициируются непосредственно командами пользователя, а должны произойти позже. Как пример: имеется сайт, где у агрегата пользователя есть дата рождения. Мы хотим поздравить его в этот день, отослав письмо. Кроме того, в ограниченном контексте магазина у этого пользователя должны начислиться бонусы на счёт. Точкой начала алгоритма считаем момент, когда пользователь укажет дату рождения в профиле.
Размышляя над задачей, пришёл к выводу, что работу с временем лучше вынести в отдельный контекст Scheduler, который будет снаружи дёргаться кроном. Дальше несколько вариантов, как этот планировщик взаимодействует с другими контекстами.
1. В момент, когда пользователь указывает дату рождения, мы планируем конкретные команды, далее псевдокод
Scheduler->schedule(new SendHappyBirthdayEmail($email), $birthdate);
Scheduler->schedule(new IncreaseBonuses($user, $amount), $birthdate);
Когда наступит дата $birthdate, планировщик выполнит команды.
Какие плюсы и минусы вижу в такой реализации:
+ достаточно очевидно и просто
- планировщик знает больно много о внешнем мире
- состав команд запросто может поменяться за время, пока событие не наступило. Допустим, вместо емэйла мы теперь решили отсылать бумажную открытку. Получается, надо убрать запланированные команды отсылки емэйлов и добавить новые команды доставки открыток.
- во время выполнения команды может потребоваться дополнительный контекст, который не запихнуть в параметры команды. Например, необходимо учесть статус покупателя, прежде чем отослать открытку. А статус может измениться в любой момент и уж точно неизвестен на момент планирования команды.
- есть необходимость удалять ненужные события, например, если дата рождения изменилась (опечатка при регистрации, например)
- как по мне, так команды - это уровень приложения, в то время как Sсheduler - это доменный уровень. Получается доменный уровень знает о приложении, что неправильно.
2. Scheduler просто с какой то необходимой периодичностью шлёт одно событие, например TimeHasChanged. Остальные контексты слушают это событие и решают, как на него реагировать.
Плюсы и минусы:
+ планировщик ничего не знает о том, как реагируют на даты
+ легко адаптировать под изменения логики реакции на событие, потому как она (реакция) происходит именно в момент наступления события, нежели в момент его планирования.
+ нет необходимости удалять неверно запланированные события
- контексты, подписанные на это событие не понимают, как на него реагировать. Например, контексту пользователей надо каждый раз идти в репозиторий и смотреть, нет ли у кого в этот день дня рождения. А если какой то контекст ждёт наступления множества дат, то поиск нужной становится слишком накладной.
- контексты слишком много знают о датах, потому как сами оперируют ими.
3. Scheduler используется как "будильник"
$userId = 'j123jjkh3j2jh3j2h';
$key = 'user_birthdate:' : $userId;;
Scheduler->schedule($key, $birthdate);
В момент наступления даты, он рассылает событие TimeHasCome(), содержащий тот самый $key, который ему задали. То есть остальные контексты просто просят его "напомни мне, когда будет эта дата".
+ всё те же, что и в п.2, плюс контексту не надо заморачиваться с поиском по дате, он может легко искать по фрагменту ключа, в моём примере по $userId, вычленив его из $key.
+ контексты не оперируют датами, перекладывая это на планировщик. Вместо этого они ждут события, которое сами же запланировали.
- неверные события так же требуется удалять, если они не нужны, иначе возникнут ошибочные срабатывания. Но, тут можно это побороть удалением по маске ключа или по целому ключу $key.
Вот, такие решения пришли в голову. Если есть идеи или замечания по этому вопросу, прошу поделиться.