领域驱动设计示例(3)

简介: 领域驱动设计示例(3)

应用架构


好的,现在让我们来看看应用程序架构。对于每个有界上下文,应该有一个单独的部署单元。这可以是Java WAR文件或EJB JAR。这取决于具体的技术实现。我们将有界上下文设计为彼此独立,并且此设计目标也应该反映在独立的部署单元中。


每个部署单元包含以下部分:

  • 领域层
  • 基础实施层
  • 应用层


域层包含我们之前在此示例中建模的基础架构独立域逻辑。基础实施层提供了与技术相关的工件,例如基于Hibernate的FreelancerRepository实现。应用层充当具有集成事务控制的业务逻辑的网关。


image.png


使用这种架构,我们的业务逻辑的域层不依赖于任何东西。我们可以将Repository实现从Hibernate更改为JPA,甚至可以将NoSQL更改为Riak或MongoDB,而不会影响任何业务逻辑。


领域层

领域层包含真实的业务逻辑,但不包含任何基础结构特定的代码。基础架构特定的实现由基础架构层提供。域模型的设计应遵循CQS(命令 - 查询 - 分离)原则的描述。可以有查询方法只返回数据而不影响状态,并且有命令方法,它们影响状态但不返回任何内容。


应用层

应用层从用户界面层获取命令,并将这些命令转换为域层上的用例调用。应用层还为业务操作提供事务控制。应用程序层负责通过Mediator或Data Transformer模式将聚合数据转换为客户特定的表示模型。


基础实施层

基础实施层为所有其他层提供基础架构相关部分,如Hibernate或JPA支持的实现。聚合数据可以存储在像Oracle或MySQL这样的RDMBS中,也可以存储为基于键值或基于文档的NoSQL引擎的XML / JSON甚至Google ProtocolBuffers序列化对象。这取决于您,只要存储提供事务控制并保证一致性。基础设施可以最好地描述为“域模型周围的一切”,因此,如果我们与其他系统交互,则数据库,文件系统资源甚至Web服务消费者。


客户端/用户界面层

客户端层使用应用程序服务并在这些服务上调用业务逻辑。每次调用都是一个新事务。


客户端层几乎可以是任何东西,从作为视图控制器的JSF 2.0 Backing Bean到SOAP Web服务端点或RESTful Web资源。甚至可以使用Swing,AWT或OpenDolphin / JavaFX来创建用户界面。

请查看UI级别的服务集成与服务器端包含(SSI)以获得想法。


上下文集成


现在我想写一下Context Integration。这是怎么回事?考虑身体租赁领域的以下要求:


  • 只有在未分配项目的情况下才能删除客户
  • 输入时间表后,需要向客户收费


同步集成


让我们从第一个开始吧。在这种情况下,客户管理有界上下文需要在删除客户之前检查是否有为给定客户注册的项目。这需要一种两种有界上下文的同步积分。


有很多机会。首先,我们希望保持上下文彼此独立。那我们该怎么处理呢?这是客户有界上下文与项目管理有界上下文交互的设计:


image.png


有一个新术语:领域服务。什么是领域服务?域服务实现了Entity,Aggregate或ValueObject无法实现的业务逻辑,因为它不属于那里。例如,如果业务逻辑调用包括跨多个域对象的操作,或者在这种情况下与另一个有界上下文集成。

ApplicationService调用CustomerService的deleteCustomerById方法。如果给定CustomerId存在项目,CustomerService将通过调用customerExists()来询问ProjectManagementAdapter。仅当它返回false时,才会从CustomerRepository中删除Customer。

ProjectManagementAdapter有两种实现方式,一种是SOAP和一种基于REST的实现。我们可以使用SOAP来使用XML编组调用完整的Web服务操作并使用完整的JAX-WS堆栈,或者我们可以使用REST并调用http://example.com/customers/customerId/projects并获取404(不是找到)或20x(确定)HTTP响应代码。这取决于您,但REST可以不那么复杂,更容易集成,也可以更好地扩展。我们也可以从REST开始,如果需要,可以切换到SOAP。在不影响域层的情况下更改实现非常容易,我们只使用适配器的另一种实现。

在Project Management Bounded Context端,有一个ApplicationWebService公开为REST资源或SOAP服务,实现了通信的服务器部分。此服务或资源委托给ProjectApplication Service,后者委托ProjectDomainService询问是否为给定的CustomerId注册了Project。



image.png


无论如何,我们必须处理交易边界。Web Service或REST资源调用不会触发开箱即用的事务,并且使用XA /两阶段提交会增加复杂性并降低可伸缩性。最好不要在物理上删除客户,而是将其标记为逻辑删除。在事务失败或并发问题的情况下,将客户恢复到其原始状态将很容易。


在这里,您还可以看到基础架构层位于所有其他层之上的原因。它必须能够根据以下层中定义的接口委托给它或实现特定于技术的工件。


一个同步例子


好的,现在我们继续一个更复杂的例子。考虑一下要求,即一旦输入时间表,就需要向客户收费。

这是一个非常有趣的。这很有趣,因为它不需要同步调用。账单可以及时发送,也可以在几个小时后或月末与其他账单一起发送。或者可以通过客户的大客户经理或其他任何方式丰富账单,Freelancer管理上下文并不关心。


我们如何使用DDD模式对此进行建模?这里的关键是“一旦时间表是......”,这是我们域中的业务相关事件,这些事件可以建模为域事件!


创建域事件并将其转发到事件存储库并存储在那里以进行进一步处理。EventStore是Bounded Context Deployment Unit的一部分,在Store中存储Event是在ApplicationService管理的运行事务下完成的。在基础结构方面,有一个Timer将存储的事件转发到最终的消息传递基础结构,例如基于JMS或AMQP,甚至可以将REST资源的调用视为消息传递。


那么为什么我们需要本地EventStore呢?好吧,消息传递基础结构可能暂时不可用,但这不应该影响我们运行的Bounded Context。因此,当基础架构再次可用时,事件将排队并交付。如果我们将消息传递基础结构直接与Event生成器耦合,则生成器可能无法在发生基础结构错误时发送。即使我们使用消息传递,如果出现问题,这可能会对整个基础架构造成连锁反应,这也是我们使用消息传递的原因:系统解耦

以下是Freelancer Management Bounded Context的建模方式:


image.png


FreelancerService创建一个TimesheetEntered域事件并将其转发到EventStore,它基本上是另一个Repository。然后,JMSMessagingAdapter从EventStore获取挂起的事件,并尝试将它们转发到目标消息传递基础结构,直到传递成功。但是这种转发在另一个事务中处理,并且可以由例如计时器触发。

好的,客户管理上下文如何处理事件?建模如下:


image.png


同样,基础架构层必须位于所有其他层,因为它必须在上下文集成调用应用程序服务的情况下。

以下是JMSMessageReceiver位于基础结构层中的来源。MessageReceiver还负责重复数据删除。这可能发生在系统故障,已经发送事件被重新传递或其他错误的情况下。由于基础架构层位于应用层之上,因此它可以调用CustomerApplicationService,CustomerApplicationService本身调用CustomerService,后者实现业务逻辑以发送账单。

在此方案中,事务边界位于ApplicationService。我们可以争辩说JMSMessageReceiver可以调用CustomerService,并围绕JMS Transaction进行。这也是一个可行的解决方案。

棘手的部分是重复数据删除。如果发生基础设施故障或系统中断,可能会发生这种情况。通过为每个事件提供唯一ID,并跟踪已处理的ID,可以避免这种情况。


另一个棘手的部分是事件排序。这取决于消息传递基础结构。如果基础设施支持事件排序,一切都很好。如果没有,这必须由我们自己实施。无论如何,将事件设计为幂等操作是一种很好的做法。这意味着每个事件都可以多次处理,并且每次都具有相同的结果而没有不必要的副作用。

查询来自多个有界上下文或聚合的数据


有时我们需要收集分布在多个聚合甚至是有界上下文的数据。这可能是一项艰巨的任务。在一个有界上下文中,我们可以使用专门的数据库视图并使用Hibernate或JPA检索数据,但是将数据分布在多个有界上下文中可能会导致许多远程方法调用和其他问题;此解决方案可能无法很好地扩展我们还要考虑使用视图可能会破坏精心设计的Aggregate的业务不变性。这是我们真正需要照顾的问题!


现在,可能是什么解决方案?我们可以考虑CQRS或Command-Query Responsibility Segregation!基本上我们将模型划分为包含业务逻辑的命令模型和用于检索数据的查询模型。因此,对于此示例,命令模型将包含我们要查询的所有有界上下文,以及查询模型,该模型用于查询聚合数据(并且被优化以有效地查询数据)。使用域事件同步命令模型和查询模型!在命令模型中触发业务操作后,将由查询模型发出并处理域事件,并更新数据。

使用CQRS,我们可以设计高性能数据处理系统,并且与商业智能集成也不再是问题。想一想:查询模型基本上可以是数据仓库。


结束语

我非常喜欢Domain-driven Design背后的想法。使用这种技术,即使非常复杂的域逻辑也可以轻松地进行提取和建模。这可以带来更好的系统,改善的用户体验以及更可靠和可维护的解决方案。感谢Eric Evans和Vaughn Vernon!DDD /域驱动设计是面向对象的编程。

原文地址:https://www.mirkosertic.de/blog/2013/04/domain-driven-design-example/

相关文章
|
22天前
|
Linux 程序员 数据格式
【2026最新】Notepad++下载、安装和使用一篇搞定(附中文版安装包)
Notepad++ 是一款免费开源、轻量高效的 Windows 文本编辑器,支持 C/Python/HTML 等 80+ 语言语法高亮、代码折叠、正则替换、编码转换及插件扩展,专为程序员与文本处理用户打造,完美替代系统记事本。(239字)
|
8天前
|
人工智能 缓存 安全
Claude Code 封号真实原因曝光,这次彻底不装了,直接针对国内开发者的账号下手?
Claude Code 封号潮背后:逆向扒出客户端隐写区域标记,Anthropic 政策收紧叠加 DeepSeek 7 月涨价,国产替代更紧迫。
|
13天前
|
人工智能 JSON 自然语言处理
让教学更智慧:用阿里云百炼工作流,自动生成中小学教材内容#小有可为#有温度的AI
通过可视化工作流编排,将大模型推理能力转化为标准化的教学内容生成引擎。教师只需输入教材标题和适用学段,即可自动获得结构完整、符合课程标准的章节内容,大幅降低备课门槛,助力教育资源均衡化。
496 127
|
17天前
|
存储 人工智能 监控
QoderWork完全指南:从入门到精通,把“AI实习生”变成你的全能工作搭档
阿里云2026年推出的桌面端AI工作助手QoderWork,不止聊天,更可动手干活:本地运行、安全可控,支持文件整理、数据分析、PPT生成、网页开发等;内置专家套件、多Agent协作与自定义Skills,让AI真正成为你身边的“AI实习生”。
|
8天前
|
人工智能 安全 程序员
终于,Claude Code 封号的原因被曝光了!竟然针对中国用户,植入隐形代码?!
通俗易懂地揭秘 Claude Code 封号的手段,分享一些自己对 AI 编程困境的思考,Codex、Cursor、DeepSeek、智谱 GLM、甚至是豆包,都有所行动了
464 1
|
9天前
|
人工智能 安全 Cloud Native
Higress 新发布:AI Gateway 能力增强,Gateway API 及其推理扩展持续打磨
增强 AI 网关能力,持续打磨 Gateway API 及其推理扩展。
393 125
|
6天前
|
人工智能 编解码 物联网
2026 最新Stable Diffusion 本地部署教程 下载安装使用详细图解(含官网安装包)
Stable Diffusion(SD)是2022年发布的开源文生图模型,由Stability AI等联合开发。支持文生图、图生图、局部重绘等,依托VAE降低算力需求,可在消费级显卡运行。本文提供秋葉aaaki制作的Windows整合包(含图形界面与插件),开箱即用,零配置启动。