如何在DDD/CQRS/EventSourced项目中为投票/类似系统建模?

Tre*_*ent 7 architecture domain-driven-design cqrs event-sourcing

这里是我的域名的简要说明:

我的文章基本上像任何文章(标题,摘要和正文).

我需要允许对我的文章投票,投票将由匿名用户投票(无需注册,但会议将存储投票,请不要关注此事).

在此域中,Article是我的聚合根.

我找不到用以下要求来模拟我的投票的方法:

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

具有关联会话的访客用户每篇文章只能投一票.

那么,Vote应该自行聚合吗?

就像是

Class Vote { public function cast(ArticleId id, GuestSessionToken token, VoteValue value); }

但在这种情况下,我应该如何检查单一性?使用最终一致性(似乎没问题,因为我很少有重复,因为它们很少见).

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

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

你们中的任何人以前做过类似的事吗?

Seb*_*eri 7

投票本身就是一个聚合根.如果我们考虑协会"一篇文章有​​很多选票"那么我们正在采用一种关系方法,这使我们倾向于DDD社区同意这种批评的大集合方法.相反,我们希望专注于行为.我们已经知道一篇文章不会持有一系列投票.由于投票需要自己的生命周期,因此它将拥有自己的全局标识,因此它拥有自己的存储库.一篇文章是由用户投票的,这是一种很好的方法来为我们的域模型提供语义,因此使用域专家语义我们可以说"文章由用户投票"

anArticle.votedBy(aReader);
Run Code Online (Sandbox Code Playgroud)

请记住,用户在此有界上下文中扮演读者的角色.votedBy方法是一种工厂方法,可以创建投票.它的实施将是:

Article.votedBy(aReader) {
  return new Vote(this, aReader);
}
Run Code Online (Sandbox Code Playgroud)

始终记住,最后投票将具有文章和读者的概念标识符,促进断开连接的模型,而不是保持对其他聚合根的实际引用.所以域名服务将是读者本身.假设您为休息接口建模

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

Reader.vote(anArticleId) {
  article = articleRepository.get(anArticleId);
  vote = article.votedBy(this);
  voteRepository.add(vote);
}
Run Code Online (Sandbox Code Playgroud)

您应该通过在数据库级别放置一个组合的唯一约束来检查唯一(确保用户只投票一次文章).这是检查它的最不具侵入性的方法,否则你应该添加另一个审核投票的域模型.也许这个新对象在创建不同的投票业务规则时更有意义,但是为了确保读者只在文章足够时投票,我认为最简单的解决方案(即放置数据库约束).DDD是一个学习过程,当我们学习关于域的新事物时,我们意识到用户可以在文章上点击"喜欢"或"不喜欢"按钮,所以我们考虑重新分解到目前为止我们所做的事情:

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

Article.dislikedBy(aReader) {
  return Vote.negativeByOn(aReader, this);
}
Run Code Online (Sandbox Code Playgroud)

两个实现都是:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

此外,由于Article AR上的likesBy/dislikedBy工厂方法正在创建投票,因此您可以设置一个约束条件,即文章的投票次数不能超过N次或任何其他业务场景.

希望能帮助到你,

塞巴斯蒂安.


小智 -1

我会将 Vote 对象与 VoteCast 对象分开。

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

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