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

时间:2014-11-21 09:52:49

标签: architecture domain-driven-design cqrs event-sourcing

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

我的文章基本上与任何文章(标题,摘要和正文)一样。

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

在此域中,文章是我的聚合根。

我无法找到根据以下要求对我的投票进行建模的方法:

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

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

那么,Vote应该单独汇总吗?

类似

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

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

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

我知道在设计DDD方式时不应考虑性能和优化,但这是我在实现任何内容之前需要解决的特定问题。

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

2 个答案:

答案 0 :(得分:7)

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

anArticle.votedBy(aReader);

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

Article.votedBy(aReader) {
  return new Vote(this, aReader);
}

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

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(确保用户只投票一次)。这是检查它的最不具侵入性的方法,否则你应该添加另一个审核投票的域模型。也许这个新对象在创建不同的投票业务规则时更有意义,但是为了确保读者只在文章足够时投票,我认为最简单的解决方案(即放置数据库约束)。 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);
  }
}

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

希望它有所帮助,

塞巴斯蒂安。

答案 1 :(得分:-1)

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

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

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