开发者社区> 超努力的写代码> 正文

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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
DDD领域驱动设计实战电商活动中心重构
DDD领域驱动设计实战电商活动中心重构
142 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
9155 0
浅谈我对DDD领域驱动设计的理解
DDD的全称为Domain-driven Design,即领域驱动设计。下面我从领域、问题域、领域模型、设计、驱动这几个词语的含义和联系的角度去阐述DDD是如何融入到我们平时的软件开发初期阶段的。要理解什么是领域驱动设计,首先要理解什么是领域,什么是设计,还有驱动是什么意思,什么驱动什么。
194 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
24831 0
领域驱动设计(DDD)-简单落地
一、序言     领域驱动设计是一种解决业务复杂性的设计思想,不是一种标准规则的解决方法。在本文中的实战示例可能会与常见的DDD规则方法不太一样,是简单、入门级别,新手可以快速实践版的DDD。如果不熟悉DDD设计思想可看下基础思想篇 二、设计阶段     领域建模设计阶段常见的方法有 四色建模法、EventSourcing等 推荐一篇博文正确理解领域建
5848 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
18057 0
1946
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
OceanBase 入门到实战教程
立即下载
阿里云图数据库GDB,加速开启“图智”未来.ppt
立即下载
实时数仓Hologres技术实战一本通2.0版(下)
立即下载