实现领域驱动设计----第一章

带着问题上路

  • 什么是领域驱动设计(是什么)
  • 为什么要做领域驱动设计(为什么要做)
  • 怎样做领域驱动设计(怎样做)
  • 其他的设计模式与领域驱动设计的区别(有类似为什么要做,但是是在取长补短的总结)

译者序

就像在20世纪六七十年代出现了软件危机之后,面向对象成为了人们的救赎;瀑布式开发过程遇到瓶颈时,敏捷被搬上了舞台。

读到这想到了本科课程中《软件开发》,第一次接触到了瀑布模型,而后研究生课程中《现代软件开发》,知道了管理程序员也不是件易事,要做代码估算,要做时间估算,要做质量评估。

如何使用本书

DDD总览
通用语言作用于某个限界上下文,它对于领域建模时非常重要的,不管你是在战术上还是战略上设计软件模型,你都应该保证:在一个特定的限界上下文中只使用一套通用语言,并且保证它的清晰性和简洁性。

战略建模
限界上下文是一种概念上的边界,领域模型便工作于其中。同时,限界上下文为通用语言提供了一套环境,项目成员便通过通用语言来表达软件模型。

在战略设计的过程中,你将发现上下文映射图是非常有用的。你的团队将使用上下文映射图来理解姓名的范围。

架构
一种能够支撑限界上下文的架构是六边形(Hexagonal)架构,它可以辅助其他架构风格,比如面向服务架构、REST和时间驱动(Event-Driven)等。

战术建模
我们在限界上下文中进行DDD的战术建模。战术设计的一个重要模式是聚合。
聚合可以由单个实体组成,也可以由一组实体和值对象组成,此时我们必循在聚合的整个生命周期中保证事务上的一致性。

聚合实列通过资源库进行持久化,另外,对聚合的查找和获取也通过资源库完成。
在这里插入图片描述


我们通常忽略了模块,但是正确地设计模块同样是重要的。简单来讲,我们可以将模块看成是Java中的包或C#中的命名空间。如果只是机械式地设计模块,而不是根据通用语言,那么我们将得不偿失。模块中包含的领域对象应该是内聚在一起的。
在这里插入图片描述

第一章DDD入门

本章学习路线图

  • 了解DDD可以为你的项目和团队带来哪些好处
  • 如何确定你的项目是否适合采用DDD
  • 了解DDD的常见替代方案和它们将导致问题的原因
  • 学习DDD的基础
  • 学习如何向你的管理层,领域专家和技术成员推销DDD
  • 了解使用DDD时所面临的挑战
  • 看看一个正在学习采用DDD的团队时如何工作的

什么时领域模型?
领域模型时关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同时包含了数据和行为,并且表达了准确的业务含义。

为什么我们需要DDD

  • 使领域专家和开发者在一起工作,这样开发出来的软件能够准确地传达业务规则。当然,对于领域专家和开发者来说,这并不表示单单地包容对方,而是将他们组成一个密切协作地团队。
  • “准确传达业务规则”地意思是说,此时地软件就像如果领域专家是人员时所开发出来地一样。
  • 可以帮助业务人员自我提高。没有任何一个领域专家或者管理者敢说他对业务已经了如指掌了,业务知识也需要一个长期地学习过程。在DDD中,每个人都在学习,同时每个人又是知识地贡献者。
  • 关键在于对知识地集中,因为这样可以确保软件知识并不只是掌握在少数人手中。
  • 在领域专家,开发者和软件本身之间不存在“翻译”,意思是当大家都使用相同地语言进行交流时,每人都能听懂他人所说的。
  • 设计就是代码,代码就是设计。设计是关于软件如何工作的,最好的编码设计来自于多次试验,这得益于敏捷的发现过程。
  • DDD同时提供了战略设计和战术设计两种方式。战略设计帮助我们理解哪些投入是最重要的;哪些既有软件资产是可以重新拿来使用的;哪些人应该被加到团队中?战术设计则帮助我们创建DDD模型中各个部件。



    贫血领域对象是不好的,因为你花了很大的成本来开发领域对象,但是从中却收益甚少。比如,由于存在对象-关系阻抗失配(Object-Relational Impedance),开发者需要将很多时间花在对象和数据存储之间的映射上。这样的代价太大,而收益太小。这样的领域对象根本就不是领域对象,而只是将关系型数据库中的模型映射到了对象上而已。这样的领域对象更像是活动记录(Active Record),此时你可以对架构做个简化,然后使用事务脚本进行开发。

为什么会有贫血领域对象

其中一个原因是:贫血领域对象反映了一种自然的过程式编程风格,但我并不认为这是首要原因。软件业中有很多开发者都是学着示例代码做开发的,这并不是什么坏事,只要示例代码本身是好的。然而,通常情况是,示例代码只是用尽可能简单的方式来展示某个特定的概念或者API特性,而并不强调要遵循多好的设计原则。一些极度简化的示例代码总是包含了大量的getter和setter,于是这些getter和setter随着示例代码每天被程序员原封不动地来回复制。

我们一般的Dao模式存在的问题

  1. saveCustomer()业务意图不明确
  2. 方法的实现本身增加了潜在的复杂性
  3. Customer领域对象根本就不是对象,而只是一个数据持有器(data holder)

如何使用DDD

通用语言和限界上下文同时构成了DDD的两大支柱,并且它们是相辅相成的。

上下文术语
可以将限界上下文看成是整个应用程序之内的一个概念性边界。这个边界之内的每种领域术语、词组或句子----也即通用语言,都有确定的上下文含义。

通用语言
是团队共享的语言。领域专家和开发者使用相同的通用语言进行交流。事实上,团队中每个人都使用相同的通用语言。不管你在团队中的角色如何,只要你是团队的一员,你都将使用通用语言。

使用了领域对象的行为,这种行为表示出了领域中的通用语言:

public class BacklogItem extends Entity{
	private SprintId sprintId;
	private BacklogItemStatusType status;
	...
	public void commitTo(Sprint aSprint){
		if(!this.isScheduledForRekease()){
			throw new IllegalStateException(
				"Must be scheduled for release to commit to sprint.");
				}
		if(this.isCommottedToSprint()){
			if(!aSprint.sprintId().equals(this.sprintId())){
				this.uncommitFromSprint();
			}
		}
		this.elevateStatusWith(BacklogItemStatus.COMMOTTED);
		this.setSprintId(aSprint.sprintId());
		DomainEventPublisher
							.instance()
							.publish(new BacklogItemCommitted(
								this.tenant(),
								this.backlogItemId(),
								this.sprintId()));
	}
	...
}											

此时客户端的代码

//客户端同特定于领域的行为将BacklogItem提交到Sprint中
backlogItem.commitTo(sprint);

以上的代码将行为暴露给客户,行为方法的名字清楚地表明了业务含义。这个领域的专家在建模时讨论了以下需求:

允许将每一个待定项提交到冲刺中。只有在一个待定项位于发布计划(Release)中时才能进行提交。如果一个待定项已经提交到了另外一个冲刺中,那么需要先将其回收。提交完成时,通知相关客户方。

DDD并不笨重
DDD能够很好地与敏捷项目框架结合起来,比如Scrum。DDD也倾向“测试先行,逐步改进”地设计思路。在你开发一个新的领域对象时,比如实体或值对象,你可以采用以下步骤进行:

  1. 编写测试代码以模拟客户代码时如何使用该领域对象地。
  2. 创建该领域对象以使测试代码能够编译通过。
  3. 同时对测试和领域对象进行重构,直到测试代码能够正确地模拟客户代码,同时领域对象拥有能够表明业务行为地方法签名。
  4. 实现领域对象地行为,直到测试通过为止,再对实现代码进行重构。
  5. 向你地团队成员展示代码,包括领域专家,以保证领域对象能够正确地反映通用语言。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章