ddd领域驱动设计_「首席架构看设计」权威领域驱动设计(DDD)简介

原作者:Dan Haywood

今天的企业应用程序无疑是复杂的,并依赖一些专门技术(持久性,AJAX,Web服务等)来完成它们的工作。作为开发人员,我们倾向于关注这些技术细节是可以理解的。但事实是,一个不能解决业务需求的系统对任何人都没有用,无论它看起来多么漂亮或者如何很好地构建其基础设施。

领域驱动设计(DDD)的理念 - 首先由Eric Evans在他的同名书[1]中描述 - 是关于将我们的注意力放在应用程序的核心,关注业务领域固有的复杂性本身。我们还将核心域(业务独有)与支持子域(通常是通用的,如金钱或时间)区分开来,并将更多的设计工作放在核心上。

域驱动设计包含一组用于从域模型构建企业应用程序的模式。在您的软件生涯中,您可能已经遇到过许多这样的想法,特别是如果您是OO语言的经验丰富的开发人员。但将它们一起应用将允许您构建真正满足业务需求的系统。

在本文中,我将介绍DDD的一些主要模式,了解一些新手似乎很难解决的问题,并重点介绍一些工具和资源(特别是一个),以帮助您在工作中应用DDD。

代码和模型......

使用DDD,我们希望创建问题域的模型。持久性,用户界面和消息传递的东西可以在以后出现,这是需要理解的领域,因为正在构建的系统中,可以区分公司的业务与竞争对手。 (如果不是这样,那么考虑购买包装产品)。

按模型,我们不是指图表或一组图表;确定,图表很有用,但它们不是模型,只是模型的不同视图(参见图)。不,模型是我们选择在软件中实现的概念集,以代码和用于构建交付系统的任何其他软件工件表示。换句话说,代码就是模型。文本编辑器提供了一种使用此模型的方法,尽管现代工具也提供了大量其他可视化(UML类图,实体关系图,Spring beandocs [2],Struts / JSF流等)。

8f27aa0b21122b9458bbb7cfff6a2e57.png

Figure 1: Model vs Views of the Model

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

说到语言

现在让我们看一下域驱动设计的另一个基本原则。回顾一下:我们想要构建一个捕获正在构建的系统的问题域的域模型,并且我们将在代码/软件工件中表达这种理解。为了帮助我们做到这一点,DDD提倡领域专家和开发人员有意识地使用模型中的概念进行沟通。因此,域专家不会根据屏幕或菜单项上的字段描述新的用户故事,而是讨论域对象所需的基础属性或行为。类似地,开发人员不会讨论数据库表中的类或列的新实例变量。

严格要求我们开发一种无处不在的语言。如果一个想法不能轻易表达,那么它表明了一个概念,这个概念在领域模型中缺失,并且团队共同努力找出缺失的概念是什么。一旦建立了这个,那么数据库表中的屏幕或列上的新字段就会继续显示。

像DDD一样,这种开发无处不在的语言的想法并不是一个新想法:XPers称之为“名称系统”,多年来DBA将数据字典组合在一起。但无处不在的语言是一个令人回味的术语,可以出售给商业和技术人员。现在,“整个团队”敏捷实践正在成为主流,这也很有意义。

模型和上下文......

每当我们讨论模型时,它总是在某种情况下。通常可以从使用该系统的最终用户集推断出该上下文。因此,我们有一个部署到交易员的前台交易系统,或超市收银员使用的销售点系统。这些用户以特定方式与模型的概念相关,并且模型的术语对这些用户有意义,但不一定对该上下文之外的任何其他人有意义。 DDD称之为有界上下文(BC)。每个域模型都只存在于一个BC中,而BC只包含一个域模型

我必须承认,当我第一次读到关于BC时,我看不出这一点:如果BC与域模型同构,为什么要引入一个新术语?如果只有与BC相互作用的最终用户,则可能不需要这个术语。然而,不同的系统(BC)也相互交互,发送文件,传递消息,调用API等。如果我们知道有两个BC相互交互,那么我们知道我们必须注意在一个概念之间进行转换领域和其他领域

在模型周围设置明确的边界也意味着我们可以开始讨论这些BC之间的关系。实际上,DDD确定了BC之间的一整套关系,因此当我们需要将不同的BC链接在一起时,我们可以合理地确定应该做什么:

  1. 已发布的语言:交互式BCs就共同的语言(例如企业服务总线上的一堆XML模式)达成一致,通过它们可以相互交互;
  2. 开放主机服务:BC指定任何其他BC可以使用其服务的协议(例如RESTful Web服务);
  3. 共享内核:两个BC使用一个共同的代码内核(例如一个库)作为一个通用的通用语言,否则以他们自己的特定方式执行其他的东西;
  4. 消费者/提供者:一个BC使用另一个BC的服务,并且是另一个BC的利益相关者(客户)。因此,它可以影响该BC提供的服务;
  5. 顺从者:一个BC使用另一个BC的服务,但不是其他BC的利益相关者。因此,它使用“原样”(符合)BC提供的协议或API;
  6. 反腐败层:一个BC使用另一个服务并且不是利益相关者,但旨在通过引入一组适配器 - 一个反腐败层来最小化它所依赖的BC变化的影响。

你可以看到,在这个列表中,两个BC之间的合作水平逐渐降低(见图2)。 使用已发布的语言,我们从BC建立一个他们可以互动的共同标准开始; 既不拥有这种语言,而是由他们所居住的企业所拥有(甚至可能是行业标准)。 有了开放主机,我们仍然做得很好; BC提供其作为任何其他BC调用的运行时服务的功能,但是(可能)随着服务的发展将保持向后兼容性。

34c897b491a63fa5716798fbbb0c3097.png

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

然而,当我们走向顺从时,我们只是和我们一起生活; 一个BC明显屈服于另一个。 如果我们必须与购买megabucks的总分类帐系统集成,那可能就是我们所处的情况。如果我们使用反腐败层,那么我们通常会与遗留系统集成,但是 额外的层将我们尽可能地隔离开来。 当然,这需要花钱来实施,但它降低了依赖风险。 反腐败层也比重新实现遗留系统便宜很多,这最多会分散我们对核心域的注意力,最坏的情况是以失败告终。

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

00badbd948a2481ffd937ce72289fc4c.png

Figure 3: Context Mapping Example

所有这些关于背景图和BC的讨论有时被称为战略性DDD,并且有充分的理由。 毕竟,当你想到它时,弄清楚BC之间的关系是非常政治的:我的系统将依赖哪些上游系统,我是否容易与它们集成,我是否能够利用它们,我相信它们吗? 下游也是如此:哪些系统将使用我的服务,我如何将我的功能作为服务公开,他们会对我有利吗? 误解了这一点,您的应用程序可能很容易失败。

分层和六边形

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

06a4b08ff251916af0a4a642d52561c0.png

Figure 4: Layered Architecture

当然,我们多年来一直在构建多层系统,但这并不意味着我们必须擅长它。确实,过去的一些主流技术 - 是的,EJB 2,我正在看着你! - 对域模型可以作为有意义的层存在的想法产生了积极的影响。所有的业务逻辑似乎渗透到应用层或(更糟糕的)表示层留下一组贫血的域类[3]作为数据持有者的空壳。这不是DDD的意思。

因此,要绝对清楚,应用程序层中不应存在任何域逻辑。相反,应用程序层负责事务管理和安全性等事务。在某些体系结构中,它还可能负责确保从基础结构/持久层中检索的域对象在与之交互之前已正确初始化(尽管我更喜欢基础结构层执行此操作)。

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

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

例如,FitNesse [4]等工具允许我们从最终用户的角度验证我们系统的行为。但是这些工具通常不会通过表示层,而是直接进入下一层,即应用层。所以从某种意义上说,FitNesse就是另一种观察者。

同样,我们可能有多个持久性实现。我们的生产实现可能使用RDBMS或类似技术,但是对于测试和原型设计,我们可能有一个轻量级实现(甚至可能在内存中),因此我们可以模拟持久性。

我们可能还想区分“内部”和“外部”层之间的交互,其中内部我指的是两个层完全在我们的系统(或BC)内的交互,而外部交互跨越BC。

因此,不要将我们的应用程序视为一组图层,另一种方法是将其视为六边形[5],如图5所示。我们的最终用户使用的查看器以及FitNesse测试使用内部客户端API(或端口),而来自其他BC的调用(例如,RESTful用于开放主机交互,或来自ESB适配器的调用用于已发布的语言交互)命中外部客户端端口。对于后端基础架构层,我们可以看到用于替代对象存储实现的持久性端口,此外,域层中的对象可以通过外部服务端口调用其他BC。

cbff04543a7b3100c5cf6214c16d5f90.png

Figure 5: Hexagonal Architecture

但这足够大的东西; 让我们来看看DDD在煤炭面板上的样子。

构建模块

正如我们已经注意到的,大多数DDD系统可能会使用OO范例。因此,我们的域对象的许多构建块可能很​​熟悉,例如实体,值对象和模块。例如,如果您是Java程序员,那么将DDD实体视为与JPA实体基本相同(使用@Entity注释)就足够安全了;值对象是字符串,数字和日期之类的东西;一个模块就是一个包。

但是,DDD倾向于更多地强调值对象,而不是过去习惯。所以,是的,您可以使用String来保存Customer的givenName属性的值,例如,这可能是合理的。但是一笔钱,例如产品的价格呢?我们可以使用int或double,但是(甚至忽略可能的舍入错误)1或1.0是什么意思? $ 1吗? €1? ¥1? 1分,甚至?相反,我们应该引入一个Money值类型,它封装了Currency和任何舍入规则(将特定于Currency)。

而且,值对象应该是不可变的,并且应该提供一组无副作用的函数来操作它们。我们应该写:

Money m1 = new Money("GBP
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值