DDD之DP对象的应用

定义

在领域驱动设计(Domain-Driven Design,DDD)中,“Domain Primitive”(领域原语)是指领域模型中的基本数据类型或值对象,它们代表了业务领域中的最基本的、不可分割的数据元素。Domain Primitive通常是不可变的,它们的行为受到业务规则的约束,并且通常不包含任何业务逻辑。

Domain Primitive可以是简单的数据类型,如字符串、整数、布尔值等,也可以是一些复杂的值对象,如金额、日期、时间范围等。它们用于描述业务领域中的基本概念和属性,例如订单号、产品价格、用户姓名等。通过使用Domain Primitive,可以有效地表达业务需求,并且保持领域模型的清晰和简洁。

在DDD中,Domain Primitive与其他领域模型元素(如实体、聚合、服务等)一起组成了完整的领域模型。通过对领域原语的合理设计和使用,可以构建出具有高内聚性和低耦合性的领域模型,从而更好地满足业务需求,并且易于维护和演化。

上面是比较官方的解释,下面是我总结的DP的特性:

DP:隐式对象、隐式上下文、多对象封装 -- 无状态的业务值对象。

  1. DP是一个传统意义上的Value Object,拥有Immutable-不可变的特性。
  2. DP是一个完整的概念整体,拥有精准定义。
  3. DP使用业务域中的原生语言。
  4. DP可以是业务域的最小组成部分、也可以构建复杂组合。

结合具体例子讲解下DP在实际业务中的应用。

应用

1.单个原子业务概念的封装。

 一个新应用在全国通过 地推业务员 做推广,需要做一个用户的注册系统,在用户注册后能够通过用户电话号的区号对业务员发奖金。


在这里,我们可以看到,原来电话号仅仅是用户的一个参数,属于隐形概念,但实际上电话号的区号才是真正的业务逻辑,而我们需要将电话号的概念显性化,通过写一个Value Object来表示。

public class PhoneNumber {private final String number;public String getNumber() {return number;}public PhoneNumber(String number) {if (number == null) {throw new ValidationException("number不能为空");} else if (isValid(number)) {throw new ValidationException("number格式错误");}this.number = number;}public String getAreaCode() {for (int i = 0; i < number.length(); i++) {String prefix = number.substring(0, i);if (isAreaCode(prefix)) {return prefix;}}return null;}private static boolean isAreaCode(String prefix) {String[] areas = new String[]{"0571", "021", "010"};return Arrays.asList(areas).contains(prefix);}public static boolean isValid(String number) {String pattern = "^0?[1-9]{2,3}-?\\d{8}$";return number.matches(pattern); }}

DP对象将业务数据和业务校验的逻辑封装内聚到PhoneNumber VO对象中,它是业务模型的最小对象不可再分割,同时精准表达业务的原生语义。

下面来看一个组合对象的例子。

2. 当我们做这个支付功能时,实际上需要的一个入参是支付金额 + 支付货币。我们可以把这两个概念组合成为一个独立的完整概念:Money。

@Value //不可变类
public class Money {private BigDecimal amount;private Currency currency;public Money(BigDecimal amount, Currency currency) {this.amount = amount;this.currency = currency;}
}@Value 
public class Currency {private String code;private String name;
}

其中Currency表示货币代码和名称。

3. 对隐藏业务行为的封装,可以将转换汇率的功能,封装到一个叫做 ExchangeRate 的 DP 里。

@Value
public class ExchangeRate {private BigDecimal rate;private Currency from;private Currency to;public ExchangeRate(BigDecimal rate, Currency from, Currency to) {this.rate = rate;this.from = from;this.to = to;}public Money exchange(Money fromMoney) {notNull(fromMoney);isTrue(this.from.equals(fromMoney.getCurrency()));BigDecimal targetAmount = fromMoney.getAmount().multiply(rate);return new Money(targetAmount, to);}
}

下面在领域服务中使用DP对象: 

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) {1.当前业务处理// 参数校验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); 2.关联业务处理 -- 发送领域事件通知 // 发送审计消息AuditMessage message = new AuditMessage(sourceAccount, targetAccount, targetMoney);auditMessageProducer.send(message);return Result.success(true);}
}

DDD的注意点

  • 实体对象只能保存自己的状态,不能关联其他实体对象,只可以通过参数的方式依赖。
  • 实体的不变性、数据的一致性 : 创建即一致\尽量避免public setter\通过聚合根保证主子实体的一致性。一个最容易导致不一致性的原因是实体暴露了public的setter方法,特别是set单一参数会导致状态不一致的情况。【建议】在有些简单场景里,有时候确实可以比较随意的设置一个值而不会导致不一致性,也建议将方法名重新写为比较“行为化”的命名,会增强其语意。比如setPosition(x, y)可以叫做moveTo(x, y),setAddress可以叫做assignAddress等。
  • 不可以强依赖其他聚合根实体或领域服务:只保存外部实体的ID、针对于“无副作用”的外部依赖。
  • 任何实体的行为只能直接影响到本实体(和其子实体)。
  • 在自己的业务中强制验证,能百分百保证使用这个业务时的正确性,防止不熟悉的人或者时间久远导致遗漏。

    Player.equip(Weapon, EquipmentService) {

        EquipmentService.canEquip(this, Weapon);

    }  ✅

    boolean canEquip = EquipmentService.canEquip(Player, Weapon);
    if (canEquip) {
        Player.equip(Weapon); // ❌,这种方法不可行,因为这个方法有不一致的可能性,迪米特法则--内部封装、隐藏细节
    }

  • 在封装领域规则时,可以通过策略管理对象 + 具体的策略对象完成策略的逻辑。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统常见20问题及处理方法(涵盖了安装、配置、使用、维护等方面)

一、系统启动问题 1. 无法启动系统 示例: 开机后出现黑屏或无法进入登录界面。 可能原因: 硬件故障: 电源故障:电源线未连接或电源损坏。内存故障:内存条松动或损坏。主板故障:主板损坏。显卡故障:显卡损坏。GRUB引导菜单错误: 引导菜单配置错误:引导菜单中没有可引导的…

Linux第86步_了解“阻塞和非阻塞IO”以及相关处理函数

1、IO “应用程序”对“驱动设备“进行输入/输出操作&#xff0c;简称IO操作&#xff0c;它是Input和Output的缩写。 2、阻塞IO 阻塞IO是“应用程序”对“驱动设备”进行操作&#xff0c;若不能获取到设备资源&#xff0c;则阻塞IO应用程序的线程会被“挂起”&#xff0c;直到…

使用LNMP部署动态网站环境

目录 实验环境 一、配置LNMP架构环境 二、验证部署的LNMP 动态网站环境是否可用 三、配置过程中遇到的问题及解决思路 实验环境 centos7 192.168.81.131/24 一、配置LNMP架构环境 概念及配置手册参考第20章 使用LNMP架构部署动态网站环境。 | 《Linux就该这么学》 安装g…

Java编程练习之接口的声明及实现

1.创建老师类和学生类&#xff0c;两个类都实现了问候接口和工作接口&#xff0c;模拟上课的场景&#xff0c;运行效果如下&#xff1a; package Zaria; interface hello{public void speak(); } interface work{public void dowork(); } class Student implements hello,work{…

PCL 基于马氏距离KMeans点云聚类

文章目录 一、简介二、算法步骤三、代码实现四、实现效果参考资料一、简介 在诸多的聚类方法中,K-Means聚类方法是属于“基于原型的聚类”(也称为原型聚类)的方法,此类方法均是假设聚类结构能通过一组原型刻画,在现实聚类中极为常用。通常情况下,该类算法会先对原型进行初始…

嵌入式与移动物联网开发教程和案例

一、嵌入式与移动物联网概述 嵌入式系统是指嵌入到设备中的专用计算机系统&#xff0c;用于控制、监视或辅助设备操作。而移动物联网则是指通过物联网技术将各种智能设备与互联网连接起来&#xff0c;实现设备之间的互联互通和智能化管理。嵌入式与移动物联网技术的结合&#…

深入剖析Cargo缓存机制

一、介绍 Cargo作为Rust的包管理工具&#xff0c;不仅在项目构建中扮演了重要的角色&#xff0c;其高效的缓存机制也为Rust开发者节省了大量的时间。本文将深入探讨Cargo的缓存原理和使用技巧&#xff0c;并提供丰富的示例让你轻松掌握Cargo缓存的管理和优化。 二、Cargo缓存…

相位校正啊

相位校正是信号处理中的一种常见技术&#xff0c;用于确保多个信号在相位上对齐&#xff0c;这对于后续的信号分析和处理至关重要。在处理三相信号时&#xff0c;相位校正尤为重要&#xff0c;因为它可以保证三相之间的相位关系准确&#xff0c;从而正确分析信号特性和检测问题…

MongoDB聚合运算符:$pow

文章目录 语法使用 举例 MongoDB聚合运算符&#xff1a; p o w ‘ pow pow‘pow聚合运算符用于求数字指定的指数并返回结果。 语法 { $pow: [ <number>, <exponent> ] }参数说明&#xff1a; <number>表达式可以是任何可解析为数值的表达式<exponent&g…

golang 协程题目

都是一个货色&#xff0c;要么使用无缓冲channel, 要么使用有缓冲chanwaitgroup等待协程退出&#xff0c;或者使用全局变量判断是否终止协程 2个协程交替打印奇数和偶数 无缓冲channel实现 package mainimport "fmt"func main() {maxval : 10ch1 : make(chan stru…

python——双下划线

一、名称修饰&#xff08;Name Mangling&#xff09; 在Python中&#xff0c;当你在一个类中定义一个方法&#xff0c;其名称以两个下划线 __ 开头但不以两个下划线结尾时&#xff08;例如&#xff1a;__private_method&#xff09;&#xff0c;Python会对这个方法名进行名称修…

gitee如何新建仓库并用小乌龟上传代码

目录 1.登录并注册gitee账号 2.创建新仓库 3.填写仓库信息 4.初始化本地仓库 5.上传数据 7.gitee官网查看上传文件 8.如何安装小乌龟 1.登录并注册gitee账号 2.创建新仓库 登录后&#xff0c;点击页面右上角的「」按钮&#xff0c;选择「新建仓库」。 3.填写仓库信息 …

@JvmDefaultWithout/WithCompatibility

JvmDefaultWithoutCompatibility JvmDefaultWithCompatibility 使用 JvmDefaultWithoutCompatibility Used only with -Xjvm-defaultall-compatibility. JvmDefaultWithCompatibility Used only with -Xjvm-defaultall. 区别 前置知识 当一个接口包含默认方法&#xff0…

入门Adaptive AUTOSAR(一) -- 为什么要提Adaptive(1)

目录 1.Adaptive AUTOSAR 1.1 AUTOSAR的由来 1.2 AUTOSAR的方法论 1.3 Why Adaptive 2.小结 1.Adaptive AUTOSAR 1.1 AUTOSAR的由来 2017年&#xff0c;国内绝大部分供应商还在思考如何用最小代价切入到AUTOSAR Classic Platform的时候&#xff0c;AUTOSAR Adaptive Pla…

把持中国互联网流量的“四大家族”,各个牛逼plus!

中国互联网80%流量被四大家族把持着&#xff0c;其余要么去这些家族批发流量&#xff0c;要么去抢占剩余20%。 以下是对中国互联网流量四大家族的介绍和代表性的流量入口产品&#xff1a; 百度系&#xff1a; 百度是中国最大的搜索引擎公司&#xff0c;其搜索引擎百度是中国互…

【位运算】Leetcode 两整数之和

题目解析 371. 两整数之和 算法讲解 异或的本质就是无进位相加&#xff0c;但是我们需要处理进位&#xff0c;就需要知道哪一位上有进位&#xff0c;再让无进位相加的结果 进位即可&#xff0c;在重复这个过程&#xff0c;当进位等于0的时候&#xff0c;说明相加的过程已经结…

OSPF防环文档

OPSF在区域内会产生俩类LSA&#xff1a;Router LSA &#xff0c;Network LSA 路由器以自己为树根构建最短路径树 &#xff0c;这里的最短路径树按两步形 成&#xff0c;第一步&#xff0c;仅考虑路由器和传输网络之间的连接。通过 Dijkstra 算法&#xff0c;根据链路状态数据…

[Linux] keytool 命令(by Copilot)

keytool 是一个用于管理密钥和证书的 Java 工具。它可以用于生成、导入、导出和修改密钥库和证书。以下是一些常用的 keytool 命令和参数&#xff1a; 1.生成密钥对&#xff1a; 使用 -genkeypair 命令生成密钥对&#xff0c;例如&#xff1a; keytool -genkeypair -alias m…

Docker - MongoDB

博文目录 文章目录 说明命令后置 说明 Docker Hub MongoDB 数据卷数据卷印射在容器内的路径mongo/data/dbmongo.config.db/data/configdb 容器内的路径说明/data/db数据目录/data/configdb不太清楚 部分环境变量是否必要说明MONGO_INITDB_DATABASE可选设置数据库的名称, 首次…

吴恩达2022机器学习专项课程(一) 第二周课程实验:特征工程和多项式回归(Lab_04)

目标 探索特征工程和多项式回归&#xff0c;使用线性回归来拟合非常复杂甚至非线性的函数。 1.为什么线性回归能拟合非线性函数&#xff1f; fxw*xb&#xff0c;属于线性回归的扩展&#xff0c;这个公式在数学中不属于线性&#xff0c;因为有x&#xff0c;而在机器学习中属于…