「首席架构看领域驱动设计」领域驱动的设计和开发最佳实践(下)

简介: 「首席架构看领域驱动设计」领域驱动的设计和开发最佳实践

缓存

当我们讨论域层的状态(数据)时,我们必须讨论缓存的方面。频繁访问的域数据(如按揭贷款处理应用程序中的产品和利率)是很好的缓存候选者。缓存可以提高性能并减少数据库服务器上的负载。服务层是缓存域状态的理想选择。像TopLink和Hibernate这样的ORM框架也提供了数据缓存。

贷款处理示例应用程序使用JBossCache框架来缓存产品和费率细节,以最小化数据库调用并提高应用程序性能。

事务管理

事务管理对于保持数据完整性和提交或回滚UOW非常重要。关于在应用程序体系结构层中应该在何处管理事务,一直存在争议。还有跨实体事务(跨越同一UOW中的多个域对象),它们影响应该在何处管理事务的设计决策。

有些开发人员喜欢在DAO类中管理事务,这是一个糟糕的设计。这导致了过于细粒度的事务控制,这没有提供管理事务跨多个域对象的用例的灵活性。服务类应该处理事务;这样,即使事务跨越多个域对象,服务类也可以管理事务,因为在大多数用例中,服务类处理控制流。

示例应用程序中的FundingServiceImpl类管理资金请求的事务,并通过调用存储库执行多个数据库操作,并在单个事务中提交或回滚所有数据库更改。

数据传输对象

DTO也是SOA环境中设计的一个重要部分,在SOA环境中,域对象模型在结构上与从业务服务接收和发送的消息不兼容。消息通常在XML模式定义文档(XSD)中定义和维护,从XSD中编写(或代码生成)DTO对象并将其用于域和SOA服务层之间的数据(消息)传输是一种常见的实践。在分布式应用程序中,将数据从一个或多个域对象映射到一个DTO将成为一个必要的麻烦,因为从性能和安全角度来看,通过网络发送域对象可能并不实际。

从DDD的角度来看,DTO还有助于维护服务层和UI层之间的分离,其中DO用于域,服务层用于表示层,DTO用于表示层。

Dozer框架用于将一个或多个域对象组装到一个DTO对象中。它是双向的,这节省了大量额外的代码和时间转换域对象到DTO的,反之亦然。DO和DTO对象之间的双向映射有助于消除单独的DO -> DTO和DTO -> DO转换逻辑。框架还正确处理类型和数组转换。

当请求进入资金处理时,样例应用程序使用Dozer映射文件(XML)将FundingRequestDTO对象分割为贷款、借款人和FundingRequest实体对象。该映射还负责将来自实体的资金响应数据聚合到返回客户端的单个DTO对象中。

DDD实施框架

Spring和Real Object Oriented (ROO)、Hibernate等框架有助于设计和实现域模型。其他支持DDD实现的框架有JMatter、Naked Objects、Ruby On Rails、Grails和Spring Modules XT框架。

Spring负责实例化和连接域类(如服务、工厂和存储库)。它还使用@ configurationannotation将服务注入实体。该注释是特定于Spring的,因此实现此注入的其他选项是使用诸如Hibernate拦截器之类的东西。

ROO是一个建立在“领域第一,基础设施第二”理念上的DDD实现框架。开发该框架是为了减少web应用程序开发中模式的样板代码。在使用ROO时,我们定义域模型,然后框架(基于Maven原型)为模型-视图-控制器(MVC)、DTO、业务层Facade和DAO层生成代码。它甚至为单元测试和集成测试生成存根。

ROO有一些非常实用的实现模式。例如,它区分状态管理的字段,持久层使用字段级访问,公共构造函数只反映强制字段。

开发

没有实际的实现,模型是没有用的。实现阶段应该包括尽可能多地自动化开发任务。要查看哪些任务可以自动化,让我们来看一个涉及域模型的典型用例。以下是用例中的步骤列表:

请求:

  • 客户端调用Facade类,以XML文档的形式发送数据(与XSD兼容);Facade类为UOW启动一个新的事务。
  • 对输入的数据运行验证。这些验证包括主要的(基本的/数据类型/字段级别的检查)和业务验证。如果存在任何验证错误,则提出适当的异常。
  • 将描述翻译成代码(对域友好)。
  • 使数据格式更改对域模型友好。
  • 对属性进行任何分离(例如将客户名拆分为customer实体对象中的first和last name属性)。
  • 将DTO数据分解为一个或多个域对象。
  • 持久化域对象的状态。

响应:

  • 从数据存储中获取域对象的状态。
  • 必要时缓存状态。
  • 将域对象组装到应用程序友好的数据对象(DTO)中。
  • 对数据元素进行任何合并或分离(例如将姓和名合并到单个客户名属性中)。
  • 把代码翻译成描述。
  • 对数据格式进行必要的更改,以满足客户端数据使用需求。
  • 必要时缓存DTO状态
  • 当控制流退出时,事务提交(或回滚)。

下表显示了在应用程序中将数据从一个层传送到另一个层的不同对象。

表3. 数据流经应用程序层


正如您所看到的,在应用程序架构中有几个层,其中相同的数据以不同的形式(DO、DTO、XML等)流动。这些包含数据和其他类(如DAO、DAOImpl和DAOTest)的大多数对象(Java或XML)本质上都是基础结构。这些具有样板代码和结构的类和XML文件非常适合用于代码生成。

代码生成

ROO之类的框架还为新项目创建了一个标准的、一致的项目模板(使用Maven插件)。使用预先生成的项目模板,我们可以在目录结构中实现一致性,在哪里存储源和测试类、配置文件,以及内部和外部(第三方)组件库的依赖性。

当我们考虑到开发一个典型的企业软件应用程序需要大量的类和配置文件时,这可能会让人难以承受。代码生成是解决这个问题的最佳方法。代码生成工具通常使用某种模板框架来定义模板或映射,代码生成器可以从这些模板或映射生成代码。Eclipse Modeling Framework (EMF)有几个子项目,可以帮助生成web应用程序项目中所需的各种构件的代码。像AndroMDA这样的模型驱动架构(Model Driven Architecture, MDA)工具使用EMF根据架构模型生成代码。

当涉及到在域层中编写委托类时,我看到开发人员手动编写这些类(主要是从头开始编写第一个类,然后按照“复制和粘贴”模式为其他域对象创建所需的委托类。由于大多数这些类基本上都是域类的外观,所以它们是代码生成的良好候选对象。代码生成选项是一个很好的长期解决方案,即使它涉及一些初始投资(在编码和时间方面)来构建和测试代码生成器(引擎)。

对于生成的测试类,一个好的选择是为需要进行单元测试的主类中具有复杂业务逻辑的方法创建抽象方法。通过这种方式,开发人员可以扩展生成的基本测试类,并实现不能自动生成的自定义业务逻辑。对于任何具有不能自动创建的测试逻辑的测试方法都是一样的。

脚本语言是编写代码生成器的更好选择,因为它们的开销更少,并且支持模板创建和自定义选项。如果我们利用DDD项目中的代码生成,我们只需要从头开始编写几个类。必须从头创建的工件包括:

  • XSD
  • 域对象
  • 服务

一旦我们定义了XSD和Java类,我们就可以通过代码生成以下所有或大部分类和配置文件:

  • DAO接口和实现类
  • 工厂
  • 存储库
  • 域委托(如果需要)
  • Facade(包括EJB和web服务类)
  • DTO的
  • 以上类的单元测试(包括测试类和测试数据)
  • Spring配置文件

下面的表4列出了web应用程序体系结构中的不同层,以及可以在该层生成什么工件(Java类或XML文件)。

表4:DDD实现项目中的代码生成


委托层是唯一同时具有领域对象和DTO知识的层。其他层,如持久层,应该不知道DTO的。

重构

重构是在不改变应用程序的功能或行为的情况下改变或重组应用程序代码。重构可以与设计或代码相关。进行设计重构是为了不断地细化模型并重构代码以改进域模型。

重构在DDD项目中扮演着重要的角色,因为它具有领域建模的迭代和进化性质。将重构任务集成到项目中的一种方法是在调用迭代完成之前将其添加到项目的每个迭代中。理想情况下,重构应该在每个开发任务之前和之后进行。

重构应该严格遵守规则。结合使用重构、CI和单元测试来确保代码更改不会破坏任何功能,同时这些更改确实有助于预期的代码或性能改进。

自动化测试在重构应用程序代码中起着至关重要的作用。如果没有良好的自动化开发人员测试和测试驱动开发(TDD)实践,重构可能会适得其反,因为没有自动的方法来验证作为重构工作一部分的设计和代码更改不会改变行为或破坏功能。

Eclipse之类的工具可以帮助以迭代的方式实现域模型,并将重构作为开发工作的一部分。Eclipse具有诸如提取或将方法移动到不同的类或将方法下推到子类等特性。还有一些Eclipse的代码分析插件可以帮助管理代码依赖项和识别DDD反模式。当我对项目进行设计和代码评审时,我依赖JDepend、Classycle和Metrics等插件来评估应用程序中域和其他模块的质量。

Chris Richardson谈到了使用Eclipse提供的重构特性,应用代码重构将过程设计转换为面向对象设计。

单元测试/持续集成

我们前面谈到的目标之一是,域类应该是单元可测试的(在初始开发期间以及稍后重构现有代码时),而不需要对容器或其他基础结构代码有太多依赖。TDD方法帮助团队在项目的早期发现任何设计问题,并验证代码是否与域模型一致。DDD对于测试优先的开发是理想的,因为状态和行为包含在域类中,并且应该很容易对它们进行隔离测试。重要的是测试域模型的状态和行为,而不是过多地关注数据访问或持久性的实现细节。

像JUnit或TestNG这样的单元测试框架是实现和管理域模型的好工具。其他测试框架,如DBUnit和Unitils,也可以用来测试域层,特别是将测试数据注入到DAO类中。这将最小化为在单元测试类中填充测试数据而编写的额外代码。

模拟对象还有助于在隔离状态下测试域对象。但是重要的是不要在域层中疯狂地使用模拟对象。如果有其他测试域类的简单方法,您应该使用这些选项,而不是使用模拟对象。例如,如果您可以使用后端中真实的DAO类(而不是模拟DAO实现)和内存中的HSQL数据库(而不是真实数据库)来测试实体类;它将使域层单元测试运行得更快,这是使用模拟对象背后的主要思想。这样,您将测试域对象之间的协作(交互)以及它们之间交换的状态(数据)。对于模拟对象,我们将只测试域对象之间的交互。

一旦开发任务完成,在开发阶段创建的所有单元和集成测试(使用或不使用TDD实践)都将成为自动化测试套件的一部分。应该在本地和更高的开发环境中频繁地维护和执行这些测试,以确定新代码更改是否将任何bug引入了域类。

Eric Evans在他的书中谈到了CI,他说CI工作应该总是在有限的上下文中应用,它应该包括人和代码的同步。CI工具比如CruiseControl和哈德逊可以用来建立一个自动构建和测试环境中运行应用程序构建脚本(使用Ant或Maven这样的构建工具创建)检出代码从SCM存储库(如CVS, Subversion等),编译域类(以及其他类的应用程序),如果没有构建错误,然后自动运行所有的测试(单元测试和集成)。如果有任何构建或测试错误,也可以设置CI工具来通知项目团队(通过电子邮件或RSS提要)。

部署

域模型从不是静态的;它们随着项目生命周期中业务需求的演进和新项目中出现的新需求而变化。另外,在开发和实现域模型时,您需要不断地学习和改进,并希望将新知识应用到现有的模型中。

在打包和部署域类时,隔离是关键。由于域层的一端依赖于DAO层,另一端依赖于服务Facade层(参见图2中的应用程序体系结构关系图),因此将域类打包并部署为一个或多个模块以优雅地管理这些依赖关系非常有意义。

虽然DI、AOP和工厂等设计模式在设计时最小化了对象之间的耦合并使应用程序模块化,但OSGi(以前称为开放服务网关计划)在运行时解决了模块化问题。OSGi正在成为打包和分发企业应用程序的标准机制。它很好地处理了模块之间的依赖关系。我们还可以使用OSGi进行域模型版本控制。

我们可以将DAO类打包在一个OSGi包中(DAO包),将服务facade类打包在另一个包中(服务包),因此当修改DAO或服务实现或部署应用程序的不同版本时,由于OSGi,不需要重新启动应用程序。如果为了向后兼容而必须支持某些域对象的现有版本和新版本,我们还可以部署同一个域类的两个不同版本。

为了利用OSGi的功能,应用程序对象在被使用之前必须在OSGi平台上注册(也就是说,在客户端对它们进行查找之前)。这意味着我们必须使用OSGi api来进行注册,但是我们还必须在服务启动和停止使用OSGi容器时处理故障场景。Spring Dynamic Modules框架通过允许在应用程序中导出和导入任何类型的对象而不需要修改任何代码,在这方面提供了帮助。

Spring DM还提供了在容器外运行OSGi集成测试的测试类。例如,AbstractOsgiTests可用于直接从IDE运行集成测试。设置由测试基础结构处理,因此我们不必编写清单。MF文件进行测试,或做任何打包或部署。该框架支持当前可用的大多数OSGi实现(Equinox、Knopflerfish和Apache Felix)。

贷款处理应用程序使用OSGi、Spring DM和Equinox容器来管理模块级依赖项以及域和其他模块的部署。LoanAppDeploymentTests展示了Spring DM测试模块的使用。

示例应用程序设计

贷款处理样本申请中使用的域类如下:

实体:

  • Loan
  • Borrower
  • UnderwritingDecision
  • FundingRequest

值对象:

  • ProductRate
  • State

服务:

  • FundingService

存储库:

  • LoanRepository
  • BorrowerRepository
  • FundingRepository

图3显示了示例应用程序的域模型图。

图片


图3。分层应用程序域模型(单击屏幕快照打开全尺寸视图)。

本文中讨论的大多数DDD设计概念和技术都应用于示例应用程序。使用了诸如DI、AOP、注释、域级安全性和持久性等概念。此外,我还使用了几个开源框架来帮助完成DDD开发和实现任务。这些框架如下:

  • Spring
  • Dozer
  • Spring Security
  • JAXB (Spring-WS for marshalling and unmarshalling the data)
  • Spring Testing (for unit and integration testing)
  • DBUnit
  • Spring Dynamic Modules

样例应用程序中的域类使用Equinox和Spring DM框架部署为一个OSGi模块。下表显示了示例应用程序的模块打包细节。

表5所示。打包和部署细节


结论

DDD是一个强大的概念,它将改变建模人员、架构师、开发人员和测试人员在团队接受了DDD培训并开始应用“领域第一,基础设施第二”的理念之后看待软件的方式。不同利益相关者(从IT和业务单位)与不同背景和领域的专业知识参与域建模、设计和实现工作,引用Eric Evans,“重要的是不要模糊的哲学之间的线路设计(DDD)和技术工具框,帮助我们完成它(OOP, DI和AOP)”。

推进前沿

本节介绍一些影响DDD设计和开发的新方法。其中一些概念仍在发展中,看看它们将如何影响DDD将是很有趣的。

体系结构规则和契约实施设计在域模型标准和实现最佳实践的治理和策略实施中扮演重要角色。Ramnivas谈到了使用方面来执行只通过工厂创建存储库对象的规则;这是一个容易违反设计规则在领域层。

领域特定语言(DSL)和业务自然语言(BNL)近年来受到越来越多的关注。可以使用这些语言表示域类中的业务逻辑。BNL的强大之处在于,它们可以用来捕获业务规范、记录业务规则,以及作为可执行代码。它们还可以用来创建测试用例,以验证系统是否按预期工作。

行为驱动开发(BDD)是最近讨论的另一个有趣的概念。BDD通过提供跨越业务和技术之间的鸿沟的公共词汇表(普遍存在的语言),帮助将开发重点放在交付优先级高的、可验证的业务价值上。通过使用关注于系统的行为方面而不是测试方面的术语,BDD试图帮助开发人员将注意力集中在TDD最成功的地方的真正价值上。如果正确地实践,BDD可以成为DDD的一个很好的补充,在DDD中,领域对象的开发受到BDD概念的积极影响;毕竟,所有的域对象都应该封装状态和行为。

事件驱动架构(EDA)是另一个可以在领域驱动设计中发挥作用的领域。例如,用于通知域对象实例中的任何状态更改的事件模型将有助于处理需要在域对象的状态更改时触发的事件后处理任务。EDA有助于封装基于事件的逻辑,从而避免嵌入到核心域逻辑中。Martin Fowler记录了关于域事件设计模式的内容。

资源

  • 领域驱动设计,解决软件核心的复杂性,Eric Evans, Addison Wesley
  • 应用领域驱动的设计和模式,Jimmy Nilsson, Addison Wesley
  • 《重构到模式》,Joshua Kerievsky, Addison Wesley著
  • DDD可以在没有DI和AOP的情况下充分实现吗?
相关文章
|
3天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
7天前
|
容灾 网络协议 数据库
云卓越架构:云上网络稳定性建设和应用稳定性治理最佳实践
本文介绍了云上网络稳定性体系建设的关键内容,包括面向失败的架构设计、可观测性与应急恢复、客户案例及阿里巴巴的核心电商架构演进。首先强调了网络稳定性的挑战及其应对策略,如责任共担模型和冗余设计。接着详细探讨了多可用区部署、弹性架构规划及跨地域容灾设计的最佳实践,特别是阿里云的产品和技术如何助力实现高可用性和快速故障恢复。最后通过具体案例展示了秒级故障转移的效果,以及同城多活架构下的实际应用。这些措施共同确保了业务在面对网络故障时的持续稳定运行。
|
10天前
|
运维 监控 BI
卓越架构之FinOps最佳实践
本文探讨了云成本管理的趋势和FinOps的最佳实践。随着云计算的普及,传统的IT管理模式已无法适应按需使用和按量付费的新模式,导致企业面临资源浪费和成本失控的风险。FinOps作为一种管理理念,强调运维、财务和技术团队的合作,通过数据驱动和业务价值驱动的方式优化云成本。文章介绍了FinOps的核心挑战、最佳实践及技术工具的应用,帮助企业有效管理和优化云成本,实现降本增效。
|
14天前
|
Kubernetes 安全 数据安全/隐私保护
云卓越架构:容器安全最佳实践
本次分享由阿里云智能集团解决方案架构师张玉峰主讲,主题为“云卓越架构:容器安全最佳实践”。内容涵盖容器安全的挑战、云原生容器安全架构及典型场景。首先分析了容器安全面临的问题,如镜像漏洞和权限管理。接着介绍了容器安全架构的五个维度:身份权限管理、配置安全检查、运行时防护、镜像安全检测及发布的安全管控。最后通过具体场景展示了容器身份与权限管理、密钥管理、运行时防入侵等最佳实践,强调了安全左移的重要性,确保从开发到运行的全生命周期安全覆盖。
|
23天前
|
机器学习/深度学习 前端开发 算法
婚恋交友系统平台 相亲交友平台系统 婚恋交友系统APP 婚恋系统源码 婚恋交友平台开发流程 婚恋交友系统架构设计 婚恋交友系统前端/后端开发 婚恋交友系统匹配推荐算法优化
婚恋交友系统平台通过线上互动帮助单身男女找到合适伴侣,提供用户注册、个人资料填写、匹配推荐、实时聊天、社区互动等功能。开发流程包括需求分析、技术选型、系统架构设计、功能实现、测试优化和上线运维。匹配推荐算法优化是核心,通过用户行为数据分析和机器学习提高匹配准确性。
76 3
|
21天前
|
前端开发 搜索推荐 安全
陪玩系统架构设计陪玩系统前后端开发,陪玩前端设计是如何让人眼前一亮的?
陪玩系统的架构设计、前后端开发及前端设计是构建吸引用户、功能完善的平台关键。架构需考虑用户需求、技术选型、安全性等,确保稳定性和扩展性。前端可选用React、Vue或Uniapp,后端用Spring Boot或Django,数据库结合MySQL和MongoDB。功能涵盖用户管理、陪玩者管理、订单处理、智能匹配与通讯。安全性方面采用SSL加密和定期漏洞扫描。前端设计注重美观、易用及个性化推荐,提升用户体验和平台粘性。
53 0
|
1月前
|
运维 监控 Java
后端开发中的微服务架构实践与挑战####
在数字化转型加速的今天,微服务架构凭借其高度的灵活性、可扩展性和可维护性,成为众多企业后端系统构建的首选方案。本文深入探讨了微服务架构的核心概念、实施步骤、关键技术考量以及面临的主要挑战,旨在为开发者提供一份实用的实践指南。通过案例分析,揭示微服务在实际项目中的应用效果,并针对常见问题提出解决策略,帮助读者更好地理解和应对微服务架构带来的复杂性与机遇。 ####
|
1月前
|
消息中间件 运维 安全
后端开发中的微服务架构实践与挑战####
在数字化转型的浪潮中,微服务架构凭借其高度的灵活性和可扩展性,成为众多企业重构后端系统的首选方案。本文将深入探讨微服务的核心概念、设计原则、关键技术选型及在实际项目实施过程中面临的挑战与解决方案,旨在为开发者提供一套实用的微服务架构落地指南。我们将从理论框架出发,逐步深入至技术细节,最终通过案例分析,揭示如何在复杂业务场景下有效应用微服务,提升系统的整体性能与稳定性。 ####
46 1
|
1月前
|
消息中间件 运维 API
后端开发中的微服务架构实践####
本文深入探讨了微服务架构在后端开发中的应用,从其定义、优势到实际案例分析,全面解析了如何有效实施微服务以提升系统的可维护性、扩展性和灵活性。不同于传统摘要的概述性质,本摘要旨在激发读者对微服务架构深度探索的兴趣,通过提出问题而非直接给出答案的方式,引导读者深入
46 1
|
1月前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。