领域驱动设计示例(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/

相关文章
|
5天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
2099 12
|
云安全 安全 网络安全
|
5天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
907 1
|
18天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
3055 49
|
11天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
2105 2
|
5天前
|
安全 算法 定位技术
《高性价比人生指南》完整原版:普通人用最少的资源,过好这一生的底层逻辑~How To Live Better
📖《高性价比人生指南》是一份基于实证、拒绝焦虑的生存手册,倡导以“人生时薪”为视角,重新计算时间、金钱与精力的成本与收益。它不承诺逆袭,只教人算清账:省下的钱若耗掉时间便亏;买体验比买物品更值;睡眠是最高杠杆的健康投资;关系贵在情绪价值而非表面热络;学习重在改变行为而非自我安慰。核心理念:用低成本、高回报的微小行动,换取长期的选择权——不是成为谁,而是掌控自己人生的主动权。
|
10天前
|
人工智能 JSON Linux
【保姆级】ComfyUI部署教程(下载+安装+配置+工作流,2026最新)
ComfyUI 是一款开源节点式AI生成工具,支持图像、视频、音频及3D内容创作。通过拖拽连线组合模型加载、提示词、采样等节点,构建可复用、易修改的工作流,兼容SD1.5、SDXL、Flux、Qwen Image等主流模型。
|
4天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)