领域驱动设计(DDD)的实际应用
领域驱动设计(DDD)的实际应用
笔者先前参与了一个有关汽车信息的网站开发,用于显示不同品牌的汽车的信息,包括车型,发动机型号,车身尺寸和汽车报价等信息。在建模时,我们只需要创建名为Car的实体(Entity)对象。其他的信息,比如车身尺寸,都是对Car起描述作用的,因此应该建模成值对象(Value Object)。
此时创建的Car对象如下:

public class Car {
private String id;
private CarType type;
private EngineType engineType;
private String brand;
private double length;
private double height;
private double width;
private int price;
}

对应的CarRepository为:
public interface CarRepository {
List<Car> getAllCars();
Car getCarById(String id);
}
现在新的需求来了:对于有些品牌的汽车,该网站与这些品牌的汽车经销商建立了合作关系,使得用户在网站上点击一个链接便可以进入对应的汽车经销商网站。用户每点击一次链接,汽车经销商都会给该网站相应的提成,这也成为了该网站的收入来源之一。该网站因此做出预测,在将来还会有更多这样的定制化需求,即针对不同的品牌显示不同的内容。
(一)错误的建模方法
该网站的开发者立刻决定:可以将这些定制化需求建模成对象,名为Functionality,再在数据库中存放这些Functionality和品牌(Brand)之间关联关系。比如现在有两种类型的定制化需求,一种即为上面讲到的是否显示经销商链接,另一种即为是否显示报价。因此,他们建立了以下数据库表:
Functionality | Brands |
ShowAgencyLink | BMW,HONDA |
ShowPrice | TOYOTA,VOLVO,HONDA |
相应地,他们创建了一个名为FuncitonalityEnablement的类与上表对应:
public class FunctionalityEnablement {
private Functionality functionality;
private String brands;
}
请注意,这里使用了一个String来包含多个Brand。要看某个品牌的汽车是否具有某个Functionality,可以通过以下Service类来完成:
public interface BrandFunctionalityService {
boolean isFunctionalityEnabled(Functionality functionality, String brand);
}
该BrandFuntionalityService先通过DAO层获取到某中Functionality在数据库中所对应的FunctionalityEnablement,再调用isFunctionalityEnabled()方法,传入Brand值,检查该Brand是否拥有该Functionality,即检查该Brand是否包含在FunctionalityEnablement中的brands中。
对于以上建模方式,我至少可以看到两处不足之处:
(1)判断某个Brand是否拥有某种Functionality更应该是Brand本身的一种行为,而不是通过Service来完成。
(2)在有了新的需求之后,不同的Functionality对Brand起到了描述作用,并且这些描述信息有可能随着时间改变,比如在之后某个时刻,该网站又与BUICK品牌的经销商建立的合作关系。这样一来,Brand不再是值对象了,而是变成了具有生命周期的实体对象。但是以上的解决方案依然将Brand作为值对象来使用,并且将本应该成为描述信息的Functionality当成了实体来使用,的确不应该。
(二)正确的建模方法——采用领域驱动设计(DDD)
在使用领域驱动设计时,我们实际上可以建立两个限界上下文(Bounded Context),一个为汽车目录上下文(Car Category Context),另一个为品牌功能上下文(Brand Functionality Context)。在有些情况下,不同的上下文运行在不同的进程空间中,但是对于本文中的情况,由于两个上下文联系密切,又相对较小,我们可以通过引入不同的Java包来划分这两个限界上下文。
这样一来,在汽车目录上下文中,Brand依然可以建模成值对象,但是在品牌功能上下文中,Brand则应该建模成实体对象并且进行持久化。汽车目录上下文将作为品牌功能上下文的下游,即依赖于品牌功能上下文。在汽车目录上下文中,如果需要查看某个品牌是否拥有某种功能,我们可以调用品牌功能上下文所提供的应用服务(Application Service)。应用服务是非常薄的一层,限界上下文的领域模型便通过该层向外界提供基于用例的服务。
这里我们将重点放在品牌功能上下文上。通过以上讨论,我们知道,Brand应该为实体对象,并且拥有一种或多种Functionality,为了不至产生混淆,我们将实体类型的Brand命名为ConfigurableBrand。该ConfigurableBrand定义如下:

public class ConfigurableBrand {
private String name;
private List<Functionality> functionalities; public boolean hasFunctionality(Functionality functionality) {
return functionalities.contains(functionality);
}
}

对应的ConfigurableBrandRepository为:
public interface ConfigurableBrandRepository {
public List<ConfigurableBrand> getAllConfigurableBrands(); public ConfigurableBrand getConfigurableBrandByName(String name);
}
在持久化ConfigurableBrand时,我们可以像上文中那样,在不完全遵循关系型数据库范式的情况下对其进行持久化,此时是将ConfigurableBrand的name作为主键,其他信息(这里只有Functionality)则序列化到一个列中:
BrandName | Funcionalities |
BMW | ShowAgencyLink |
TOYOTA | ShowPrice |
HONDA | ShowAgencyLink,ShowPrice |
VOLVO | ShowPrice |
当然,如果你习惯了遵循数据库范式,那么你也可以建立3张数据库表,一张用于存放ConfigurableBrand,一张用于存放Functionality,另一张关联表存放前两者之间的关联关系。此时,ConfigurableBrand和Functionality存在着多对多的关系。
品牌功能上下文的应用服务提供了以下业务方法:
public interface ConfigurableBrandFunctionalityService {
boolean isFunctionalityEnabled(String functionality, String brand);
}
当汽车目录上下文需要知道某个品牌是否拥有某种功能时,它便应该调用品牌功能上下文的应用服务ConfigurableBrandFunctionalityService,该Service首先通过ConfigurableBrandRepository找到相应的ConfigurableBrand实体对象,再调用ConfigurableBrand中的hasFunctionality()方法以判断该ConfigurableBrand是否拥有某种Functionality。
对于ConfigurableBrandFunctionalityService,我们需要注意,首先外界上下文如果需要访问品牌功能上下文,它必须通过ConfigurableBrandFunctionalityService应用服务,再由该应用服务委派给品牌功能的领域模型,即应用服务才是领域模型的直接客户。另外,在调用ConfigurableBrandFunctionalityService时,我们并没有传入ConfigurableBrand和Functionality领域对象,而是直接使用了String类型,这也是合理的,因为外界不应该直接访问品牌功能上下文中的领域模型,而是应该通过应用服务。再者,在上文中我们讲到,isFunctionalityEnabled()方法更应该建模在ConfigurableBrand实体上,但是这里我们依然将其放在了ConfigurableBrandFunctionalityService上。原因在于,判断一个品牌是否拥有某种功能的核心业务逻辑的确是放在ConfigurableBrand中的,即hasFunctionality()方法,而ConfigurableBrandFunctionalityService中的isFunctionalityEnabled()方法只是反应了一个业务用例,它本身并不处理业务逻辑,而是将逻辑委派给领域模型ConfigurableBrand。
领域驱动设计(DDD)的实际应用的更多相关文章
- 领域驱动设计(DDD)
领域驱动设计(DDD)实现之路 2004年,当Eric Evans的那本<领域驱动设计——软件核心复杂性应对之道>(后文简称<领域驱动设计>)出版时,我还在念高中,接触到领域驱 ...
- 领域驱动设计(DDD:Domain-Driven Design)
领域驱动设计(DDD:Domain-Driven Design) Eric Evans的"Domain-Driven Design领域驱动设计"简称DDD,Evans DDD是一套 ...
- python 全栈开发,Day116(可迭代对象,type创建动态类,偏函数,面向对象的封装,获取外键数据,组合搜索,领域驱动设计(DDD))
昨日内容回顾 1. 三个类 ChangeList,封装列表页面需要的所有数据. StarkConfig,生成URL和视图对应关系 + 默认配置 AdminSite,用于保存 数据库类 和 处理该类的对 ...
- 关于领域驱动设计 DDD(Domain-Driven Design)
以下旨在 理解DDD. 1. 什么是领域? 妈妈好是做母婴新零售的产品,应该属于电商平台,那么电商平台就是一个领域. 同一个领域的系统都有相同的核心业务. eg: 电商领域都有:商品浏览.购物 ...
- 基于领域驱动设计(DDD)超轻量级快速开发架构(二)动态linq查询的实现方式
-之动态查询,查询逻辑封装复用 基于领域驱动设计(DDD)超轻量级快速开发架构详细介绍请看 https://www.cnblogs.com/neozhu/p/13174234.html 需求 配合Ea ...
- 分享我对领域驱动设计(DDD)的学习成果
本文内容提要: 1. 领域驱动设计之领域模型 2. 为什么建立一个领域模型是重要的 3. 领域通用语言(Ubiquitous Language) 4.将领域模型转换为代码实现的最佳实践 5. 领域建模 ...
- 领域驱动设计(DDD)实现之路
2004年,当Eric Evans的那本<领域驱动设计——软件核心复杂性应对之道>(后文简称<领域驱动设计>)出版时,我还在念高中,接触到领域驱动设计(DDD)已经是8年后的事 ...
- 领域驱动设计(DDD:Domain-Driven Design) 介绍
Eric Evans的“Domain-Driven Design领域驱动设计”简称DDD,Evans DDD是一套综合软件系统分析和设计的面向对象建模方法,本站Jdon.com是国内公开最早讨论DDD ...
- 我对领域驱动设计(DDD)的学习成果
领域驱动设计之领域模型 2004年Eric Evans发表Domain-Driven Design – Tackling Complexity in the Heart of Software (领域 ...
- 初学者浅谈我对领域驱动设计(DDD)的理解
一.为什么要学习领域驱动设计 如果你已经设计出了优雅而万能的软件架构,如果你只是想做一名高效的编码程序员,如果你负责的软件并不复杂,那你确实不需要学习领域驱动设计. 如果用领域驱动设计带来的收获: 能 ...
随机推荐
- android 之 下载管理器 无论监测在当地的设计思路
我相信你.让应用市场.或其它下载,想不管是什么地方监测进展情况.而且很好的实现. 这里分享一个相对简单的,并防止内存溢出等..我们用一个引用弱结合View进展更新方法. Map<String, ...
- .NET中Redis安装部署及使用方法简介
一Redis服务端以服务方式运行 修改端口压缩文件中配置的是6488 修改密码 修改库的数量 工具配置安装后如下图 二Redis服务端以控制台方式运行 第一步配置本地服务 第二部安装和配置客户端 三C ...
- BIEE在creating domain步骤停止的解决的方法
1.错误现象: biee11g creating domain csf entries will not be parsed since the adminserver is unreachable ...
- 单点登录SSO原则的实现
单点登录SSO(Single Sign On)简单地把它是下一个在多系统环境中共存,在用户登录,不要在其他系统上的登录.这是用户第一次登录得到所有其他系统的信任. 单点登录在一个大型网站非常频繁使用, ...
- java_部署jar
javaw -ms100m -mx256m -jar MyApp.jar 上面指定了使用最小100M最大256M内存. 4)如果main函数要带参数 java -mss10m -mx300m -jar ...
- MVC自定义过滤器,自定义Area过滤器,自定义Controller,Action甚至是ViewData过滤器
实现MVC自定义过滤器,自定义Area过滤器,自定义Controller,Action甚至是ViewData过滤器 MVC开发中几种以AOP方式实现的Filters是非常好用的,默认情况下,我们通过A ...
- 什么时候需要使用Double? double、float、decimal的区别
原文:什么时候需要使用Double? double.float.decimal的区别 float:浮点型,含字节数为4,32bit,数值范围为-3.4E38~3.4E38(7个有效位) double: ...
- Web API-路由(二)
路由匹配主要有三个阶段: 1.将URI匹配到一个路由模版: 2.选择一个controller 3.选择一个action: 可以使用系统提供的拓展点,修改默认的匹配与选择逻辑规则. 路由模版: 路由 ...
- 阅读安卓在线(Android)系统源代码
http://grepcode.com/project/repository.grepcode.com/java/ext/com.google.android/android/ 版权声明:本文博客原创 ...
- 写给初学前端工程师的一封信 (转于Kejun)
大家好: 应波波的邀请写一写我对这个话题的想法.从去年开始不少朋友让我帮忙介绍前端工程师,绝大部分忙都没帮上,原因是真找不到人.我当时是这么跟他们分析的:过去的客户端以browser为主,所以HTML ...