DDD实践思考:文章投票点赞功能的领域建模

选中文字可对指定文章内容进行评论啦,→和←可快速切换按钮,绿色背景文字可以点击查看评论额。

功能说明

文章投票功能简单描述:用户阅读文章后,可以给文章投票,投票包括两个方向:支持或不支持。

设计思考

基于领域驱动设计常见的做法,首先可以确定的是文章Article是一个聚合根。Article包含title,content,description等等。

有疑惑的地方是:投票Vote是否要单独出来做聚合根,还是把它放在Article聚合根下?需要考虑一下几点:

  1. Article和Vote是一对多的关系,但vote的数量是不确定的,如果把Vote放到Article的集合里,会存在加载Article性能问题以及内存额外消耗。
  2. Vote是否有自己特定的业务以及生命周期呢?如取消,支持和不支持等等
  3. Vote除了通过Article聚合查找外,是否需要按用户查找用户自己投票信息列表呢?
  4. 如果把Vote放到Article里,那么收藏,评论等类似功能是否也要放到Article里,最后Article就会演变称一个大聚合,虽然说保证了聚合内规则的一致性,但是一个类承担了多个责任。

文章Effective Aggregate Design Part I: Modeling a Single Aggregate针对大聚合的问题做了比较详细的分析,其中一个原则是:尽量设计小的聚合。

基于以上考虑,把Vote作为单独的聚合根来设计。

单独Vote聚合根设计

简单投票功能

简单描述投票的领域语义:用户对文章投了票。代码表示为:

article.votedBy(readId)

这里的votedBy是一个工厂方法,它是Article用来创建投票的方法,实现如下:

//在Article类里
public Vote votedBy(UserId readerId) {
  return new Vote(this.id, readerId);
}

在应用服务层的ArticleApplicationService,可以这样调用:

public void voteArticle(UserId readId, ArticleId articleId) {
   Article article = articleRepository.get(articleId);
   Vote vote = article.votedBy(readId);
   voteRepository.save(vote);
}

支持和不支持投票代码重构

对于投票要分为支持和不支持两种情况,那么以上代码就要重构下。

//Article类的方法
//用户投支持票
public Vote likedBy(UserId readerId) {
  return Vote.positiveByOn(readerId, this.id);
}

//用户投不支持票
public Vote unlikedBy(UserId readerId) {
  return Vote.negativeByOn(readerId, this.id);
}

对于Vote类实现代码简单如下:

public class Vote {

  privte UserId readerId;
  private Article articleId;
  private VoteTpe type;

  private Vote(UserId readerId,ArticleId articleId,VoteType voteType) {
    this.readerId = readerId
    this.articleId = articleId
    this.type = voteType
  }

  public Vote static positiveByOn(UserId readerId,ArticleId articleId) {
    return new Vote(readerId, articleId, VoteType.POSITIVE);
  }

  Vote static negativeByOn(UserId readerId,ArticleId articleId) {
    return new Vote(readerId, articleId, VoteType.NEGATIVE);
  }
}

public Enum VoteType { POSITIVE, NEGATIVE }

Vote类也是提供了两个静态的工具方法,用于创建支持和不支持两种情况的投票。

 

版权声明:著作权归作者所有。

相关推荐

Python 函数内修改关键字参数**kw的安全性考虑

Python定义函数常常会使用关键字参数**kw来接收字典值。基本语法def f(p1,p2,**kw) **kw将接收0个或任意个含参数名的参数,这些关键字参数在函数内部会自动组合为一个字典。示例def person(**kw)     for k in kw:    &nbs

[译]Angular 5:升级和新功能的总结

代号为pentagonal-donut的Angular 5刚刚发布,它带来了一些新功能以及一些内部变化,这些会让Angular应用变得更小,执行变得更快。 在这篇文章中,我们将简要介绍一些最重要的变化以及升级指南。 有关更多详细信息,请参阅公告博客帖子,有关所有更改的详细信息,请参阅官方更新日志。性能这是Angular 5包含的一些改变,它会让你的应用变得更

使用Angular CLI的6个最佳实践及专业技巧

我们在新建前端项目时,常常会为项目的代码结构以及技术选型(如gunt,gulp,webpack,systemjs等等)绞尽脑汁。Angular CLI为我们开发Angular应用提供了一个标准的项目模板。项目模板里包含了完整的测试:包括unit测试,e2e测试多环境构建,提供了develop和production这两种基本的构建还提供了一系列代码开发工具,如ng generat

Java创建文件的常用方法

Java创建文件有几种常用的方法File.createNewFile()创建空白文件java.io.File类里的方法createNewFile()可以用来创建文件。createNewFile()新建的是空文件。创建文件首先要使用File类构建将要被创建的文件,然后再调用createNewFile()把新文件创建出来。createNewFile()的结果分为三种情况:新文件创建成功返回true。如

Java使用nio的Files新建文件并写入内容

Java 7新增的java.nio.file.Files提供了很简单并且效率高的新建文件并写入内容的方法。直接写入字节Charset utf8 = StandardCharsets.UTF_8; try {     Files.write(Paths.get("file1.txt"),&n

ES6简化版的JavaScript中间件模式的实现

JavaScript中间件模式的目的是分解前端业务逻辑,通过next方法层层传递给下一个业务。比较经典的是express和koa。这是使用ES6实现的一个简版的中间件模式:class Middleware { use(fn) { this.go = (stack => next => stack(fn.bind(this, next.bind(this))))(this.go)

PHP添加csrf token的注意点

首先不建议使用rand(),unique()来生成,如$token = md5(uniqid(rand(), TRUE));这是因为rand()函数产生的随机字符串是可以预测的。runiqid()和md5()增加的复杂度不高。产生tokenPHP 7session_start();if (empty($_SESSION['token'])) { $_SESSION['token'] = bi

领域驱动设计——如何发布领域事件

领域事件(Domain Event)是域驱动设计的构建块之一,它通常是一个以过去时命名的不可变数据容器类。如:public class OrderPlaced { private Order order; public OrderPlaced(Order order){ this.order = order; } public Order getOrder()