бизнес логика и валидация в модели

У меня проблема со следующей ситуацией. У меня есть объект 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, который бы обрабатывал эту логику?

Что пришло мне в голову:

  • проверка в сущности — это хорошо, потому что никто не может установить неправильную дату публикации в коде (например, другой член команды, который знал бы о ArticleService)
  • субъект все время находится в действительном состоянии
  • с другой стороны, мне не нравится сигнатура метода 🙂

0

Решение

«Пользователь может предложить, когда статья должна быть опубликована, если его нет
уже «онлайн». Потому что есть какая-то статья, он немного
ограничен в своем решении. Таким образом, 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, вы всегда получаете один и тот же ответ: «Это зависит от вашего домена». Поэтому вы должны выбрать подход, который лучше согласуется с доменом и может быть легко выражен в виде концепций домена, определенных вездесущим языком.

-1

Другие решения

Определенный вами класс Article предназначен для моделирования опубликованной или неопубликованной статьи (оба представления используют одну и ту же абстракцию).
Давайте немного подумаем о методе «updatePublishDate», который вы написали для класса Article. Должна ли уже опубликованная статья получить сообщение (вызов метода) «updatePublishDate»? Это странно: статья, которая уже опубликована, никогда не должна иметь в своем протоколе сообщение, открывающее для всех изменения, чтобы сообщить ей: «Публикуйте статью, измените дату публикации на…». Так что это дизайн запаха. Теперь, на первый взгляд, мы видим, что нам нужно одно представление для публикуемой статьи и другое представление для черновика статьи.
Таким образом, у вас будет два агрегата с репозиториями:

DraftArticle > DraftArticleRepository
Article > ArticleRepository

Всякий раз, когда вы хотите опубликовать статью, вы выбираете черновик статьи, создаете статью и перемещаете ее в репозиторий статей:

Publisher.publishArticle(anId) {
draftArticle = draftArticleRepository.get(andId);
article = new Article(draftArticle.text());
articleRepository.add(article);
}

Статья получает текущую системную дату как дату публикации. С другой стороны, черновик статьи не знает о датах публикации. Тем не менее, вы можете иметь dateToBePublished для черновика статьи и иметь фоновый поток для ежедневного запуска и публикации черновиков статей, соответствующих критериям времени.

Надеюсь, поможет.

0

По вопросам рекламы [email protected]