Architecture 如何在DDD/CQRS/EventSourced项目中对投票/类似系统进行建模?

Architecture 如何在DDD/CQRS/EventSourced项目中对投票/类似系统进行建模?,architecture,domain-driven-design,cqrs,event-sourcing,Architecture,Domain Driven Design,Cqrs,Event Sourcing,以下是我的域名的简要说明: 我写的文章基本上和任何文章都一样(标题、摘要和正文) 我需要允许对我的文章进行投票,投票将由匿名用户进行(无需注册,但会话将存储投票,请不要关注此问题) 在这个域中,Article是我的聚合根 我无法找到符合以下要求的投票模型: 投票可以是我喜欢的,也可以是我不喜欢的,它应该是可变的(可以随时间改变,甚至可以取消) 具有关联会话的来宾用户只能对每篇文章投一票 那么,投票本身应该是聚合的吗 差不多 集体投票{ 公共函数强制转换(ArticleId、GuestSessi

以下是我的域名的简要说明:

我写的文章基本上和任何文章都一样(标题、摘要和正文)

我需要允许对我的文章进行投票,投票将由匿名用户进行(无需注册,但会话将存储投票,请不要关注此问题)

在这个域中,Article是我的聚合根

我无法找到符合以下要求的投票模型:

投票可以是我喜欢的,也可以是我不喜欢的,它应该是可变的(可以随时间改变,甚至可以取消)

具有关联会话的来宾用户只能对每篇文章投一票

那么,投票本身应该是聚合的吗

差不多


集体投票{
公共函数强制转换(ArticleId、GuestSessionToken令牌、VoteValue);
}

但在这种情况下,我应该如何检查单一性?使用最终的一致性(这似乎还可以,因为我没有一些复制品,因为它们很少)

因为如果我将投票方法添加到我的文章聚合中,我将不得不重播每个投票的历史记录,这听起来相当慢(考虑到每个文章我可以有10万张投票)

我知道在设计DDD方式时不应该考虑性能和优化,但在这里,这是我在实现任何东西之前需要解决的一个具体问题


你们中有人以前做过类似的事情吗?

我会将投票对象与VoteCast对象分开

VoteCast是事件,它的值可以是Up、Down或Cancel。还有一个VoteCasts更新的投票对象


计算选票时,您不需要重播演员阵容,只需计算选票即可。您可以将投票放入文章的集合中(向上、向下),然后返回集合计数。VoteCast将修改投票所属的集合。

投票本身就是一个聚合根。如果我们想到“一篇文章有很多票”的关联,那么我们正在应用关系方法,这使我们倾向于DDD社区一致同意的如此受到批评的大集合方法。相反,我们希望关注行为。我们已经知道,一篇文章不会收集选票。由于投票将需要它自己的生命周期,它将有自己的全局标识,因此也有自己的存储库。一篇文章是由用户投票的,这是一种很好的方法,可以为我们的领域模型提供语义,因此,与领域专家语义一起玩,我们可以说“一篇文章是由用户投票的”

记住,用户在这个有限的上下文中扮演读者的角色。votedBy方法是一种工厂方法,可以创建投票。其执行将是:

Article.votedBy(aReader) {
  return new Vote(this, aReader);
}
始终记住,在投票结束时,将有一篇文章和一个读者的概念标识符,这将促进断开连接的模型,而不是保留对其他聚合根的实际引用。所以域服务就是阅读器本身。假设您为rest接口建模

RestInterface.voteArticle(articleId) {
  reader = new Reader(articleRepository);
  reader.vote(articleId);
}

Reader.vote(anArticleId) {
  article = articleRepository.get(anArticleId);
  vote = article.votedBy(this);
  voteRepository.add(vote);
}
您应该通过在数据库级别放置一个组合的unique约束来检查unique(确保用户只对一篇文章投票一次)。这是检查它的干扰最小的方法,否则您应该添加另一个调节投票的域模型。当创建了不同的投票业务规则时,这个新对象可能更有意义,但是为了确保读者只在一篇文章中投票一次就足够了,我认为这是最简单的解决方案(即设置DB约束)。 DDD是一个学习过程,当我们了解该领域的新事物时,我们意识到用户可以点击文章上的“喜欢”或“不喜欢”按钮,因此我们考虑重新考虑我们迄今为止所做的工作:

Article.likedBy(aReader) {
  return Vote.positiveByOn(aReader, this);
}

Article.dislikedBy(aReader) {
  return Vote.negativeByOn(aReader, this);
}
其中两种实现都是:

class Vote {

  readerId
  articleId
  typeId

  Vote(aReaderId, anArticleId, aType) {
    readerId = aReaderId
    articleId = anArticleId
    type = aType
  }

  public Enum VoteType { POSITIVE, NEGATIVE }

  Vote static positiveByOn(aReader, anArticle) {
    return new Vote(aReader.id, anArticle.id, POSITIVE);
  }

  Vote static negativeByOn(aReader, anArticle) {
    return new Vote(aReader.id, anArticle.id, NEGATIVE);
  }
}
此外,由于文章AR上的likedBy/dislikedBy工厂方法正在创建投票,您可以设置一个约束条件,说明一篇文章的投票次数不能超过N次或任何其他业务场景

希望有帮助


Sebastian。

您甚至可以通过“重新计票”将VoteCast重新应用于投票:-)在存储库保存操作期间,您是指DB约束吗?如果我们不使用数据库呢?我在这里回答了一个类似的问题:
class Vote {

  readerId
  articleId
  typeId

  Vote(aReaderId, anArticleId, aType) {
    readerId = aReaderId
    articleId = anArticleId
    type = aType
  }

  public Enum VoteType { POSITIVE, NEGATIVE }

  Vote static positiveByOn(aReader, anArticle) {
    return new Vote(aReader.id, anArticle.id, POSITIVE);
  }

  Vote static negativeByOn(aReader, anArticle) {
    return new Vote(aReader.id, anArticle.id, NEGATIVE);
  }
}