【问题标题】:How would model a vote/like system in a DDD/CQRS/EventSourced project?如何在 DDD/CQRS/EventSourced 项目中为投票/点赞系统建模?
【发布时间】:2015-01-19 10:20:04
【问题描述】:

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

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

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

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

我找不到满足以下要求的投票模型:

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

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

那么,投票是否应该单独汇总?

类似

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

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

因为如果我将投票方法添加到我的文章聚合中,我将不得不重播每次投票的历史记录,这听起来相当缓慢(考虑到每篇文章可以有 100k 投票的事实)。

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

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

【问题讨论】:

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


【解决方案1】:

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

anArticle.votedBy(aReader);

请记住,用户在这个有限的上下文中扮演着读者的角色。 votedBy 方法是一个工厂方法,它可以创建一个 Vote。它的实现是:

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);
}

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

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

希望对你有帮助,

塞巴斯蒂安。

【讨论】:

  • 数据库约束是指在存储库保存操作期间?如果我们不使用数据库怎么办?
【解决方案2】:

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

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

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

【讨论】:

  • 您甚至可以进行“重新计票”以将 VoteCasts 重新应用于投票 :-)
猜你喜欢
  • 1970-01-01
  • 2015-03-16
  • 2021-11-04
  • 1970-01-01
  • 2014-07-16
  • 1970-01-01
相关资源
最近更新 更多