模型中的业务逻辑和验证

时间:2014-11-28 10:22:55

标签: php validation oop model domain-driven-design

我遇到以下情况的问题。我有一个文章实体:

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)
  • 实体始终处于有效状态
  • 另一方面,我不喜欢方法签名: - )

2 个答案:

答案 0 :(得分:0)

您定义的Article类旨在为已发布或未发布的文章建模(两种表示都使用相同的抽象)。 让我们稍微考虑一下方法" updatePublishDate"你为Article课写的。如果已发布的文章收到消息(方法调用)" updatePublishDate"?这很奇怪:一篇已经发布的文章在其协议中永远不应该有一条消息,这个消息为每个人打开,告诉它:#34; ey发表文章,将你的出版日期改为......"。所以这是一种气味设计。现在乍一看,我们可以看到我们需要一个代表发表一篇文章,另一个代表一篇正在撰写的文章。 因此,您将拥有两个带有回购的聚合:

DraftArticle > DraftArticleRepository
Article > ArticleRepository

每当您想要发表文章时,您都会选择一篇文章草稿,创建一篇文章并将其移至文章存储库:

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

本文将当前系统日期作为发布日期。另一方面,条款草案不了解出版日期。但是,您可以在草稿文章中使用dateToBePublished,并且每天运行一个后台线程并发布符合时间条件的草稿文章。

希望它有所帮助。

答案 1 :(得分:-1)

  

" 用户可以在文章发布时提出,如果不是   已经"在线"。因为有一些文章规划,他有点   他的决定有限。所以PublishService首先计算   可能的发布日期。并拥有一个有效的实体(来自企业   逻辑观点)我需要检查用户提出的日期   大于或等于PublishService提供的日期。" - kodlik

一个更好的名字?

嗯,首先,DDD就是在代码中反映域名。看看你如何向我描述用例(以粗体显示)以及它在代码中的实际反映方式:

public function updatePublishDate(DateTime $date, PublishService $service)

根据我上面的内容,我了解提议发布日期并不一定涉及立即发布文章。

proposePublishingDate而不是updatePublishDate呢?

注入服务

现在,在方法级别将服务注入实体并没有什么不妥,但您应该遵循Interface Segregation Principle (ISP)并更明确地了解相关性。因此,您可以依赖于实现PublishService方法的PublishingDateGenerator接口,而不是依赖nextPublishDate

我希望您更多地关注原则,而不是我在这里选择的名称,但不要忘记这些是您的域模型的一部分,应该是您无处不在的语言的一部分。

Tell Don't Ask原则

从我所看到的情况来看,您似乎只是从PublishService中提取日期,以便将其与用户建议的日期进行比较。

与其向PublishService询问日期并使用日期来执行某些业务逻辑,为什么不通过PublishingDatePolicy而使该规则成为您域名的明确概念?

以下是伪代码:

public proposePublishingDate(DateTime date, PublishingDatePolicy policy) {
    if (this.isPublished()) throw ...;
    if (!policy.isSatisfiedBy(date)) throw ...;

    this.proposedPublishingDate = date;
}

注意:Evans一书中讨论了策略,这是明确规定业务规则的一种方法。它们与战略模式中的策略几乎相同。

嵌入实体的逻辑

您可能会将此视为违反Single Responsibility Principle (SRP),但我认为从与其密切相关的实体中提取简单逻辑并不总是值得的。但是,如果您希望规则经常更改,那么这可能是一个好主意......

结论

很难说什么是最好的,每当你提出DDD问题时,你总能得到相同的答案:"这取决于你的域名。"。因此,您应该选择更好地与域对齐的方法,并且可以很容易地表达为无处不在的语言定义的域概念。