DDD系列:三、Repository模式

为什么需要Repository?

Anemic Domain Model(贫血领域模型)特征:

  1. 有大量的XxxDO对象:这里DO虽然有时候代表了Domain Object,但实际上仅仅是数据库表结构的映射,里面没有包含(或包含了很少的)业务逻辑;
  2. 服务和Controller里有大量的业务逻辑:比如校验逻辑、计算逻辑、格式转化逻辑、对象关系逻辑、数据存储逻辑等;
  3. 大量的Utils工具类等。

Anemic Domain Model的缺陷:

  1. 无法保护模型对象的完整性和一致性:因为对象的所有属性都是公开的,只能由调用方来维护模型的一致性,而这个是没有保障的;之前曾经出现的案例就是调用方没有能维护模型数据的一致性,导致脏数据使用时出现bug,这一类的bug还特别隐蔽,很难排查到。
  2. 对象操作的可发现性极差: 单纯从对象的属性上很难看出来都有哪些业务逻辑,什么时候可以被调用,以及可以赋值的边界是什么;比如说,Long类型的值是否可以是0或者负数?
  3. 代码逻辑容易重复: 比如校验逻辑、计算逻辑,都很容易出现在多个服务、多个代码块里,提升维护成本和bug出现的概率;一类常见的bug就是当贫血模型变更后,校验逻辑由于出现在多个地方,没有能跟着变,导致校验失败或失效。
  4. 代码的健壮性差:比如一个数据模型的变化可能导致从上到下的所有代码的变更。

充血模型中,需要严格区分的两个模型:

  • 数据模型(Data Model):指业务数据该如何持久化,以及数据之间的关系,也就是传统的ER模型。只属于Infrastructure Layer
  • 业务模型/领域模型(Domain Model):指业务逻辑中,相关联的数据该如何联动。只属于Domain Layer

链接了这两层的关键对象,就是Repository。

Repository的价值

   在传统的数据库驱动开发中,我们会对数据库操作做一个封装,一般叫做Data Access Object(DAO)。DAO的核心价值是封装了拼接SQL、维护数据库连接、事务等琐碎的底层逻辑,让业务开发可以专注于写代码。但是在本质上,DAO的操作还是数据库操作,DAO的某个方法还是在直接操作数据库和数据模型,只是少写了部分代码。

   所以,Repository 的价值是:

  1. 向内层,屏蔽存储相关的逻辑:
    • 业务逻辑(DomianService) 和 DB相关的操作(DAO、DB)隔离开,业务逻辑中不关心数据存储形式和技术
  2. 向外层,屏蔽业务变化
    • 业务迭代时,如果不调整DB字段,仓储层不变化。

模型对象代码规范

模型类型:

3种模型的区别,Entity、Data Object (DO)和Data Transfer Object (DTO):

  • DO(数据对象)

    ​ 在DDD的规范里,DO应该仅仅作为数据库物理表格的映射,不能参与到业务逻辑中。为了简单明了,DO的字段类型和名称应该和DB中的字段类型和名称一一对应。(当然,实际上也没必要一摸一样,只要你在Mapper那一层做到字段映射)

  • Entity(实体对象)

    ​ 实体对象是我们正常业务应该用的业务模型,它的字段和方法应该和业务语言保持一致,和持久化方式无关。也就是说,Entity 和 DO 很可能有着完全不一样的字段命名和字段类型,甚至嵌套关系。Entity的生命周期应该仅存在于内存中,不需要可序列化和可持久化

  • DTO(传输对象):

    ​ 主要作为Application Layer 的入参和出参,比如CQRS里的Command、Query、Event,以及Request、Response等都属于DTO的范畴。DTO的价值在于适配不同的业务场景的入参和出参,避免让业务对象变成一个万能大对象。该对象需要序列化,其余两模型都不能实现序列化接口。

模型对象间的关系:

  • 复杂的Entity拆分多个DO对象
  • 多个关联的 Entity 合并一个 DO
  • 从复杂 Entity 里抽取部分信息形成一个 DTO 列表
  • 从多个 Entity 中提取信息,输出一个 DTO

模型转化器

请添加图片描述

DTO Assembler:

​ 在 Application 层,Entity 与 DTO 之间的转化器有一个标准的名称叫DTO Assembler,他的核心作用就是将1个或多个相关联的Entity转化为1个或多个DTO。

Data Converter:

​ 在Infrastructure层,Entity 与 DO 的转化器没有一个标准名称,但是为了区分 Data Mapper(Mybatis的Mapper),我们叫这种转化器Data Converter。

带来的好处:

​ 通过抽象出一个 Assembler/Converter 对象,把复杂的转化逻辑都收敛到一个对象中,并且可以很好的进行单元测试。

带来的问题:

  • 当业务复杂时,手写 Assembler/Converter 是一件耗时且容易出bug的事情,所以业界会有多种Bean Mapping的解决方案,从本质上分为动态和静态映射。
    • 动态映射根据反射动态赋值,大量的反射调用,将带来性能问题。
    • 推荐使用 MapStruct(MapStruct官网),该组件通过注解,在编译时静态生成映射代码,其最终编译出来的代码和手写的代码在性能上完全一致,且有强大的注解等能力。
  • 从使用复杂度角度来看,区分了DO、Entity、DTO带来了代码量的膨胀(从1个变成了3+2+N个)。但是在实际复杂业务场景下,通过功能来区分模型带来的价值是功能性的单一和可测试、可预期,最终反而是逻辑复杂性的降低。

Repository代码规范

​ Repository 出现的目的,是为了将 业务逻辑(软件) 与 硬件(DB、Cache、文件系统等)和 固件(与硬件强关联的软件)完全隔离开。所以为了体现出软件的特点,Repository 中需要注意以下三点:

  1. **接口名称不应该使用底层实现的语法:**常见的insertselectupdatedelete都属于SQL语法,使用这几个词相当于和DB底层实现做了绑定,在 Repository 为了区分开,我们使用语法如findsaveinsertupdate)、remove
  2. **出参入参不应该使用底层数据格式:**Repository 操作的是 Entity 对象(实际上应该是Aggregate Root),不应该直接操作 DO。Repository 接口存在于 Domain 层,实现类 建议 在 Infrastructure 层。
  3. **应该避免所谓的“通用”Repository模式:**很多ORM框架都提供一个“通用”的Repository接口,然后框架通过注解自动实现接口,比较典型的例子是Spring Data、Entity Framework等,这种框架的好处是在简单场景下很容易通过配置实现,但是坏处是基本上无扩展的可能性(比如加定制缓存逻辑),在未来有可能还是会被推翻重做。当然,这里避免通用不代表不能有基础接口和通用的帮助类。

复杂Aggregate的save

​ 在对一个复杂Aggregate(包含多个Entity)的 save 操作中,并不是所有 Aggregate 里的 Entity 都需要变更。

​ 如果不知道 Aggregate 中的变更有哪些,那么只有全量更新,导致大量的无用DB操作;如果我们可以追踪到变更的 Entity,那么将可以减少很多无用的 DB 操作。

业界目前有两个主流的变更追踪方案:

  1. 基于Snapshot的方案:当数据从DB里取出来后,在内存中保存一份snapshot,然后在数据写入时和snapshot比较。常见的实现如Hibernate
  2. 基于Proxy的方案:当数据从DB里取出来后,通过weaving的方式将所有setter都增加一个切面来判断setter是否被调用以及值是否变更,如果变更则标记为Dirty。在保存时根据Dirty判断是否需要更新。常见的实现如Entity Framework。

方案对比:

Snapshot(推荐)Proxy
复杂程度通过cache实现,使用流程较简单通过代理标记实现,工具类较复杂,使用流程简单
内存消耗无额外内存占用
带来的问题cache与DB的数据一致性问题代理标记Dirty时,对复杂Aggregate的判断问题

Repository的迁移流程

​ 使用 Repository 模式最大的收益就是可以彻底和底层固件解耦,让上层业务可以快速自发展。

假设传统方式下,查询数据只有如下两个类:

  • OrderDO:和DB一样的数据结构
  • OrderDAO:和DB交互的业务操作类

其升级流程如下:

  1. 生成Order实体类,初期字段可以和OrderDO保持一致
  2. 生成OrderDataConverter,通过MapStruct(Java Bean转换器)基本上2行代码就能完成
  3. 写单元测试,确保Order和OrderDO之间的转化100%正确
  4. 生成OrderRepository接口和实现,通过单测确保OrderRepository的正确性
  5. 将原有代码里使用了OrderDO的地方改为Order
  6. 将原有代码里使用了OrderDAO的地方都改为用OrderRepository
  7. 通过单测确保业务逻辑的一致性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/16759.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Midjourney之logo设计(建议收藏)

目录 宠物诊所的logo设计 常见的Logo类型 图形logo: 字母LOGO APP LOGO 进阶技巧 设置艺术家风格 去掉不需要的元素 ChatGPT Midjourney设计logo 聊天(国产):文心一言通义千问 绘图(国产) UI设计 ChatGP…

【谷粒商城之服务认证OAuth2.0】

本笔记内容为尚硅谷谷粒商城服务认证OAuth2.0部分 目录 一、OAuth 2.0 二、微博登录测试 1、微博登陆准备工作 2、获取微博Access Token 3、登录测试 1.添加HttpUtils工具类 2.controller 3.service 4.vo 总结 一、OAuth 2.0 OAuth: OAuth(开…

Java多线程深入探讨

1. 线程与进程2. 创建和管理线程2.1. 继承Thread类2.2. 实现Runnable接口2.3 利用Callable、FutureTask接口实现。2.4 Thread的常用方法 3. 线程同步3.1. synchronized关键字3.1.1同步代码块:3.1.2 同步方法: 3.2. Lock接口 4. 线程间通信5. 线程池5.1 使…

【Linux】管道

目录 一、前言 二、管道 1、匿名管道 1.1、基本原理 1.2、代码实现 1.3、管道的特点 1.4、基于管道的简单设计 2、命名管道 2.1、匿名管道与命名管道的区别 2.2、代码实现命名管道通信 一、前言 为了满足各种需求,进程之间是需要通信的。进程间通信的主要目…

python函数的递归调用

引入 函数既可以嵌套定义也可以嵌套调用。嵌套定义指的是在定义一个函数时在该函数内部定义另一个函数;嵌套调用指的是在调用一个函数的过程中函数内部有调用另一个函数。而函数的递归调用指的是在调用一个函数的过程中又直接或者间接的调用该函数本身。 函数递归…

PySpark基础入门(3):RDD持久化

RDD的持久化 RDD 的数据是过程数据,因此需要持久化存储; RDD之间进行相互迭代的计算,新的RDD的生成代表着旧的RDD的消失;这样的特性可以最大化地利用资源,老旧地RDD可以及时地从内存中清理,从而给后续地计…

aop切面调用失效问题排查

应用里有较多的地方访问外部供应商接口,由于公网网络不稳定或者外部接口不稳定(重启,发版,ip切换)的原因,经常报502或者504错误。为了解决HTTP调用的500报错,选择使用spring的retryable注解进行…

Pyinstaller将python文件打包成exe程序——封装LoFTR开源匹配代码

Pyinstaller将python文件打包成exe程序——封装LoFTR开源匹配代码 1.LoFTR代码下载及环境搭建 源码下载:https://github.com/bodhisatan/LoFTR-Stitch 环境搭建:按照github项目中的readme文档进行搭建即可,几乎没有遇到问题,代码…

【Unity入门】22.动态创建实例

【Unity入门】动态创建实例 大家好,我是Lampard~~ 欢迎来到Unity入门系列博客,所学知识来自B站阿发老师~感谢 (一)脚本实例化预制体对象 (1)Instantiate克隆创建对象 昨天我们学习了预制体这个概念&#…

文献阅读(50)—— Transformer 用于肺癌诊断预测

文献阅读(50)—— Transformer 用于肺癌诊断预测 文章目录 文献阅读(50)—— Transformer 用于肺癌诊断预测先验知识/知识拓展文章结构背景文章方法1. 文章核心网络结构2. Time Encoding ViT (TeViT)3. Tim…

力扣刷题2023-05-04-1——题目:2614. 对角线上的质数

题目: 给你一个下标从 0 开始的二维整数数组 nums 。 返回位于 nums 至少一条 对角线 上的最大 质数 。如果任一对角线上均不存在质数,返回 0 。 注意: 如果某个整数大于 1 ,且不存在除 1 和自身之外的正整数因子,…

Leetcode——66. 加一

💯💯欢迎来到的热爱编程的小K的Leetcode的刷题专栏 文章目录 1、题目2、暴力模拟(自己的第一想法)3、官方题解 1、题目 给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组…

不同主题增删改查系统【控制台+MySQL】(Java课设)

有很多顾客都是只要实现各种各样的增删改查系统即可,只是主题和数据库表不一样,功能都是增删改查这四个功能,做出来的效果和下面的截图是一样的,后续这样的增删改查系统的运行效果请参考下面的截图,我就不一一演示了&a…

MATLAB实现工业PCB电路板缺陷识别和检测

PCB(PrintedCircuitBoard印刷电路板)是电子产品中众多电子元器件的承载体,它为各电子元器件的秩序连接提供了可能,PCB已成为现代电子产品的核心部分。随着现代电子工业迅猛发展,电子技术不断革新,PCB密集度…

【Git】‘git‘ 不是内部或外部命令,也不是可运行的程序

一、问题 我想利用git clone命令从github上下载项目源代码,发现报错: git 不是内部或外部命令,也不是可运行的程序或批处理文件。我用cmd跑一下git命令,发现报错: 二、问题分析 这个错误提示表明您的系统中没有安装…

电脑视频删除了怎么恢复回来?很着急

案例分享:“电脑视频删除了怎么恢复回来?我是一名影楼的摄像师,我的主要工作就是拍摄婚礼视频,最近拍了一场婚礼视频,当时由于相机的内存不足,于是将宣传片等视频都导入进了电脑里面,清空摄像机…

《软件工程教程》(第2版) 主编:吴迪 马宏茹 丁万宁 第八章课后习题参考答案

第八章 面向对象技术与UML 课后习题参考答案 一、单项选择题 D (2)C (3)B (4)D (5)C (6)B (7)A (8)C&…

2023华中杯数学建模C题完整模型代码

已完成全部模型代码,文末获取。 摘要 随着工业化和城市化的快速发展,空气污染已经成为全球性的环境问题。细颗粒物(PM2.5)等污染物对人类健康、生态环境和社会经济造成了严重影响。本研究旨在深入探究影响PM2.5浓度的主要因素&a…

ESP32(二):GPIO

一.创建例程 打开命令面板&#xff1a;ctrlshiftp&#xff0c;输入&#xff1a;esp-idf:example&#xff1b;选择hello_world工程&#xff0c;点击 Create project using example hello_world&#xff0c;选择保存工程&#xff1b;工具使用代码&#xff1a; #include <stdi…

【图像分割】视觉大模型SEEM(Segment Everything Everywhere All at Once)原理解读

文章目录 摘要&#xff08;效果&#xff09;二、前言三、相关工作四、method4.1 多用途4.2 组合性4.3 交互式。4.4 语义感知 五、实验 论文地址&#xff1a;https://arxiv.org/abs/2304.06718 测试代码&#xff1a;https://github.com/UX-Decoder/Segment-Everything-Everywher…