如何使用 DDD 为 StackOverflow 网站建模

How to model the StackOverflow website with DDD

我以 Whosebug 为例,因为显然您知道该网站,而我的实际用例非常接近。

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

最后一条大胆的规则对我来说很重要。

我对 AggregateRoot 的理解是它应该包含用于决定接受或拒绝命令的状态,并且它不应该查询数据库来这样做。它保证了应用程序的一致性。它应该只监听它发出的事件以更新它的状态。

现在我认为 SO 域有一个称为问题的聚合根。 该问题将处理如下命令:

问题是,当 EditQuestion 被触发时,Question AggregateRoot 将如何决定接受或拒绝该命令?因为如果您还记得的话,如果您的信誉 < 1000,那么如果您尝试编辑另一个用户的问题,该命令应该被拒绝。

问题 AR 维护所有用户信誉的列表,以便能够知道如何对该命令采取行动,这对我来说似乎没有意义。

问题是,在尝试对我的域进行建模时,这个建模问题一遍又一遍地出现,而且我总是以一个庞大的 AggregateRoot 结束

有人可以告诉我我缺少什么并帮助我解决这个问题吗?谢谢

这个question好像是说我们不应该把授权系统放在领域模型里面。我同意这对于基于角色的身份验证之类的事情可能很实用。但是,对我来说 "users can't edit unless they have enough reputation" 确实是一个 SO 业务规则,那么它怎么可能在域之外呢?

重要提示:回答时,请认为您是业务专家。您作为用户了解 Whosebug,并且可以自己猜出 SO 约束是什么。即使您对它们的看法是错误的,也没什么大不了的:只需为您错误的业务限制提出建议我就可以接受!!!

这样的问题我不是第一次问了,每次都是没有答案,就是没完没了的讨论。我想知道的是,如果您必须构建此站点,您将如何为 Whosebug 建模,重点关注有关要编辑的最低信誉的业务规则。

好吧,IMO 的事情非常简单(仅在这个 SO 场景中)。这就是 I 的做法(显然,其他开发者可能有不同的方法):

您用 "cqrs" 很好地标记了问题。在 EditQuestion 处理程序中,我将使用域服务(从 CQRS 的角度来看的查询),它将检查某个用途是否具有所需的点数,然后 return一个true/false。像这样的东西(或多或少的伪代码)

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);
    }
}

如果查询 return 为真,则处理程序将对问题实体执行更改,即 question.ChangeText() 或 smth(我认为 SO 采用事件源方法)。

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

我相信你会问我:"What if the read model used by the domain query is behind the command model?"。在这种情况下,它无关紧要,延迟最多可能以秒为单位。这里的主要内容是:业务规则不是问题聚合的一部分,因此它不关心立即一致。

另一件事是,用户代表始终是与问题不同的概念,因此处理代表永远不应成为问题聚合的一部分。但它是应用服务的一部分。

如果您将应用程序视为一组使用概念(它们本身封装数据和业务约束)做事的用例,则很容易识别应用程序服务、聚合、域服务等。

如果您想要立即保持一致,则采用略有不同的方法

public class UserCommandHandler 
{
  public void Handle(EditQuestion command)
  {
    // (start transaction)
    var user = userRepository.Get(command.UserId);
    if (user.Reputation < 1000) 
      // reject command here
    var edit = user.EditedQuestion(...); // or just "new QuestionEdit(...)"
    questionEditRepository.Add(edit);
    // (commit, saving the QuestionEdited event)
  }
}

由于我们使用的是 CQRS,因此反映在页面上的问题状态不会包含在 Question 聚合中,而是包含在所收听的一系列 QuestionEdited 事件的投影中并随时间累积。