让我们考虑以下示例.用户实体由ValueObjects表示,例如id(UUID),年龄和电子邮件地址.
final class User { /** * @var \UserId */ private $userId; /** * @var \DateTimeImmutable */ private $dateOfBirth; /** * @var \EmailAddress */ private $emailAddress; /** * User constructor. * @param UserId $userId * @param DateTimeImmutable $dateOfBirth * @param EmailAddress $emailAddress */ public function __construct(UserId $userId, DateTimeImmutable $dateOfBirth, EmailAddress $emailAddress) { $this->userId = $userId; $this->dateOfBirth = $dateOfBirth; $this->emailAddress = $emailAddress; } }
非业务逻辑相关的验证由ValueObjects执行.这没关系.
我在放置业务逻辑规则验证方面遇到了麻烦.
如果,假设我们需要让用户只有18岁才能拥有自己的电子邮件地址呢?
我们必须检查今天的年龄,如果不合适则抛出异常.
我应该把它放在哪里?
>实体 – 在构造函数中创建用户实体时检查它?
>命令 – 在执行插入/更新/任何命令时检查它?我在我的项目中使用战术家,所以它应该是一份工作
>命令
>命令处理程序
在哪里放置负责检查存储库数据的验证器?
像电子邮件的唯一性.我读到了规范模式.如果我直接在Command Handler中使用它,可以吗?
最后但并非最不重要.
如何将其与UI验证集成?
我上面描述的所有内容都是关于域级别的验证.但是让我们考虑从REST服务器处理程序执行命令.我的REST API客户端希望我返回有关输入数据错误时出错的完整信息.我想返回一个包含错误描述的字段列表.
我实际上可以在try块中包含所有命令准备并监听验证类型异常,但主要问题是它会在第一个异常之前向我提供有关单个错误的信息.
这是否意味着,我必须在控制器级复制我的验证逻辑(即使用zend-inputfilter – 我使用的是ZF2 / 3)?这听起来不存在……
先感谢您.
我将逐一回答你的问题,并在这里和那里给我两分钱以及如何解决问题.Non business logic related validation is performed by ValueObjects
实际上,ValueObjects代表业务领域的概念,因此这些验证实际上也是业务逻辑验证.
Entity – check it while creating User entity, in the constructor?
是的,在我看来,你应该尝试尽可能地在Aggregates中添加这种行为.如果将其放入命令或命令处理程序中,则会导致内聚性和业务逻辑泄漏到应用程序层中.我甚至会走得更远.问自己一个问题,即模型中是否存在未明确的隐藏概念.在你的情况下,它是一个AdultUser和一个UnderagedUser(它们都可以实现UserInterface)实际上有不同的行为.在这些情况下,我总是努力明确地对此进行建模.
Like email uniqueness. I read about the Specification pattern. Is it ok, if I use it directly in Command Handler?
如果您希望能够将复杂查询与逻辑运算符组合在一起(特别是对于读取模型),则规范模式很好.在你的情况下,我认为这是一个矫枉过正.将一个简单的containsUserForEmail($emailValueObject)方法添加到UserRepositoryInterface并从Use Case中调用它很好.
<?php $userRepository ->containsUserForEmail($emailValueObject) ->hasOrThrow(new EmailIsAlreadyRegistered($emailValueObject));
How to integrate it with UI validation?
首先,已经应该对相关字段进行客户端验证.以正确的方式轻松使用您的系统,并且难以以错误的方式使用它.
当然还需要服务器端验证.我们目前使用模式验证方法,其中我们有一个中央模式注册表,我们从中获取给定有效负载的模式,然后可以针对该JSON模式验证JSON有效负载.如果失败,我们返回一个序列化的ValidationErrors对象.我们还通过Content-Type:application / json告诉客户端; profile = https://some.schema.url/v1/user# header如何构建有效的有效负载.
您可以在CQRS架构here和here之上找到一些关于如何构建RESTful API的好文章.