领域驱动设计简介

概述

当今的企业应用程序无疑是复杂的,并且依靠某些专门技术(持久性、AJAX、WEB服务器等)来完成其工作。作为开发人员,我们倾向于专注于这些技术细节是可以理解的,但事实就是,不能解决业务需求的系统对任何人都没有用,无论它的外观多么漂亮或其基础架构的如何牛逼。

领域驱动设计

(DDD)的哲学是关于将我们的注意力放在应用程序的核心,专注于业务领域固有复杂性本身。我们还将核心域(对于业务而言是唯一的)与支持子域(本质上通常是通用的,例如金钱和时间)区分开来,并将我们更多设计工作适当地放在核心上。领域驱动设计由一组模式组成,这些模式用于从领域模型开始构建企业应用程序。在你的软件职业生涯中,你可能已经遇到了许多这样的想法,尤其是如果你是一位使用OO语言经验丰富的开发人员。将他们一起应用将使您能够构建真正满足业务需求的系统。
下图是要展现的模式和模式间关系的总图。

代码和模型

借助DDD,我们正在寻求创建问题域的模型。持久性,用户界面和消息传递的内容可能会在以后出现,这是需要了解的领域。从模型中去除在设计中使用的术语和所赋予的基本职责后,代码就成了模型的表达式,所以对代码的一个变更就可能称为对模型的变更。
这个影响会涉及到项目的实现中。为了紧密捆绑起实现和模型,通常需要支持建模范型的软件开发工具和语言,例如面向对象编程。面向对象编程非常适合对模型的实现,因为它们基于同一个范型。面向对象编程提供了对象的类和类之间的关联关系、对象实例、
以及对象实例之间的消息通信。面向对象编程语言让建立模型对象、对象关系与它们的编程副本之间的直接映射成为可能。过程化语言提供了有限的模型驱动设计的支持。这样的语言不能提供实现模型关键组件所必须的构建能力。

这是DDD模式的第一个:模型驱动设计。这意味着能够将模型中的概念(理想情况下完全按字面意义)映射到设计/代码的概念。模型的改变意味着代码的改变。更改代码意味着模型已更改。DDD并不要求你使用面向对象对域进行建模-例如,我们可以使用规则引擎来构建模型-但鉴于主要的企业编程语言是基于OO的,因此大多数模型的本质上都是OO。毕竟,OO是基于建模范例的。模型的概念将表示为类的接口,职责将表示类成员。

模型上下文

每当我们讨论模型时,它总是在一定范围内。通常可以从使用该系统的最终用户集合中推断出此上下文。因此,我们有一个部署到交易员的前台交易系统,或一个超市收银员使用的销售点系统。这些用户以特定的方式与模型的概念相关,并且模型的术语对这些用户有意义,但对于上下文之外的任何其他人则不一定。不是试图保持一个迟早要四分五裂的大模型,我们应该做的是有意识地将大模型分解成数个较小的部分。只要遵守相绑定的契约,整合得好的小模型会越来越有独立性。每个模型都应该有一个清晰的边界,模型之间的关系也应该被精确地定义。DDD将此称为有界上下文(BC)。每个领域模型仅存在于一个BC中,而BC恰好包含一个领域模型。

我必须承认,当我第一次读到BC时,我看不出要点:如果BC与领域模型同构,为什么要引入一个新术语?如果只有最终用户与BC进行交互,那么也许不需要这个术语。但是不同的系统(BC)也彼此交互,发送文件,传递消息,调用API等.如果我们知道有两个BC相互交互,则我们必须注意在一个概念之间进行传换。域或其他域。
在模型周围放置明确的边界还意味着我们可以开始讨论这些BC之间的关系。实际上,DDD标识了BC之间的一整套关系,以便我们可以合理化当我们需要将不同的BC链接在一起应该采取的的措施:

  • 已发布的语言:交互的BC商定一种共同的语言(例如,企业服务总线上的一堆XML模式),通过它们可以彼此交互。
  • 开放的主机服务:BC指定任何其他BC可以使用其他服务协议(例如Restful
    Web服务);
  • 共享内核:两个BC使用通用的代码内核(例如,库)作为通用的通用语言,但其他方式则以自己的特定方式执行;
  • 客户-供应商:一个BC使用另一个服务的服务,并且是另一个BC的利益相关者(客户)。因此,它可以影响该BC提供的服务;
  • 顺从者:一个BC使用另一个服务,但不是该另一个BC的利益相关者。因此,它使用原样(符合)该BC提供的协议或API;
  • 防崩溃层:一个BC使用另一方的服务,而不是利益相关者,但其目的是引入一组适配器将一个BC依赖的BC的变化所产生的影响降至最低,即反腐层。
    可以看到,当我们在列表中单击时,两个BC之间的合作水平逐渐降低。使用已发布的语言,我们从BC开始建立它们可以交互的通用标准。他们都不拥有这种语言,而是由他们所居住的企业拥有(甚至可能是行业标准)。使用开放主机,我们仍然做的不错;BC提供了作为运行时服务的功能,供任何其他BC调用,但随着服务的发展,它将(可能)保持向后兼容性。

图 2:有界上下文关系的频谱

然而,当我们开始循规蹈矩时,我们只是生活在我们身边;一个 BC 显然是从属于另一个的。如果我们必须与以百万美元购买的总系统集成,那很可能就是我们所希望的情况。如果我们使用反腐败层, 那么我们通常会与遗留系统集成,但引入一个额外的一层来尽可能地将我们自己隔离开来。当然,这需要花钱来实施,但它降低了依赖性风险。反腐败层也比重新实施该遗留系统便宜得多,这充其量会分散我们对核心领域的注意力,最坏的情况是以失败告终。

DDD 建议我们绘制一个上下文映射来识别我们的 BC 以及我们依赖或依赖的那些,识别这些依赖的性质。图 3 显示了我过去 5 年左右一直在研究的系统的上下文映射。
image
图 3:上下文映射示例

所有这些关于上下文映射和 BC 的讨论有时被称为战略 DDD,这是有充分理由的。毕竟,当你想到它时,弄清楚 BC 之间的关系都是非常政治化的:我的系统将依赖哪些上游系统,我是否容易与它们集成,我是否对它们有影响力,我是否信任它们?下游也是如此:哪些系统将使用我的服务,我如何将我的功能公开为服务,他们是否对我有影响力?误解这一点,您的应用程序很容易失败。

层和六边形

现在让我们转向内部,考虑我们自己的 BC(系统)的架构。从根本上说,DDD 只真正关心领域层,实际上,它并没有对其他层有很多话要说:表示层、应用程序或基础设施(或持久层)。但它确实希望它们存在。这就是分层架构模式。
image

图 4:分层架构

当然,我们多年来一直在构建多层系统,但这并不意味着我们一定很擅长。确实,过去的一些主导技术 - 比如,EJB2,- 对域模型可以作为有意义的层存在的想法产生了积极的危害。所有的业务逻辑似乎都渗入了应用层或(甚至更糟的)表示层,留下了一组贫乏的领域类 [3] 作为数据持有者的空壳。这不是 DDD 的内容。

所以,绝对清楚,应用层不应该有任何域逻辑。相反,应用层负责诸如事务管理和安全性之类的事情。在某些体系结构中,它还可能负责确保从基础结构/持久层检索的域对象在与交互之前正确初始化(尽管我更喜欢基础结构层来代替)。

当表示层在单独的内存空间中运行时,应用层还充当表示层和域层之间的中介。表示层通常处理域对象或域对象(数据传输对象,或 DTO)的可序列化表示,通常每个“视图”一个。如果这些被修改,则表示层将任何更改发送回应用层,应用层又确定已修改的域对象,从持久层加载它们,然后将更改转发到这些域对象。

分层架构的一个缺点是它暗示了依赖关系的线性堆叠,从表示层一直到基础设施层。但是,我们可能希望在表示层和基础设施层中支持不同的实现。如果(我认为我们是!)我们想要测试我们的应用程序,那就肯定是这种情况:

  • 例如,FitNesse [4] 等工具允许我们从最终用户的角度验证系统的行为。但是这些工具一般不经过表示层,而是直接进入下一层,即应用层。所以从某种意义上说,FitNesse 充当了另一种观看者的角色。
  • 同样,我们很可能有多个持久性实现。我们的生产实现可能使用 RDBMS 或类似技术,但对于测试和原型设计,我们可能有一个轻量级实现(甚至可能在内存中),因此我们可以模拟持久性。
    我们可能还想区分“内部”和“外部”层之间的交互,其中内部我的意思是两个层都完全在我们的系统(或 BC)内的交互,而外部交互则跨越 BC。
    因此,与其将我们的应用程序视为一组层,不如将其视为六边形 [5],如图 5 所示。我们最终用户使用的查看器以及 FitNesse 测试使用内部客户端API(或端口),而来自其他 BC 的调用(例如用于开放主机交互的 RESTful,或用于已发布语言交互的 ESB 适配器调用)命中外部客户端端口。对于后端基础设施层,我们可以看到替代对象存储实现的持久端口,此外,我们域层中的对象可以通过外部服务端口调用其他 BC。
    image

图 5:六边形架构

这种大规模的东西已经足够了,让我们多干实事,少扯虚的.

posted @ 2021-08-25 15:37  easonChen  阅读(1131)  评论(0编辑  收藏  举报