DDD系列:二、应用架构设计演变
作用:
通过规定一个固定的架构设计,可以让团队内有一个统一的开发规范,降低沟通成本,提升效率和代码质量。
目标:
在做架构设计时,一个好的架构应该需要实现以下几个目标:
- 独立于UI:前台展示的样式可能会随时发生变化(今天可能是网页、明天可能变成console、后天是独立app),但是底层架构不应该随之而变化。
- 独立于底层数据源:无论今天你用MySQL、Oracle还是MongoDB、CouchDB,甚至使用文件系统,软件架构不应该因为不同的底层数据储存方式而产生巨大改变。
- 独立于外部依赖:无论外部依赖如何变更、升级,业务的核心逻辑不应该随之而大幅变化。
- 可测试:无论外部依赖了什么数据库、硬件、UI或者服务,业务的逻辑应该都能够快速被验证正确性。
贫血模型架构下的问题
贫血模型下的跨币种转账流程图:
一个应用最大的成本一般都不是来自于开发阶段,而是应用整个生命周期的总维护成本,所以代码的可维护性代表了最终成本。
贫血模型下,通常会在业务处理层中,将流程按方法模块进行拼接,组成一个完整的业务动作。这种写法在功能上没有什么问题,但是长久来看,有以下几个很大的问题:可维护性差、可扩展性差、可测试性差。
可维护性(当依赖变化时,有多少代码需要随之改变)
- 提高数据结构的稳定性:Service 层不能直接使用 DO,避免 DO 变化影响到 Service 层。DO类是一个纯数据结构,映射了数据库中的一个表,表结构和设计是应用的外部依赖,长远来看都有可能会改变,比如数据库要做 Sharding,或者换一个表设计,或者改变字段名等。
- 依赖库的升级:Service 层不能直接使用 Data Access Layer(DAL)层。例如 xxxMapper 依赖 MyBatis 的实现,如果MyBatis未来升级版本,可能会造成用法的不同(可以参考iBatis升级到基于注解的MyBatis的迁移成本)。同样的,如果未来换一个ORM体系,迁移成本也是巨大的。
- 第三方服务依赖的不确定性:轻则API签名变化,重则服务不可用需要寻找其他可替代的服务。在这些情况下改造和迁移成本都是巨大的。同时,外部依赖的兜底、限流、熔断等方案都需要随之改变。
- 中间件更换:中间件的升级、变更、能力拓展等问题。
上述几点都表达的一点,需要将依赖解耦。
可扩展性(做新需求或改逻辑时,需要新增/修改多少代码)
- 数据来源、格式的变化
- 业务逻辑无法复用:贫血模型下,数据格式变更,带来的不兼容问题会导致核心业务逻辑无法复用。每个用例都是特殊逻辑的后果是最终会造成大量的if-else语句,而这种分支多的逻辑会让分析代码非常困难,容易错过边界情况,造成bug。
- 逻辑和数据存储的相互依赖:当业务逻辑增加变得越来越复杂时,新加入的逻辑很有可能需要对数据库schema或消息格式做变更。而变更了数据格式后会导致原有的其他逻辑需要一起跟着动。
可测试性(每个需求需要增加的测试用例数 与 编写测试用例的耗时)
- 设施搭建困难:当代码中强依赖了数据库、第三方服务、中间件等外部依赖之后,想要完整跑通一个测试用例需要确保所有依赖都能跑起来,这个在项目早期是及其困难的。在项目后期也会由于各种系统的不稳定性而导致测试无法通过。
- 运行耗时长
- 耦合度高:当耦合的子步骤越多时,需要的测试用例呈指数级增长。假如一段脚本中有A、B、C三个子步骤,而每个步骤有N个可能的状态,当多个子步骤耦合度高时,为了完整覆盖所有用例,最多需要有N * N * N个测试用例。
DDD模型下的架构
上面列举了贫血模型带来的问题,现我们使用DDD来尝试解决。
抽象数据存储层:引入 Repository 和 Entity
- 新建Entity模型:Account实体对象,一个实体(Entity)是拥有ID的域对象,除了拥有数据之外,同时拥有行为。Entity和数据库储存格式无关,在设计中要以该领域的通用严谨语言(Ubiquitous Language)为依据。
- 新建Repository层:对象储存接口类AccountRepository,Repository只负责Entity对象的存储和读取,而Repository的实现类完成Entity -> DO 的转换、DB存储的细节。通过加入Repository接口,底层的数据库连接可以通过不同的实现类而替换。
抽象第三方服务或中间件:引入Anti-Corruption Layer(防腐层或ACL)
ACL中可以做的事情:
- 适配器:外部依赖的数据、接口和协议并不符合内部规范,通过适配器模式,可以将数据转化逻辑封装到ACL内部,降低对业务代码的侵入。在这个案例里,我们通过封装了ExchangeRate和Currency对象,转化了对方的入参和出参,让入参出参更符合我们的标准。
- 缓存:对于频繁调用且数据变更不频繁的外部依赖,通过在ACL里嵌入缓存逻辑,能够有效的降低对于外部依赖的请求压力。同时,很多时候缓存逻辑是写在业务代码里的,通过将缓存逻辑嵌入ACL,能够降低业务代码的复杂度。
- 兜底:当外部依赖稳定性较差时,可以通过 ACL 层实现兜底的功能。比如当外部依赖出问题后,返回最近一次成功的缓存或业务兜底数据。这种兜底逻辑一般都比较复杂,如果散落在核心业务代码中会很难维护,通过集中在ACL中,更加容易被测试和修改。
- 易于测试:类似于之前的Repository,ACL的接口类能够很容易的实现Mock或Stub,以便于单元测试。
- 功能开关:在某些场景下开放或关闭某个接口的功能,或者让某个接口返回一个特定的值,我们可以在ACL中配置功能开关来实现,而不会对真实业务代码造成影响。同时,使用功能开关也能让我们容易的实现Mock测试,而不需要真正物理性的关闭外部依赖。
封装业务逻辑:通过Entity、Domain Primitive和Domain Service封装
用 DP 封装跟实体无关的无状态计算逻辑:
ExchangeRate 中封装汇率计算逻辑,返回应转入的金额对象
用Entity封装单对象的有状态的行为,包括业务校验
Account实体类封装所有Account的行为,包括业务逻辑校验
用Domain Service封装多对象逻辑
AccountTransferService中提供转账接口,完成源头账户的转出、目标账户的转入能力
DDD重构结果
代码:
public class TransferServiceImplNew implements TransferService {private AccountRepository accountRepository;private AuditMessageProducer auditMessageProducer;private ExchangeRateService exchangeRateService;private AccountTransferService accountTransferService;@Overridepublic Result<Boolean> transfer(Long sourceUserId, String targetAccountNumber, BigDecimal targetAmount, String targetCurrency) {// 参数校验Money targetMoney = new Money(targetAmount, new Currency(targetCurrency));// 读数据Account sourceAccount = accountRepository.find(new UserId(sourceUserId));Account targetAccount = accountRepository.find(new AccountNumber(targetAccountNumber));ExchangeRate exchangeRate = exchangeRateService.getExchangeRate(sourceAccount.getCurrency(), targetMoney.getCurrency());// 业务逻辑accountTransferService.transfer(sourceAccount, targetAccount, targetMoney, exchangeRate);// 保存数据accountRepository.save(sourceAccount);accountRepository.save(targetAccount);// 发送审计消息AuditMessage message = new AuditMessage(sourceAccount, targetAccount, targetMoney);auditMessageProducer.send(message);return Result.success(true);}
}
分层结构图:
通过对外部依赖的抽象和内部逻辑的封装重构,应用整体的依赖关系变了:
一、Domain Layer:
最底层不再是数据库,而是Entity、Domain Primitive和Domain Service。这些对象**不依赖任何外部服务和框架,而是纯内存中的数据和操作。**领域层没有任何外部依赖关系。
Application Layer
再其次的是负责组件编排的Application Service,但是这些服务仅仅依赖了一些抽象出来的ACL类和Repository类,而其具体实现类是通过依赖注入注进来的。Application Service、Repository、ACL等我们统称为Application Layer(应用层)。应用层 依赖 领域层,但不依赖具体实现。
Infrastructure Layer
最后是ACL,Repository等的具体实现,这些实现通常依赖外部具体的技术实现和框架,所以统称为Infrastructure Layer(基础设施层)。Web框架里的对象如Controller之类的通常也属于基础设施层
DDD的下的架构建议
- Types模块(facade):对外暴露的Domain Primitives的地方。DP因为是无状态的逻辑,可以对外暴露,所以经常被包含在对外的API接口中,需要单独成为模块。Types模块不依赖任何类库,纯POJO。
- Domain模块(core-model 和 core-service):是核心业务逻辑的集中地,包含有状态的Entity、领域服务Domain Service、以及各种外部依赖的接口类(如Repository、ACL、中间件等)。
- Application模块(biz-shared 和 biz-service):主要包含Application Service和一些相关的类。Application模块依赖Domain模块。
- Infrastructure模块(dal 和 integration):包含了Persistence、Messaging、External等模块。比如:Persistence模块包含数据库DAO的实现,包含Data Object、ORM Mapper、Entity到DO的转化类等。
- Web模块(web):Web模块包含Controller等相关代码
代码的演进/变化速度
在传统架构中,代码从上到下的变化速度基本上是一致的,改个需求需要从接口、到业务逻辑、到数据库全量变更,而第三方变更可能会导致整个代码的重写。但在DDD中不同模块的代码的演进速度是不一样的:
- Domain 和 Application 层属于业务逻辑,属于经常被修改的地方。通过Entity能够解决基于单个对象的逻辑变更,通过Domain Service解决多个对象间的业务逻辑变更。
- Infrastructure 和 Types 层属于最低频变更的。Infrastructure 层的模块只有在外部依赖变更了之后才会跟着升级,而外部依赖的变更频率一般远低于业务逻辑的变更频率。
所以在DDD架构中,能明显看出越外层的代码越稳定,越内层的代码演进越快,真正体现了领域“驱动”的核心思想。
SOFA下的DDD划分
相关文章:
DDD系列:二、应用架构设计演变
作用: 通过规定一个固定的架构设计,可以让团队内有一个统一的开发规范,降低沟通成本,提升效率和代码质量。 目标: 在做架构设计时,一个好的架构应该需要实现以下几个目标: 独立于UI:前…...
Spring-IOC
IOC概念和原理 什么是IOC 控制反转,为了将系统的耦合度降低,把对象的创建和对象直接的调用过程权限交给Spring进行管理。 IOC底层原理 XML解析 通过Java代码解析XML配置文件或者注解得到对应的类的全路径,获取对应的Class类 Class clazz …...
基于Java语言开发B/S架构实现的云HIS
一、云HIS系统框架简介 1、技术框架 (1)总体框架: SaaS应用,全浏览器访问 前后端分离,多服务协同 服务可拆分,功能易扩展 (2)技术细节: 前端:AngularNg…...
清洁赛道新势力,米博凭“减法”突围?
在五四青年节这个特殊的日子,方太旗下的高端智能清洁品牌“米博”发布了新一代无滚布洗地机7系列。 5月4日晚,米博以“减法生活,净请7代”为主题,举办了新品发布会。在发布会上,从小红书翻红的董洁作为方太集团米博产…...
代码随想录训练营Day6| 242、349、202、1
242. 有效的字母异位词 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。 class Solution {public boolean isAnagram(String s, String t)…...
IP-GUARD如何通过网络控制策略禁止应用程序联网?
如何通过网络控制策略禁止应用程序联网? 可以在控制台-高级-网络控制中,添加以下策略: 动作:“禁止” 应用程序:填写要禁止的程序(以QQ示例) 如何禁止没有安装客户端的电脑访问客户端电脑? 可以给所有客户端设置只允许客户端电脑访问的网络控制策略; 在控制台左边的…...
Java RSA密钥转换,从RSAPrivateKey得到RSAPublicKey
概述: 在Java编程中,我们经常用到如下一段代码来生成RSA公私钥,分别拿到公私钥然后加解密计算: KeyPairGenerator keyPairGen; keyPairGen KeyPairGenerator.getInstance("RSA"); keyPairGen.initialize(2048, new S…...
Android 12.0 Launcher3仿ios长按app图标实现抖动动画开始拖拽停止动画
1.概述 在12.0的系统rom定制化开发中,在对系统原生Launcher3的定制需求中,也有好多功能定制的,在ios等电子产品中 的一些好用的功能,也是可以被拿来借用的,所以在最近的产品开发需求中,需求要求模仿ios的 功能实现长按app图标实现抖动动画,接下来看如何分析该功能的实现…...
【五一创作】50道Java面试题
Java中的四种访问权限控制符分别是什么? 答:Java中的四种访问权限控制符分别是public、protected、default和private。 Java中的反射是什么?有什么作用? 答:Java中的反射是指在程序运行时动态获取类的信息和调用对象…...
4。计算机组成原理(3)指令系统
嵌入式软件开发,非科班专业必须掌握的基本计算机知识 核心知识点:数据表示和运算、存储系统、指令系统、总线系统、中央处理器、输入输出系统 指令系统(Instruction Set)是计算机体系结构的关键组成部分之一,它定义了处…...
【Elasticsearch】NLP简单应用
文章目录 NLP简介ES中的自然语言处理(NLP)NLP演示将opennlp插件放在ESplugins路径中下载NER模型配置opennlp重启ES、验证 NLP简介 NLP代表自然语言处理,是计算机科学和人工智能领域的一个分支。它涉及使用计算机来处理、分析和生成自然语言,例如英语、中…...
3. 云计算的落地实践(下)
本章讲解知识点 云计算如何落地实践ISO镜像文件创建虚拟机入门创建数据节点配置VMWare创建虚拟机三种网络模式1. 云计算的落地实践 上一章我们讲了云计算的业界实践,即:搭建IaaS后,用于创建虚拟机,在虚拟机上部署PaaS,用于管理同时部署在虚拟机上的容器,这就是业界普遍的…...
javaEE+mysql学生竞赛管理系统
本系统是基于JAVA平台开发的一套学生竞赛信息管理的系统。系统采用JSP为编程语言。数据库采用Mysql建立数据之间的转换。论文主要介绍了本课题的开发背景,所要完成的功能和开发的过程。重点的说明了系统设计的重点、设计思想、难点技术和解决方案。 本课题的目的是使…...
车辆出险记录查询API接口
车辆出险记录接口可以帮助车主、保险公司、交通管理部门等各方快速查询车辆的出险记录,了解车辆风险情况、核算保险费用等。这篇文章将探讨车辆出险记录接口的作用、应用场景、使用方式以及一些注意事项。 作用: 车辆出险记录接口主要解决了快速获取车…...
MySQL的概念,编译及安装
一.数据库的基本概念 1、数据(Data) • 描述事物的符号记录 • 包括数字,文字,图形,图像,声音,档案记录等 • 以“记录”形式按统一的格式进行存储 2、表 • 将不同的记录组织在一起 • …...
系统性能压力测试
系统性能压力测试 一、压力测试 压力测试是给软件不断加压,强制其在极限的情况下运行,观察它可以运行到何种程度,从而发现性能缺陷,是通过搭建与实际环境相似的测试环境,通过测试程序在同一时间内或某一段时间内&…...
从零开始学习Linux运维,成为IT领域翘楚(三)
文章目录 🔥Linux超级用户与伪用户🔥Linux文件基本属性🔥Linux权限字与权限操作 🔥Linux超级用户与伪用户 Linux下用户分为三类:超级用户、普通用户、伪用户 ⭐ 超级用户:用户名为root,具有一切…...
轻松搭建自己的ChatGPT聊天机器人,让AI陪你聊天!
随着人工智能技术的发展,聊天机器人已经成为了我们生活中的一部分。无论是在客服机器人上还是智能助手上,聊天机器人都能够给我们带来真正的便利和快乐。现在,你也可以轻松搭建自己的ChatGPT聊天机器人,和它天马行空地聊天&#x…...
CompletableFutrue异步处理
异步处理 一、线程的实现方式 1. 线程的实现方式 1.1 继承Thread class ThreadDemo01 extends Thread{Overridepublic void run() {System.out.println("当前线程:" Thread.currentThread().getName());} }1.2 实现Runnable接口 class ThreadDemo02 implements …...
【前端面经】JS-对象的可枚举性
JavaScript中的对象是非常重要的数据类型,它们作为编程中的基础构建块,可以被用来表示各种数据结构。对象是由属性构成的,每个属性都包含一个名字和一个值。属性值可以是基本类型或其他对象。在JavaScript中,对象属性有许多特性&a…...
沁恒 CH32V208(三): CH32V208 Ubuntu22.04 Makefile VSCode环境配置
目录 沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟沁恒 CH32V208(三): CH32V208 Ubuntu22.04 Makefile VSCode环境配置 硬件部分 CH32V208WBU6 评估板WCH-LinkE 或 WCH-Link 硬件环境与Windows下…...
日撸 Java 三百行day38
文章目录 说明day381.Dijkstra 算法思路分析2.Prim 算法思路分析3.对比4.代码 说明 闵老师的文章链接: 日撸 Java 三百行(总述)_minfanphd的博客-CSDN博客 自己也把手敲的代码放在了github上维护:https://github.com/fulisha-ok/…...
玩转肺癌目标检测数据集Lung-PET-CT-Dx ——④转换成PASCAL VOC格式数据集
文章目录 关于PASCAL VOC数据集目录结构 ①创建VOC数据集的几个相关目录XML文件的形式 ②读取dcm文件与xml文件的配对关系③创建VOC格式数据集④创建训练、验证集 本文所用代码见文末Github链接。 关于PASCAL VOC数据集 pascal voc数据集是关于计算机视觉,业内广泛…...
两种使用 JavaScript 实现网页高亮关键字的方法
随着各种类型的信息源变得越来越多,我们常常需要通过搜索引擎来找到自己需要的信息。在搜索结果中,通常会高亮显示与我们搜索的关键词相关的内容,这样我们就能更快地找到自己需要的信息。 在本文中,我们将探讨如何使用 JavaScrip…...
【SpringBoot】SpringBoot集成ElasticSearch
文章目录 第一步,导入jar包,注意这里的jar包版本可能和你导入的不一致,所以需要修改第二步,编写配置类第三步,填写yml第四步,编写util类第五步,编写controller类第六步,测试即可 第一…...
从 Elasticsearch 到 Apache Doris,10 倍性价比的新一代日志存储分析平台
作者介绍:肖康,SelectDB 技术副总裁 导语 日志数据的处理与分析是最典型的大数据分析场景之一,过去业内以 Elasticsearch 和 Grafana Loki 为代表的两类架构难以同时兼顾高吞吐实时写入、低成本海量存储、实时文本检索的需求。Apache Doris…...
探讨Redis缓存问题及解决方案:缓存穿透、缓存击穿、缓存雪崩与缓存预热(如何解决Redis缓存中的常见问题并提高应用性能)
Redis是一种非常流行的开源缓存系统,用于缓存数据以提高应用程序性能。但是,如果我们不注意一些缓存问题,Redis也可能会导致一些性能问题。在本文中,我们将探讨Redis中的一些常见缓存问题,并提供解决方案。 一、缓存穿…...
【Python】怎么在pip下载的时候设置镜像?(常见的清华镜像、阿里云镜像以及中科大镜像)
一、清华镜像 在使用 pip 命令下载 Python 包时,可以通过设置 pip 的镜像源为清华镜像来加快下载速度。 以下是如何设置清华镜像源的步骤: 打开终端或命令行窗口执行以下命令添加清华镜像源: pip config set global.index-url https://py…...
【AI面试】目标检测中one-stage、two-stage算法的内容和优缺点对比汇总
在深度学习领域中,图像分类,目标检测和目标分割是三个相对来说较为基础的任务了。再加上图像生成(GAN,VAE,扩散模型),keypoints关键点检测等等,基本上涵盖了图像领域大部分场景了。 …...
stack、queue和priority_queue的使用介绍--C++
目录 一、stack介绍 使用方法 二、queue介绍 queue的使用 三、priority_queeue 优先级队列介绍 一、stack介绍 1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。 2. stack是作为容器…...
企业科技网站建设/公司网站设计模板
在充分了解节点计算机硬件资源的情况下进行Storm运行性能的调优。 Storm运行性能调优主要是从以下几个方面: (1)代码层面,这得看程序编写者的功力了。 (2)并行度层面,分为: setNumWorkers取值; kafkaSpout取值&am…...
bootstrap设计的精美网站/软文营销经典案例
敏捷开发 敏捷个人敏捷一词已席卷软件界。 敏捷已经远远超过了它的炒作周期。 人们对敏捷的认识日益提高,但是仍然存在一些疑问,尤其是开发人员和测试人员。 那么,敏捷有何不同?如果您是开发人员或测试人员,这有什么关…...
做旅游网站课程设计报告/5g站长工具查询
笔者为了学习PHP,WIN7系统上装了XAMPP。 默认情况下,mysql的root密码为空。在命令行下,通过mysql -u root即可进入。笔者在mysql数据库下user表中更改了root密码,退出后,发现root用户使用新设置密码登录不了。root用户…...
网站算阵地建设/百度关键词屏蔽
主要分以下步骤: 1)关闭sendmail 2)用yum安装bind-*即DNS的配置 3)yum安装postfix,配置其主配置文件/etc/postfix/main.cf 4)安装cyrus-sasl,配置/etc/sysconfig/saslauthd 5)安装dovecot并进行配置 6)安装cyrus-imapd…...
网站开发合同范本大全/重庆seo1
本来以为很简单,经常使用到的一个设置,应该没问题的,但是现在出现的问题花了我大半天的时间才解决 最开始我是这样写的: 1、直接在form_Load()事件中设置form.visiblefalse; 但是不行的,窗体还是正常显示&a…...
手机网站开发者模式/什么是整合营销概念
阿里篇(仅有问题,没有答案需要大家共同学习探讨) 如何实现一个高效的单向链表逆序输出?已知 sqrt (2)约等于 1.414,要求不用数学库,求 sqrt (2)精确到小数点后 10 位。给定一个二叉搜索树(BST),…...