DDD

领域驱动设计揭秘


领域驱动设计,或者叫DDD,是一种软件设计方法,它的目标是研发更好的软件。工程师通过在连续设计过程中与领域专家紧密合作来实现这一目标。

Eric Evans创造了领域驱动设计并写了一本书《领域驱动设计:软件核心复杂性应对之道》。他的主要目标是在开发过程中使用模型和业务语言并遵循特定的技术模式。总之,这些步骤会帮助你处理复杂的软件而不会陷入困境。

在这篇帖子中,我会进一步揭秘领域驱动设计方法。首先,我会分享一个故事,讲述我对领域驱动设计的认知过程。然后我会详细介绍过程的建模部分。最后,我会概述技术要素。

我是如何认识领域驱动设计

坦白说,当我第一次听到“DDD”这个词时,我会笑着礼貌的点点头。对于我来说,它很神秘,而且直到我对我撰写的TDD帖子发表了些礼貌的评论之前,我并没有太多考虑它。评论者坚持认为领域驱动设计是必经之路。

在TDD的那篇帖子中,我认为测试驱动开发应该真正是“测试驱动的设计”。当然,紧急设计具有价值。但其它方法也有价值。因此,作为一个思维开放、好奇的人,我得了解更多有关领域驱动设计的知识。我阅读了Eric Evans的书并且马上意识到它对怎么解决设计问题的价值。

设计问题

在软件中,存在许多设计问题。当然,存在很多工具来解决这些问题。一方面,我们有设计模式。另一方面,我们有建模。领域驱动设计使用这些工具并引入了一些自身的概念来处理复杂性。

然而,复杂性又有哪些问题?

复杂性的麻烦

我们凭直觉知道什么是复杂性。但是,它可能与每个人和每个环境有关。一个问题可能对一个人来说很简单,但对另一个人来说却很复杂。

为了量化复杂性,让我们考虑一下这样一个事物,它包含三个以上相互关联的组件以及至少五个需要正式定义的业务规则,这些规则必须在“必填字段”之外。可能问题涉及一些计算,数据转换,多屏显示和一个外部数据源。而且,谈到外部性,软件通常是互连系统的复杂网络的一部分。那么它的复杂程度如何?这要看情况。对于一个开发人员来说,这个系统很复杂。但是对于另一位开发人员来说可能是小菜一碟。

领域驱动设计主要涉及如何处理软件开发中的复杂性。这是它的价值所在。当问题很简单时,紧急设计很有用。但是当你面临复杂性时,需要一些周到的考虑。当存在复杂性时,无论如何它都会存在。你可以迁移复杂性,但不能消除它。当你增加更多复杂性时,真正的麻烦就来了。另一方面,你可以控制复杂性。域驱动设计在“有界上下文”中控制复杂性。

有界上下文包含两件事

有界上下文包含两件事:复杂性和术语。

首先,有界上下文意味着包含复杂性。换句话说,我们可以为某些特定的复杂业务逻辑创建有界上下文。有界上下文通过适配器连接到其他上下文。而且,这些适配器可以防止变化。当上下文中包含的那些复杂性发生变化时,我们系统的其余部分也不必更改。这就是为什么我们应该使用适配器在上下文之间进行“阻抗匹配”。

另一方面,有限上下文是关于在上下文中定义术语的。例如,风险管理部门可能将雇员视为“雇员”。但是人事部门可以称他们为“人员”。而财务部门对他们有自己的称呼:“劳动力资源”。无论你是否喜欢这些术语,你都必须接受同一实体在不同上下文中有不同的名称。

作为服务行业,我们的IT人员应该花时间学习客户的语言。与人事部门交谈时,我们应使用“人员”一词。但是在进行财务工作时,我们应该使用“劳动力资源”,即使这会使我们感到畏缩。毕竟它们对于我们来说只是词语,但在特定上下文中,这些词语具有意义。想象一下,如果非开发人员使用“单元测试”一词来表示“功能测试”,你会感觉如何。如果他们坚持以这种方式滥用普遍存在的术语,你能认真对待吗?这就是为什么我们需要直接与领域专家一起工作的原因,领域专家是从事业务领域工作或具有业务领域的深入知识的人。

与领域专家一起建模

领域驱动设计强调了与业务合作的重要性。在处理模型时尤其如此。模型是用于沟通和计划的工具。它可以是简单的模型,也可以是更复杂的格式,例如UML。最后,业务领域的领域专家和工程师都必须理解它。建模是沟通业务问题和遍历解决方案的重要步骤。

请注意,这种类型的建模不是数据建模。这也不是数据库设计。数据建模和数据库设计是定义低级细节的练习。这些通常与业务无关。同样,这种类型的建模也不是对象建模。可见,对象模型是实现细节,其使用方式与数据库设计相同。

领域驱动设计中的建模是关于创建一个用于理解业务的模型。它可能有点抽象,但必须是一个共同的愿景。目的是建立共同的理解,并促进业务与工程之间关于业务问题的双向交流。只有到那时,我们才真正准备好进入技术细节。

技术部分

一旦对领域有了足够的了解,就该进行项目剩余部分了。Evans强调变化的重要性。随着项目实施的进行,新的理解不断发展。解决某些问题可能有更好的方法。通常,你需要返回到建模本身。这是一件好事,因为对模型的那些迭代将进一步改善设计!

除了改进设计之外,在领域驱动的设计中还有一些技术建议。Evans提出了许多模式,这些模式使软件对变化的适应性更强。我们已经讨论了两种模式——适配器模式和有界上下文。其他还包括包括工厂模式和存储库模式。还有其他一些,但是由于它们是最常见的,所以我将重点关注这两个。让我们先看一下工厂模式。

工厂模式

当我们需要创建一些新对象时,应该使用工厂模式。我们不希望每个消费者都关心构造特定类型或子类型的对象的细节。那么我们该怎么办?我们将责任委托给工厂。当然,我们可以直接调用构造函数,但是最好不要在构造函数中执行太多操作。复杂对象创建的解决方案是工厂!(顺便说一句,工厂对于包含有关返回哪种特定类型对象非常有用。)

工厂可以是基类上的静态方法,也可以是自己的静态方法。消费者必须使用工厂来构建对象。我们可以通过使消费者无法访问该类的构造函数来指定此行为。获取新Foo的唯一方法是调用Foo类的Build(... params)方法。然后,你将使用Foo对象来做一些事情:
Foo aFoo = Foo.build(fooData);
aFoo.reportHours(7);
aFoo.setManager(managerData);
Integer aFooId = aFoo.save();

此代码调用工厂方法来构建Foo。然后,它使用该对象报告工作时间并设置管理员。最后,它保存对象并获取生成的对象ID。以后,我们可以使用ID从保存的数据中重新操作Foo对象。

关联使用存储库

存储库是持久性机制的抽象。一些常用的持久化机制包括数据库、文件和内存。一般存储库是代表了任何可以保存和获取对象数据的事物。所有这些都发生在对象或工厂内部。在这种情况下,领域类依赖存储库。

一个领域类代表了一个现实世界中的概念,它也叫领域对象。这些领域类是应用程序中需要使用的。那么我们怎么在领域对象中关联使用存储库。

幸运的是,我们可以使用工厂进行控制。下面是例子:
Foo aFoo = Foo.Get(aFooId);
//---//
public class Foo
{
private Foo(FooData data)
{
   ...
}
public static Foo Build(FooData data)
{
   return new Foo(data);
}
public static Foo Get(int id)
{
   var data = ServiceLocator.GetRepository<Foo>().Get(id);
   if(!data) throw new NoFooFoundException(id);
   return new Foo(data);
}


你可以看到Foo的Get方法使用ServiceLocator来获取存储库。这是一个实现方式,但是你也可以通过依赖注入来达到相同的目的。

你必须记住,领域驱动设计已经问世超过十年。从那开始,我们的工具不断发展。即使如此,这里还有很多重要概念。一个非常好的收获是我们把相关职责放到一块代码,形成了Foo类。这是一个非常重要的面向对象的法则,但是它常常被遗忘。当缺少这些模式时,代码库变得更加难以维护。

最后,领域驱动设计与组织相关

最后,所有术语和技术实践加在一起就是一件事——组织。Evans的书的副标题取名“软件核心复杂性应对之道”是有原因。复杂软件项目需要特别注意才能不断向前推进。不然,项目可能陷入泥潭。DDD的概念和实践是管理复杂性的一种深思熟虑的方法。当然,还有其他优点,但是这一优点值得你注意!如果你还没有读过这本书,我强烈建议你去读读它。

原文链接:Domain-Driven Design Demystified(翻译:吴伟略)

0 个评论

要回复文章请先登录注册