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