如何使用DDD对StackOverflow网站进行建模



我以StackOverflow为例,因为很明显你知道那个网站,我的真实用例非常接近。

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

  • 有用户
  • 用户可以创建新问题
  • 用户可以创建这些问题的答案
  • 用户可以编辑自己的问题和答案
  • 如果用户的信誉超过1000,则用户可以编辑其他用户的问题(随机取该阈值)

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

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

现在我认为SO域有一个聚合根,叫做Question。这个问题将处理以下命令:

  • CreateAnswer
  • 编辑问题

问题是,当EditQuestion被触发时,Question AggregateRoot将如何决定是接受还是拒绝该命令?因为如果你还记得,如果你试图编辑另一个用户的问题,如果你有<1000声誉。

对我来说,Question AR维护所有用户声誉的列表,以便知道如何执行该命令,这似乎没有什么意义。

问题是,当我试图对我的域进行建模时,这个建模问题一次又一次地出现,最终我总是得到一个巨大的AggregateRoot

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

这个问题似乎意味着我们不应该把授权系统放在领域模型中。我同意这对于基于角色的身份验证这样的事情来说可能是实用的。然而,对我来说,"除非用户有足够的声誉,否则他们不能编辑"实际上是一条SO商业规则,那么它怎么可能在域外呢?

重要提示:在回答时,请将您视为业务专家。作为一个用户,您知道StackOverflow,并且可以自己猜测SO约束是什么。即使你对它们的看法是错误的,也没什么大不了的:只要为你错误的业务限制提出一个建议我就可以了!!!

这不是我第一次问这种问题,结果总是没有答案,只是无休止的讨论。我想知道的是,如果你必须建立这个网站,你将如何建模StackOverflow,重点是关于编辑最低信誉的商业规则。

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

你把这个问题很好地加上了"cqrs"。在EditQuestion处理程序中,我会使用域服务(从CQRS的角度来看的查询),它会检查某个使用是否具有所需的点,然后返回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);
    }
}

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

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

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

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

如果您将应用程序视为一组使用概念(这些概念本身封装了数据和业务约束)的用例,那么很容易确定哪个是应用程序服务、聚合、域服务等。

如果您想要立即一致性,则可以使用稍微不同的方法

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事件的投影,这些事件会随着时间的推移而被侦听和累积。

最新更新