《实现领域驱动设计》 (美)弗农著 9章 模块

通过模块完成设计

在DDD中,模型中的模块表示了一个命名的容器,用于存放领域中内聚在一起 的类。将类放在不同模块中的目的在于达到松耦合性。由于DDD中的模块并不是 一个通用的存储区域,因此对其进行适当的命名是重要的。事实上,模块名是通用 语言的重要组成部分。

模块应该包含一组具有高内聚性的概念集合,这样做的好处是可以在不同的模块 之间实现松耦合。否则,我们应该修改模型以重新划分这些概念。……由于模块名是通 用语言的一部分,模块名应该反映出它们在领域中的概念。

在设计模块时,有几条简单的原则,如表9.1所示。
在这里插入图片描述

领域模型的命名规范

接下来的一层模块名定位了一个限界上下文。在模块名中加入限界上下文的名称是有好处的。
以下是SaaSOvation团队对三个模块的命名:

com.saasovation.identityaccess 
com.saasovation.collaboration 
com.saasovation.agilepm

接下来,他们又向模块名中添加了另一层重要的名字,该层用于定位领域中某个特定的模块。

com.saasovation.identityaccess.domain 
com.saasovation•collaboration.domain 
com.saasovation.agilepm.domain

以上的“domain”部分可能并不直接包含实际的接口/类,而是作为更低层模 块的容器。以下是“domain”的下一层:

com.saasovation.identityaccess.domain.model 
com.saasovation.collaboration.domain.model 
com.saasovation.agilepm.domain.model

在该层中,我们定义模型中的类,接口类和抽象类也位于其中。

SaaSOvation团队喜欢在该模块中放入一些通用的接口,比如那些用于发布事件的类.还 有实体和值对象的抽象基类:

ConcurrencySafeEntity 
DomainEvent 
DomainEventPublisher 
DomainEventSubscriber
 DomainRegistry Entity
IdentifiedDomainObject 
IdentifiedValueObject

如果你喜欢将领域服务放在domain.model之外,那么你可以为其创建一个与 model同层的模块:

com.saasovation.identityaccess.domain.service 
com.saasovation.collaboration.domain.service 
com.saasovation.agilepm.domain.service

敏捷项目管理上下文中的模块

SaaSOvation公司当前正工作于敏捷项目管理核心域(2),让我们看看他们是 如何设计模块的。
SaaSOvation的ProjectOvation团队选用了3个顶层模块:tenant、team和 project。以下是tenant模块:
在这里插入图片描述该模块中包含了值对象Tenantld,Tenantld表示某个租户的唯一标识,该标识 来自于身份与访问上下文。模型中的所有其他模块都依赖于tenant模块。我们需要 将一个租户所对应的对象与另一个租户的对象分离开来。但是,这些模块之间的 依赖是非循环依赖,即tenant模块并不依赖于其他模块。

上面的team模块包含了聚合类以及一个用于管理产品团队的领域服务:
在这里插入图片描述该模块中含有3个聚合类和一个领域服务接口。Team类维护了一个 ProductOwner实例,同时还拥有一个TeamMember的集合。ProductOwner和 TeamMember实例由MemberService所创建。所有这三个聚合根实体都引用了 tenant 模块中的Tenantld:
在这里插入图片描述这里的MemberService作为防腐层(3)的前端,它的作用在于从身份与访问 上下文中同步TeamMember。同步过程釆用静默方式,即不需要用户请求。当一 个Team Member在远程上下文中注册时,该MemberService将主动地调用同步方法。该同步过程与远程系统能够保持最终一致性,只是其中有短暂的时延。同 时,MemberService还用于更新TeamMember的细节信息,比如名字和E-mail地址等。
敏捷项目管理上下文拥有一个名为product的父模块,它包含3个子模块:
在这里插入图片描述SaaSOvation的团队成员们非常喜欢这种自然的模块命名 方式,因为它与通用语言有很好的对应关系:“产品”、"产品 待定项’“产品发布"和"产品冲刺”。

这里出现了 4个聚合——他们为什么没有将这4个聚合直 接放在pmduct模块中呢?除了这4个聚合之外,还存在其他聚 合•比如Product所包含的ProductBacklogltem实体、Backlogltem 所包含的Task实体、Release所包含的ScheduledBacklogItem实体和Sprint所包含的CommittedBacklogltem实体。有些聚合还 有可能发布领域事件。这些类总计起来将近60个,要将它们放在同一个模块中显然是不合适 的。
和ProductOwner、Team和TeamMember_样,Product、Backlogltem、Release和Sprint都引 用了Tenantld。此外,还存在额外的依赖。比如Product:
在这里插入图片描述再看看BacklogItem:
在这里插入图片描述对Tenantld和Teamld的依赖是非循环依赖.它们都是单向的。但是.从Backlogkem对 Productld引用来看.我们似乎只是在backlogitem模块和product模块之间引入了单向依赖.而 事实上它们却是双向依赖。每个Product都作为创建Backlogltem (还包括Release和Sprint)的 工厂。因此.它们之间的依赖是双向的。这里的3个子模块都以product模块作为父模块,所以 我们可以放松依赖原则。在这种情况下,我们应该优先考虑模块的组织结构.而不是松耦合 性。再重申一遍.Backlogitem、Release和Sprint都自然地从属于Product.因此我们没有必要再次撕开聚合边界。

然而.通过继承一个通用的标识类型.它们之间是可以实现松耦合的。此时, Backlogitem、Release和Sprint对Product的引用通过通用的Identity完成。

在这里插入图片描述
诚然.在使用以上方式时,SaaSOvation团队获得了更好的松耦合性。但是,这种做法也有可能给程序带来bug,比如我们将无法对不同的Identity进行区分。

其他层中的模块

在不考虑架构(4)的情况下,你总需要为架构中的非模型组件创建模块并为 其命名。这里,我们讨论一个分层架构(4)中的模块命名规范,当然,这些规范也 可以用于其他架构风格。

在一个典型的分层架构中,我们将系统分为以下不同的层:用户界面层、应用 层、领域层和基础设施层。根据每层中组件的不同,它们中的模块也会不同。

让我们首先看看用户界面层(14)和其中的REST资源。可能的情况是,REST 资源通过XML、JSON和HTML等数据展现形式向GUI和系统客户端提供服务。对 于GUI而言,REST资源是不会为其创建展现布局的,而只是创建一些数据标记格 式(XML、HTML),另外就是创建一些序列化的数据格式(XML、JSON和协议缓 存等)。在客户端,用于图形布局的数据可能通过另外的渠道获取。因此,在支持 REST的用户界面层中,我们至少需要两个模块:
在这里插入图片描述REST资源由resources包维护,而那些只与展现相关的数据由view (或者称 为presentation)包中的组件提供。根据系统所需的REST资源数量,你可能需要在 每个主模块中创建多个子模块。需要记住的是,一个资源提供类可以服务于多个 URI,你可能有多个这样资源提供类,此时可以将它们全部放在主模块中。之后, 如果新的需求需要进一步为它们划分子模块,修改起来也是简单的。

应用层可能还有另外的模块,比如一种服务对应一个模块:
在这里插入图片描述与组织REST资源的原则一样,只有在需要的时候才为应用层划分子模块。比 如,在身份与访问上下文中只存在为数不多的应用服务,于是SaaSOvation团队决定 将它们置放与主模块中:

com.saasovation.identityaccess.application

当然,你可能会倾向于使用第一种更加模块化的设计,那也是可以的。当应用 服务变多时,我们便需要仔细考虑了。

先考虑模块,再试限界上下文

对于何时应该对领域模型进行分离,何时将领域模型建模成一个整体,我们 应该仔细地思考与对待。有时,通用语言可以很好地帮助我们做出正确的选择。 但是另外的时候,其中的术语将变得非常含糊。在这种情况下,我们并不清楚如何 划分上下文边界。此时,我们可以首先将它们放在一起,使用模块来对模型进行划 分,而不是限界上下文。

但是,这并不意味着我们就应该限制对限界上下文的创建。我们应该通过通 用语言的需求来划分模型边界。你应该知道,限界上下文不是用来代替模块的。使 用模块的0的在于组织那些内聚在一起的领域对象,对于那些内聚性不强或者没 有内聚性的领域对象来说,我们应该将它们划分在不同的模块中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值