我最近开始玩 DDD。今天,我在将验证逻辑放入我的应用程序时遇到了问题。我不确定我应该选择哪一层。我在 Internet 上搜索,但找不到解决我问题的统一解决方案。
让我们考虑以下示例。用户实体由 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 block 中并监听验证类型的异常,但主要问题是它会向我提供有关单个错误 的信息,直到出现第一个异常。 这是否意味着我必须在 Controller 级别复制我的验证逻辑(即使用 zend-inputfilter - 我正在使用 ZF2/3)?听起来不一致...
提前谢谢你。
最佳答案
我会尝试一个一个地回答您的问题,另外在这里和那里给出我的两分钱以及我将如何解决这些问题。
Non business logic related validation is performed by ValueObjects
实际上 ValueObjects 代表您业务领域的概念,因此这些验证实际上也是业务逻辑验证。
Entity - check it while creating User entity, in the constructor?
是的,在我看来,您应该尝试将这种行为尽可能深入地添加到聚合中。如果将其放入命令或命令处理程序中,则会失去内聚性,并且业务逻辑会泄漏到应用程序层中。我什至会走得更远。问问自己,您的模型中是否存在未明确表示的隐藏概念。在您的情况下,AdultUser 和 UnderagedUser(它们可以都实现 UserInterface)实际上 有不同的行为。在这些情况下,我总是努力明确地对此进行建模。
Like email uniqueness. I read about the Specification pattern. Is it ok, if I use it directly in Command Handler?
如果您希望能够将复杂查询与逻辑运算符结合起来(尤其是对于读取模型),那么规范模式非常有用。就您而言,我认为这是一种矫枉过正。在 UserRepositoryInterface 中添加一个简单的 containsUserForEmail($emailValueObject) 方法并从用例中调用它就可以了。
<?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 如何构建有效的负载。
关于php - DDD、PHP - 在哪里执行验证?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38770935/
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我遵循了教程http://gettingstartedwithchef.com/,第1章。我的运行list是"run_list":["recipe[apt]","recipe[phpap]"]我的phpapRecipe默认Recipeinclude_recipe"apache2"include_recipe"build-essential"include_recipe"openssl"include_recipe"mysql::client"include_recipe"mysql::server"include_recipe"php"include_recipe"php::modul
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:
当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested
我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下