Domain driven design 如何用DDD建模StackOverflow网站

Domain driven design 如何用DDD建模StackOverflow网站,domain-driven-design,cqrs,event-sourcing,Domain Driven Design,Cqrs,Event Sourcing,我举StackOverflow的例子,因为很明显你知道那个网站,我真正的用例非常接近 让我们想象一个简化的So域描述: 有用户 用户可以创建新问题 用户可以创建这些问题的答案 用户可以编辑自己的问题和答案 如果其他用户的声誉超过1000,则用户可以编辑其他用户的问题(随机选取该阈值) 最后一条大胆的规则对我来说很重要 我对AggregateRoot的理解是,它应该包含决定接受或拒绝命令的状态,而不应该查询数据库来执行此操作。它保证了应用程序的一致性。它应该只监听它发出的事件来更新它的状态 现

我举StackOverflow的例子,因为很明显你知道那个网站,我真正的用例非常接近

让我们想象一个简化的So域描述:

  • 有用户
  • 用户可以创建新问题
  • 用户可以创建这些问题的答案
  • 用户可以编辑自己的问题和答案
  • 如果其他用户的声誉超过1000,则用户可以编辑其他用户的问题(随机选取该阈值)
最后一条大胆的规则对我来说很重要

我对AggregateRoot的理解是,它应该包含决定接受或拒绝命令的状态,而不应该查询数据库来执行此操作。它保证了应用程序的一致性。它应该只监听它发出的事件来更新它的状态

现在我想的是,SO域有一个聚合根,名为Question。 然后,该问题将处理以下命令:

  • CreateAnswer
  • 编辑问题
问题是,当EditQuestion被激发时,问题聚合器如何决定接受还是拒绝该命令?因为如果您记得,如果您试图编辑另一个用户的问题(如果您的声誉<1000),那么该命令应该被拒绝

对于我来说,问题AR维护一个所有用户声誉的列表,以便能够知道如何执行该命令,这似乎没有意义

问题是,当我尝试为我的领域建模时,我会一次又一次地遇到这个建模问题,最终我总是得到一个大的胖聚合对象

有人能告诉我我遗漏了什么并帮我解决这个问题吗?谢谢

这似乎意味着我们不应该将授权系统放在域模型中。我同意这可能适用于基于角色的身份验证。然而,对我来说,“除非有足够的声誉,否则用户不能编辑”真的是一个非常商业化的规则,那么它怎么可能在域之外呢

重要的是:当回答时,请考虑你是业务专家<强>。作为一个用户,您知道StackOverflow,并且可以自己猜测SO约束是什么。即使你错了,也没什么大不了的:只要为你错误的业务限制提出一个建议,我同意


这不是我第一次问这样的问题,结果总是没有答案,只是没完没了的讨论。我想知道的是,如果你必须建立这个网站,你将如何对StackOverflow进行建模,重点是关于要编辑的最低声誉的业务规则。

嗯,在我看来,事情非常简单(仅在这个SO场景中)。这就是I的方法(显然,其他开发人员可能有不同的方法):

你用“CQR”很好地标记了这个问题。在EditQuestion处理程序中,我将使用一个域服务(从CQRS角度的查询),它将检查某个使用是否具有所需的点,然后返回true/false。类似这样的内容(或多或少是伪代码)

如果查询返回true,那么处理程序将对问题实体执行更改,即Question.ChangeText()或smth(我认为它采用事件源方法)

这里您看到的是一个简单的概念“问题”用例,它的命令行为“编辑”,以及指示谁可以做什么的业务规则。问题是,1000 rep规则从来都不是问题概念定义的一部分,因此它不属于该集合,即问题的编辑方式。然而,它是用例本身和应用程序服务的一部分

我相信您会问我:“如果域查询使用的读取模型在命令模型后面会怎么样?”。在这种情况下,它无关紧要,延迟可能最多以秒为单位。这里最重要的一点是:业务规则不是问题集合的一部分,因此它不关心是否立即一致

另一件事是,用户代表总是一个不同于问题的概念,因此处理代表永远不应该是问题集合的一部分。但它是应用程序服务的一部分


如果您将应用程序视为一组使用概念(它们本身封装了数据和业务约束)进行操作的用例,那么很容易确定哪一个是应用程序服务、聚合、域服务等。如果您希望立即实现一致性,那么这是一种稍微不同的方法

公共类UserCommandHandler
{
公共无效句柄(EditQuestion命令)
{
//(启动事务)
var user=userRepository.Get(command.UserId);
如果(用户信誉<1000)
//此处拒绝命令
var edit=user.EditedQuestion(…);//或仅“new QuestionEdit(…)”
questionEditRepository.Add(编辑);
//(提交,保存已编辑事件)
}
}

由于我们使用的是CQR,页面上反映的问题状态不会包含在
问题
聚合中,而是一系列
问题编辑的事件的投影,这些事件会随着时间的推移被倾听和累积。

你不能总是依赖于强一致性。如果用户在编辑问题的同一秒钟内达到1000,但却失去了50个声誉,这真的很重要吗?它究竟对业务产生了什么样的影响?归根结底是AR规模,但总体规模越大,竞争就越激烈。正如plalx正确地说的那样,如果代表减少,这真的很重要吗?因此,请检查问题编辑方法(传入完整的用户对象)中的user.Reputation属性。如果您对此非常担心,那么可以使用saga将edit commit作为一个外部事务来实现,该事务将完成或回滚并返回到某个已知状态,并通知相关人员parties@g18c甚至不确定这将如何使用最终一致性建模?你愿意吗
public class CanUserEditQuestionService
{
    //constructor with deps\\

    public bool Handle(CanUserEditQuestion input)
    {
       //query the read model, maybe a query object to get us the rep of the user
      var rep=getReputation.Get(input.UserId);

      //we can have a dependency here which tell us the number of points required for a specific permission

     return(rep>=1000);
    }
}