У меня проблема со следующей ситуацией. У меня есть объект Article:
class Article {
private $publishDate;
public function updatePublishDate(DateTime $date = null) {
$this->publishDate = $date;
}
}
Я хотел бы применить некоторые бизнес-правила для обновления даты, такие как:
— обновлять дату только тогда, когда она еще не опубликована
— отказаться от установки даты в прошлом (должна быть действительная дата публикации)
Поскольку есть некоторая логика в создании даты публикации, я хотел бы иметь отдельный класс для этого, потому что я знаю, что это изменится:
class PublishService {
public function generatePublishDate() {
return new DateTime('tomorrow');
}
}
Вопрос: где должна быть проверка? Должен ли я иметь подтверждение в организации:
...
public function updatePublishDate(DateTime $date, PublishService $service) {
if ($date
&& $this->datePublish > new DateTime
&& $date >= $service->generatePublishDate()) {
$this->datePublish = $date;
} else {
throw new InvalidArgumentException('Something wrong with the date...');
}
}
...
Или я должен создать отдельный ArticleService, который бы обрабатывал эту логику?
Что пришло мне в голову:
«Пользователь может предложить, когда статья должна быть опубликована, если его нет
уже «онлайн». Потому что есть какая-то статья, он немного
ограничен в своем решении. Таким образом, PublishService рассчитывает в первую очередь
Возможна дата публикации. И иметь действующее лицо (от бизнеса
логическая точка зрения) мне нужно проверить, предложена ли дата пользователем
больше или равно дате, предоставленной PublishService. «- kodlik
Ну, во-первых, DDD — это отражение домена в коде. Посмотрите, как вы описали вариант использования для меня (жирным шрифтом выше) и как он на самом деле отражается в вашем коде:
public function updatePublishDate(DateTime $date, PublishService $service)
Из того, что я прочитал выше, я понимаю, что предложение даты публикации не обязательно предполагает публикацию статьи сразу.
Как насчет proposePublishingDate
скорее, чем updatePublishDate
?
Теперь нет ничего плохого в том, чтобы внедрить сервис в сущность на метод уровень, но вы должны следовать Принцип разделения интерфейса (ISP) и быть более явным о зависимости. Поэтому, а не в зависимости от PublishService
, вы можете зависеть от PublishingDateGenerator
интерфейс, который реализует nextPublishDate
метод.
Я хочу, чтобы вы сосредоточились больше на принципе, чем на именах, которые я выбрал здесь, но не забывайте, что они являются частью вашей модели предметной области и должны быть частью вашего вездесущего языка.
Из того, что я вижу, вы, кажется, извлекаете дату из вашего PublishService
только для того, чтобы сравнить его с датой, предложенной пользователем.
Вместо того, чтобы спрашивать PublishService
для даты и используйте дату для выполнения некоторой бизнес-логики, почему бы вам не сделать это правило явным понятием вашего домена, имея PublishingDatePolicy
вместо?
Ниже приведен псевдокод:
public proposePublishingDate(DateTime date, PublishingDatePolicy policy) {
if (this.isPublished()) throw ...;
if (!policy.isSatisfiedBy(date)) throw ...;
this.proposedPublishingDate = date;
}
Примечание. Политики обсуждаются в книге Эванса и являются средством явного определения бизнес-правил. Они в значительной степени совпадают со стратегиями в паттерне стратегий.
Вы можете увидеть это как нарушение Принцип единой ответственности (SRP), но я думаю, что не всегда стоит извлекать простую логику из сущности, с которой она тесно связана. Однако, если вы ожидаете, что правило будет меняться очень часто, это может быть хорошей идеей …
Трудно сказать, что лучше, и всякий раз, когда вы задаете вопрос DDD, вы всегда получаете один и тот же ответ: «Это зависит от вашего домена». Поэтому вы должны выбрать подход, который лучше согласуется с доменом и может быть легко выражен в виде концепций домена, определенных вездесущим языком.
Определенный вами класс Article предназначен для моделирования опубликованной или неопубликованной статьи (оба представления используют одну и ту же абстракцию).
Давайте немного подумаем о методе «updatePublishDate», который вы написали для класса Article. Должна ли уже опубликованная статья получить сообщение (вызов метода) «updatePublishDate»? Это странно: статья, которая уже опубликована, никогда не должна иметь в своем протоколе сообщение, открывающее для всех изменения, чтобы сообщить ей: «Публикуйте статью, измените дату публикации на…». Так что это дизайн запаха. Теперь, на первый взгляд, мы видим, что нам нужно одно представление для публикуемой статьи и другое представление для черновика статьи.
Таким образом, у вас будет два агрегата с репозиториями:
DraftArticle > DraftArticleRepository
Article > ArticleRepository
Всякий раз, когда вы хотите опубликовать статью, вы выбираете черновик статьи, создаете статью и перемещаете ее в репозиторий статей:
Publisher.publishArticle(anId) {
draftArticle = draftArticleRepository.get(andId);
article = new Article(draftArticle.text());
articleRepository.add(article);
}
Статья получает текущую системную дату как дату публикации. С другой стороны, черновик статьи не знает о датах публикации. Тем не менее, вы можете иметь dateToBePublished для черновика статьи и иметь фоновый поток для ежедневного запуска и публикации черновиков статей, соответствующих критериям времени.
Надеюсь, поможет.