Php 发送给处理程序的命令,以在DDD和CQR中聚合根到存储库的流

Php 发送给处理程序的命令,以在DDD和CQR中聚合根到存储库的流,php,domain-driven-design,cqrs,ddd-repositories,Php,Domain Driven Design,Cqrs,Ddd Repositories,在学习DDD和CQR时,我需要澄清以下几点。在购物环境中,我有一个客户,我相信它是我的聚合根,我想实现更改客户名称的简单用例 这是我的实现,据我所知,我使用DDD/CQRS实现了这一点 我担心的是 对于验证,该命令是否还应该验证输入以使其与值对象一致,还是可以将其留给处理程序 我的整个流程是否正常,或者我是否严重遗漏了某个地方 如果这个流程是正确的,我看到Customer Aggregate root将是一个巨大的类,包含许多函数,比如changeName、changeAddress、chan

在学习DDD和CQR时,我需要澄清以下几点。在购物环境中,我有一个客户,我相信它是我的聚合根,我想实现更改客户名称的简单用例

这是我的实现,据我所知,我使用DDD/CQRS实现了这一点

我担心的是

  • 对于验证,该命令是否还应该验证输入以使其与值对象一致,还是可以将其留给处理程序
  • 我的整个流程是否正常,或者我是否严重遗漏了某个地方
  • 如果这个流程是正确的,我看到Customer Aggregate root将是一个巨大的类,包含许多函数,比如changeName、changeAddress、changePhoneNumber、deleteSavedPaymentMethod等等。 它将成为一个god类,这对我来说似乎有点奇怪,它实际上是DDD聚合根实现的正确方式吗
//价值对象

类客户名称
{
私有字符串$name;
公共函数构造(字符串$name)
{
if(空($name)){
抛出新的InvalidNameException();
}
$this->name=$name;
}
}
//聚合根

class客户
{
私有UUID$id;
私人客户名称$name;
公共函数构造(UUID$id,CustomerName$name)
{
$this->id=$id;
$this->name=$name;
}
公共函数changeName(CustomerName$oldName,CustomerName$newName){
如果($oldName!=$this->name){
抛出新的不一致性异常('可能名称已更改');
}
$this->name=$newName;
}
}
//命令

    class ChangeNameCommand
    {
      private string $id;
      private string $oldName;
      private string $newName;
    
      public function __construct(string $id, string $oldName, string $newName)
      {
        if(empty($id)){ // only check for non empty string
          throw new InvalidIDException();
        }
        $this->id = $id;
        $this->oldName = $oldName;
        $this->newName = $newName;
      }
    
      public function getNewName(): string
      {
        return $this->newName; // alternately I could return new CustomerName($this->newName)] ?
      }
    
      public function getOldName(): string
      {
        return $this->oldName;
      }
    
      public function getID(): string
      {
        return $this->id;
      }
    }
    
//处理者

类ChangeNameHandler
{
私人EventBus$eBus;
公共函数构造(EventBus$bus)
{
$this->eBus=$bus;
}
公共函数句柄(ChangeNameCommand$nameCommand){
试一试{
//用于验证的值对象
$newName=newCustomerName($nameCommand->getNewName());
$oldName=newcustomername($namecondmand->getOldName());
$customerTable=新的customerTable();
$customerRepo=新customerRepo($customerTable);
$id=新的UUID($nameCommand->id());
$customer=$customerRepo->find($id);
$customer->changeName($oldName,$newName);
$customerRepo->add($customer);
$event=新客户机化($id);
$this->eBus->dispatch($event);
}捕获(例外$e){
$event=new customerMechangFailed($nameCommand,$e);
$this->eBus->dispatch($event);
}
}
}
//控制器

类控制器
{
公共职能变更($请求)
{
$cmd=newchangenamecommand($request->id,$request->old\u name,$request->new\u name);
$eventBus=新的eventBus();
$handler=newchangenamehandler($eventBus);
$handler->handle($cmd);
}
}
为简洁起见,跳过了一些类,如UUID、Repo等

该命令是否也应该验证输入以使其与值对象一致,还是可以将其留给处理程序

“可以吗”——当然可以;DDD警察不会来抓你的

也就是说,从长远来看,您可能会更好地设计代码,使不同的概念是显式的,而不是隐式的

例如:

$cmd = new ChangeNameCommand($request->id, $request->old_name, $request->new_name);
这告诉我,
ChangeNameCommand
是HTTP API模式的内存表示,也就是说,它是您与消费者之间契约的表示。客户合同和域模型不会因为相同的原因而更改,因此在代码中明确区分这两个方面可能是明智的(即使底层信息是“相同的”)

验证http请求中出现的值是否确实满足客户模式的要求应该发生在控制器附近,而不是模型附近。毕竟,如果负载不满足模式(例如:),则由控制器负责返回客户端错误

验证输入是否令人满意后,您可以将信息(如果需要)从信息的HTTP表示形式转换为域模型的表示形式。这应该总是起作用[tm]——如果不起作用,则表明您在某个地方存在需求差距

翻译发生在哪里并不特别重要;但是,如果您设想有多个不同的模式,或接受此信息的不同接口(命令行应用程序、队列读取服务等),那么翻译代码可能属于该接口,而不是域模型

我的整个流程是否正常,或者我是否严重遗漏了某个地方

您的组合选择看起来很可疑,特别是EventBus的生存期属于Controller::change,而CustomerRepo的生存期属于ChangeNameHander::handle

它将成为一个神类

那就分手吧。看

事实是:仅仅存储外部世界提供的信息副本的数据模型并不特别有趣。真正值得投入工作的好的方面是根据外部世界提供的信息决定事情的状态机

如果一个状态机不使用一条信息来做出决策,那么该信息属于“其他地方”——要么是另一个状态机,要么是数据库或缓存等不太复杂的地方