写点什么

领域驱动设计 101 - 领域服务

用户头像
luojiahu
关注
发布于: 4 小时前
领域驱动设计101 - 领域服务

前面,我们讨论了实体和值对象这两个概念,实体和值对象用来对领域中的事物或者事物的属性进行建模,但是,我们面对的领域中,不仅仅有实体和值对象,还有针对实体的操作。这些操作有可能是实体本身的操作,例如修改文章状态,实际是文章这一实体状态的改变。另外,还有可能是对某个或者多个实体的操作,如果这些操作足够复杂,将其添加为某个实体的操作函数,可能并不符合领域的实际,这个时候,通常需要领域服务这一建模方法。

什么(不)是领域服务

不是 RPC

服务这一词汇在服务器应用开发当中使用非常普遍,尤其是微服务作为最流行的架构的今天。如果不特殊说明,那么服务在一般的讨论当中都被当做是可供远程调用的一个接口,或者简单来说是 RPC。但是领域服务与 RPC 完全不同,领域服务首先是富含领域规则或者知识的一类操作,其次,根据应用部署架构的不同,领域服务可能也以远程服务的形式被调用,但这仅仅是由应用部署架构决定的,和领域本身并没有多大关系。

与应用层服务的区别

另外,在应用层,更多的业务逻辑被实现成服务的形式,同样要注意的是领域服务和应用层的服务也不同。领域服务充满着领域内的业务规则,与具体的应用场景并没有多大关系。


但实际当中,如何区分一个业务操作是属于领域层还是应用层可能并不是那么清晰,因为往往应用的场景目前可能只有一个,使得判断层次归属比较困难。这里,一个比较实用的办法是假设一个与当前类似的场景,该场景与当前场景不同的可能是发起来源不同,也可能是有细微的操作差异。然后,考察当前的业务操作在假设场景下是否是相对固定不变的,如果是,那么很可能是属于领域层。

进一步判断

基于前面的讨论,进一步进行分析,在进行领域建模时,是否需要将某个业务操作建模为领域服务,可以从如下几个方面进行考察:


  • 明显的业务操作过程

  • 首先,领域服务必须是一个相对明显的业务操作过程,其中包含相对复杂的业务逻辑,既不是某个实体属性的简单变化,也不是具体应用场景中的特殊操作。

  • 符合领域通用语言描述

  • 其次,领域服务还应当是符合领域通用语言描述的,不能脱离了具体领域范围。

  • 涉及多个领域对象

  • 此外,有些领域操作,可能涉及领域内的多个实体对象,这个时候,将其添加至任何一个实体可能都并不恰当,建模为领域服务将是最佳的办法。例如,作者提交文章这一操作,涉及对文章状态的修改,同时也涉及对作者文章数的统计。

一个例子

还是以文章为例子,在作者编辑完文章后,进行文章保存或者提交,这时需要统计文章的字数(当然,最好的体验是实时统计字数并展示,这里对实际需求进行了一定程度的简化)。


乍一看,统计文章字数这一操作,输入来源是文章的内容,输出是文章的字数统计,都是文章本身的属性,似乎应该作为文章本身的一个操作来实现。但进一步分析发现,首先,文章字数的统计需要对文章的内容加工处理,可能涉及排除无效符号等文本加工操作,相对复杂;其次,字数统计本身实际接收的输入是文本内容,与文章这一实体并无紧密关系,对于任一一段文本,都可以统计其字数。


所以,这里应该将其建模为字数统计的领域服务,接收一个文本对象的输入,输出按照业务规则进行文本加工后统计出的文本字数。


再来考虑这个服务应该是属于领域层还是应用层。很明显,这毫无疑问是一个明显的业务操作过程,进行了一定的计算;同时,也符合领域通用语言:计算“文本”的“字数”;最后,这个服务虽然仅接受一个领域对象,但是适用于多种领域对象,将其建模为领域服务而不是实体的一个函数,更具有通用性。


再用“场景发散”方法进行分析,统计文本字数,并不与作者保存或者提交文章紧密关联,很可能也会在接收其他系统通过 API 推送的文章时需要进行,所以不应当建模为应用层服务。


具体的代码实现如下,首先是字数统计服务的实现:


/** * @author luojiahu * @usage: 字数统计服务 * @created: 2021/6/22 22:11 */public class WordStatisticService {
public int calcWordNum(String text) { if (null == text || "".equals(text)) { throw new IllegalArgumentException("text should not be empty."); }
int wordNum = 0; // TODO trim meaningless symbols and count word number. return wordNum; }}
复制代码


然后是该服务的应用,在文章工厂类创建文章实体时,通过调用字数统计服务,获取文章的字数属性。


/** * @author luojiahu * @usage: 文章实体创建工厂方法 * @created: 2021/6/22 22:33 */public class ArticleFactory {
static WordStatisticService wordStatisticService;
/** * 创建文章 * @param title * @param content * @return */ public static Article createFrom(String title, String content) { int wordNum = wordStatisticService.calcWordNum(content); Article article = new Article(title, content, ArticleStatus.DRAFT, wordNum); return article; }}
复制代码


发布于: 4 小时前阅读数: 3
用户头像

luojiahu

关注

喜欢思考组织、过程、产品的后端开发 2017.01.08 加入

还未添加个人简介

评论

发布
暂无评论
领域驱动设计101 - 领域服务