两文读懂DDD领域驱动设计,举例说明,通俗易懂【值得收藏】

news2024/10/9 18:30:24

最近对架构莫名的感兴趣,慢慢觉得架构本身是为了提供方便,定制规范,目标一致并更好的协作,它的变动也并不是像变形金刚一样,而是像幼苗一样按规律成长起来的

DDD是一种方法也是一种思想,大家前面个别概念看不懂没关系,后面会举例讲,下面一一说一下DDD

DDD是什么

DDD(Domain-Driven Design,领域驱动设计)是一种软件开发方法,也是一种思想,它强调软件系统设计应该以问题领域为中心,而不是技术实现为主导。DDD通过一系列手段如统一语言、业务抽象、领域划分和领域建模等来控制软件复杂度,主要用来指导如何解耦业务系统、划分业务模块、定义业务领域模型及其交互,让开发以及相关人员能够清晰明了的开发和协作

DDD的优点

  • 提高业务理解能力:DDD强调与业务专家的紧密合作,有助于开发人员深入理解业务需求。
  • 降低系统复杂度:通过领域划分和领域建模,将复杂的业务系统拆分成若干个相对简单的子领域,从而降低系统复杂度。
  • 提高代码可维护性:领域模型为代码提供了清晰的业务语义,使得代码更易于理解和维护。
  • 提高开发效率:通过统一的领域模型和语言,减少了团队成员之间的沟通成本,提高了开发效率。
  • 促进团队合作,大家对于架构的理解是一致的

DDD的挑战

DDD很好,但是也不是谁都会

  1. 学习曲线陡峭:DDD概念复杂,理解需要时间,可以由架构师完成DDD的设计,讲解设计完成的架构以及开发时的相关使用问题以及原因
  2. 设计难度大:很多公司和项目没有足够的时间
  3. 工具支持不足:DDD几乎完全靠经验,工具很少

DDD设计的步骤

  • 1.需求分析:明确理解系统的业务需求和功能需求。
  • 2.梳理核心概念:通过领域调研,识别业务属性和概念
  • 3.定义限界上下文:进行业务分析,确定业务领域中的“限界上下文”,找到核心业务领域。
  • 4.领域建模:基于领域分析的结果,构建领域模型,包括实体、值对象、聚合、领域服务等。
  • 5.设计应用服务:应用层的设计,负责编排领域服务
  • 6.实现基础设施层:如数据库、适配器等等
  • 7.核心业务逻辑实现:根据领域模型,实现核心业务逻辑。
  • 8.持续改进和文档化

核心概念

DDD分层架构

图片

1.用户接口层(interfaces)

面向前端提供服务适配,接口就写在这里

  1. 处理restful请求,解析并基础校验
  2. 组装数据转换成DTO或者context
  3. 传递给应用层
  4. 处理应用层返回的数据转换VO给页面(正常原则为页面需要什么vo给什么,多余的不给,只是在实际开发过程中很多人不转直接就返回给前端了,把最大的数据体暴露给前端,虽然达到了最大的需要,但是消息体的大小和数据安全都有很多问题)

2.应用层(application)

  1. 组合和编排领域层的调用
  2. 向上为用户接口层提供数据的展示与支持服务
  3. 基础校验

3.领域层(domain)

领域的核心逻辑,这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务、事件等,以及他们组合所形成的业务能力

聚合

先说聚合,领域层中的聚合很重要,是由聚合根,实体,值对象组成;

聚合根是聚合的入口点,它本身就是一个实体,保证聚合内部的一致性,提供事务边界,封装内部逻辑(这个所谓的内部逻辑是指聚合某些属性和值对象)

比如有一个订单的聚合

  • 聚合根:订单(Order)
  • 实体:订单项(OrderLineItem)后面讲实体
  • 值对象:地址(Address)后面讲值对象
//你看,它就是一个实体
public class Order {
    private String orderId;
    private List<OrderLineItem> items;
    private Address deliveryAddress;
    private OrderStatus status;

    public Order(String orderId, Address deliveryAddress) {
        this.orderId = orderId;
        this.deliveryAddress = deliveryAddress;
        this.items = new ArrayList<>();
        this.status = OrderStatus.NEW;
    }
    public void addProduct(Product product, int quantity) {
        OrderLineItem lineItem = new OrderLineItem(product, quantity);
        this.items.add(lineItem);
    }
    public void placeOrder() {
        // Business logic for placing the order
        this.status = OrderStatus.PLACED;
    }
    public String getOrderId() {
        return orderId;
    }
    public OrderStatus getStatus() {
        return status;
    }
    public Address getDeliveryAddress() {
        return deliveryAddress;
    }
}

public class OrderLineItem {
    private Product product;
    private int quantity;

    public OrderLineItem(Product product, int quantity) {
        this.product = product;
        this.quantity = quantity;
    }
    public Product getProduct() {
        return product;
    }
    public int getQuantity() {
        return quantity;
    }
}

public class Address {
    private String street;
    private String city;
    private String state;
    private String zipCode;
    public Address(String street, String city, String state, String zipCode) {
        this.street = street;
        this.city = city;
        this.state = state;
        this.zipCode = zipCode;
    }
    // Getters and setters
}

问题来了,聚合根是个实体的话,如果我A聚合根想调用B实体,那不是耦合了吗?

是的,所以不能这么调用,那如何让A实体(聚合根)调用B实体

  1. 通过领域服务进行协调封装
  2. 通过基础层查询

实体Entity

这个实体并非是对应数据库表结构,它通常包含与业务紧密相关的行为,一般方法比较简单,因为只改实体本身,记住通常与单一实体状态相关的逻辑封装在实体中(涉及多个实体或复杂业务逻辑封装在领域服务中),一般不直接调用基础层

示例如下

public class Order {
	private PaymentStatus paymentStatus;
	public void initializePayment(PaymentMethod paymentMethod) {
    	// 初始化支付,设置初始状态
	    this.paymentStatus = PaymentStatus.INITIALIZED;
	}

	public void confirmPayment(PaymentConfirmation confirmation) {
	    // 确认支付,更新状态
    	this.paymentStatus = PaymentStatus.CONFIRMED;
	}		
}

值对象

值对象代表了业务中的不可变数据,它的身份由属性决定,比如地址,年龄,性别等等

特性
  • 无唯一标识符:它的身份由其属性决定,就是说比如性别这个属性决定了这个人的值对象
  • 不可变性:一旦创建,属性就不能改变,比如性别不能改变
  • 业务意义:值对象通常代表业务中的具体概念,如地址、货币金额、日期时间等等

使用场景:

  1. 描述性信息:描述实体的某些特性,比如地址,颜色,金额等等
  2. 复合属性
  3. 数学对象:如坐标
  4. 不可变数据:如订单中总价
示例
public final class PostalAddress {
    private final String street;
    private final String city;
    private final String state;
    private final String zipCode;

    // 私有构造函数
    private PostalAddress(String street, String city, String state, String zipCode) {
        this.street = street;
        this.city = city;
        this.state = state;
        this.zipCode = zipCode;
    }

    // 工厂方法
    public static PostalAddress create(String street, String city, String state, String zipCode) {
        return new PostalAddress(street, city, state, zipCode);
    }
	//只提供getter,没有setter
    public String getStreet() {
        return street;
    }
    public String getCity() {
        return city;
    }
    public String getState() {
        return state;
    }
    public String getZipCode() {
        return zipCode;
    }
	//重写equals,hashCode,toString()
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PostalAddress that = (PostalAddress) o;
        return Objects.equals(street, that.street) &&
               Objects.equals(city, that.city) &&
               Objects.equals(state, that.state) &&
               Objects.equals(zipCode, that.zipCode);
    }
    @Override
    public int hashCode() {
        return Objects.hash(street, city, state, zipCode);
    }
    @Override
    public String toString() {
        return "PostalAddress{" +
               "street='" + street + '\'' +
               ", city='" + city + '\'' +
               ", state='" + state + '\'' +
               ", zipCode='" + zipCode + '\'' +
               '}';
    }
}

问题来了

如何使用值对象呢?
  • 实体中使用值对象,可以看到依赖了
public class Customer {
    private final String customerId;
    private final String name;
    private final PostalAddress address;

    public Customer(String customerId, String name, PostalAddress address) {
        this.customerId = customerId;
        this.name = name;
        this.address = address; // 值对象作为属性
    }

    public String getCustomerId() {
        return customerId;
    }

    public String getName() {
        return name;
    }

    public PostalAddress getAddress() {
        return address;
    }
}
  • 领域服务中使用,比如总价Money类中使用币种
public final class Money {
    private final BigDecimal amount;
    private final Currency currency;

    public Money(BigDecimal amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }
    public static Money create(BigDecimal amount, Currency currency) {
        return new Money(amount, currency);
    }
    public BigDecimal getAmount() {
        return amount;
    }
    public Currency getCurrency() {
        return currency;
    }
    public Money add(Money other) {
        if (!currency.equals(other.getCurrency())) {
            throw new IllegalArgumentException("Cannot add money with different currencies.");
        }
        return new Money(amount.add(other.getAmount()), currency);
    }
}

领域服务

封装了不属于任何单一实体或值对象的复杂业务逻辑,领域服务通常包含跨多个实体的操作或者协调多个聚合

注意和聚合根的区别

事件

使用事件用来解耦,当某个实体发生变化时,可以发布一个领域事件,其他感兴趣的实体或者服务可以通过监听事件来响应,避免直接依赖,支持异步处理

4.基础层(infrastructure)

贯穿所有层,提供基础资源服务,如附件、配置、三方调用、数据库服务等,其中对应数据库表结构的PO对象就在这一层维护

注意

  1. 代码边界一定要清晰,尽可能松耦合和低关联,聚合的逻辑要在应用层实现对领域层的编排调用,不可以领域A调用领域B,领域层逻辑相对独立,若存在对其他领域的调用,应该拆分小粒度,由应用层去编排这个调用,这种松耦合的代码关联,在以后业务发展和需求变更时,可以很方便地实现业务功能和聚合代码的重组,在微服务架构演进中将会起到非常重要的作用
  2. 代码分层代码职责要清晰,各个层负责的是什么要清楚,核心业务逻辑不要放到应用层和接口层,否则就变成了传统的三层架构模型了,不要有坏代码的味道(这时你有疑问了,复杂业务逻辑必然是调用多方,无法放到领域层,别着急,后面专门解释)

好这篇就先写到这,大家可以理解一下,下一篇会介绍服务依赖关系、典型目录结构及示例、数据对象转换、设计思考 see you later~

这里路由到DDD二~~~~~~~~~~~~~~~~~~~~~
两文读懂DDD领域驱动设计(二),举例说明,通俗易懂【值得收藏】

参考文章:领域驱动设计DDD:https://mp.weixin.qq.com/s/mujGrnl6GZs0RmDr2vP9Kg

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

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

相关文章

C++-再探构造函数(进阶)

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 前言 来到类和对象最后一个章节&#xff0c;这里的难度已经极大程度的降低了 再探构造…

FFMpeg源码分析,关键结构体分析(一)

http://lazybing.github.io/blog/categories/ffmpegyuan-ma-fen-xi/ 一、下载FFmpeg的编译源码 进入网站&#xff1a;http://ffmpeg.org/download.html二、编译源码 执行下述命令&#xff1a; ./configure --prefix/usr/local/ffmpeg --enable-debug3 --enable-ffplay sudo …

22年408数据结构

第一题&#xff1a; 解析&#xff1a; 观察一下这个程序&#xff1a;我们注意到最外层的循环是从i1开始的&#xff0c;每次ii*2&#xff0c;直到i<n为止&#xff0c;假设程序总共执行k次执行&#xff0c;则有2^(k1)>n。则k1>log(2)n这里是以2为底n的对数, k>log(2)…

Oracle11g服务器linux 安装

一&#xff0e;安装前准备 1.检查硬件&#xff08;内存&#xff0c;交换分区&#xff0c;tmp分区&#xff0c;cpu信息&#xff0c;内核版本&#xff09; # grep MemTotal /proc/meminfo # grep SwapTotal /proc/meminfo # df -k /tmp&#xff08;>400M&#xff09; # grep …

汽车氛围灯行业分析:未来几年年复合增长率CAGR为7.15%

汽车氛围灯是一种起到装饰和指示作用的照明灯&#xff0c;它属于装饰类的照明灯。通常是红色、蓝色、绿色等&#xff0c;主要是为了使车厢更加绚丽&#xff0c;烘托气氛&#xff0c;营造室内情调。氛围灯能够具有以下特性&#xff1a;功能性、舒适性、设计感、豪华感、个性化、…

【C++】——继承

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;Yan. yan.                        …

深入解析MySQL事务管理:ACID特性与基本操作

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storm…

自动猫砂盆是养猫新型智商税吗?测评2024年热门款智能猫砂盆分享

铲屎官们只要一察觉到猫主子拉屎&#xff0c;就要马上去铲掉&#xff0c;这不仅是为了猫砂盆中其他干净的猫砂&#xff0c;更是为了防止猫屎残留发臭&#xff0c;特别是便便这种东西&#xff0c;一旦放久了就很招虫子&#xff0c;家里出现这些虫子又要大扫除消杀&#xff0c;特…

2024 年顶级智能文档处理解决方案

在当今的数字时代&#xff0c;智能文档处理(IDP) 对于提高业务效率和降低成本至关重要。IDP 可实现文档处理的自动化&#xff0c;最大限度地减少人工劳动和错误。由于有众多 IDP 解决方案可供选择&#xff0c;因此选择合适的解决方案可能具有挑战性。 本指南回顾了 10 款最…

Android Handler消息机制完全解析-同步屏障(三)

Android 消息机制Handler完全解析(一) Android 消息机制Handler完全解析(二) 前面两篇我们主要讲了Handler消息机制的一些基础&#xff0c;今天来看下消息屏障&#xff0c;通过本篇文章你将学到如下知识点 (1)什么是同步屏障 (2)为什么要有同步屏障 (3)同步屏障的原理 (4…

获取时隔半个钟的三天

摘要&#xff1a; 今天遇到需求是配送时间&#xff0c;时隔半个钟的排线&#xff01;所以需要拼接时间&#xff01;例如2024-10-08 14&#xff1a;30&#xff0c;2024-10-08 15&#xff1a;00&#xff0c;2024-10-08 15&#xff1a;30 <el-form-item label"配送时间&a…

24下软考中级系统集成项目管理工程师怎么备考?

备考资料&#xff1a; 1.教材 教材可以准备由清华大学出版社出版的系统集成项目管理工程师教材&#xff0c;这也是官方所推荐的教材&#xff0c;准备这本书是绝对没错的。 2.真题 真题也是在备考过程中少不了的资料之一&#xff0c;而且系统集成项目管理工程师考试就是需要多…

初始项目托管到gitee教程,开箱即用

0.本地仓库与远程仓库关联&#xff08;需先在gitee创建仓库&#xff09; ①打开powershell生成ssh key ssh-keygen -t ed25519 -C "Gitee SSH Key"-t key 类型-C 注释 生成成功如下&#xff0c;并按下三次回车 ②查看公私钥文件 ls ~/.ssh/输出&#xff1a; id_…

华为---Super VLAN简介及示例配置

目录 1. Super VLAN技术产生背景 2. Super VLAN概念 3. Super VLAN应用场景 4. Super VLAN工作原理 5. Super-VLAN主要配置命令 6. Super-VLAN主要配置步骤 7. 示例配置 7.1 示例场景 7.2 网络拓扑 7.3 配置代码 7.4 代码解析 7.5 测试验证 1. Super VLAN技术产生背…

虹软人脸 报错 Can‘t find dependent libraries

系列文章目录 文章目录 系列文章目录一、虹软人脸 报错 Can‘t find dependent libraries 一、虹软人脸 报错 Can‘t find dependent libraries 在项目中使用了 虹软 人脸识别SDK&#xff0c;环境一直出错。 错误&#xff1a; Can’t find dependent libraries 从错误信息来…

项目启动 | 盘古信息赋能奥尼视讯数字化转型升级,实现全面数智化发展

随着信息技术的飞速发展与全球市场竞争的日益激烈&#xff0c;传统制造业正面临生存和发展的危机&#xff0c;制造企业为谋求发展&#xff0c;纷纷开启数字化转型之路&#xff0c;深度融入数字技术&#xff0c;实现生产流程的智能化、管理模式的精细化以及产品服务的个性化&…

[面试] java开发面经-1

前言 目录 1.看到你的简历里说使用Redis缓存高频数据&#xff0c;说一下Redis的操作 2.说一下Redis的缓存击穿、缓存穿透、缓存雪崩 3.你的项目中使用了ThreadLocal&#xff0c;那么当有两个请求同时发出时&#xff0c;会怎么处理&#xff0c;可以同时处理两个请求吗 4.使用…

CUDA、Pytorch、Pycharm的安装与配置

文章目录 一、CUDA安装1.检查英伟达驱动支持的最高CUDA版本 二、Pytorch的安装与环境配置1.选择是下载CPU版本还是GPU版本2.上Pytorch官网找到安装命令3.运行指令(1)CPU版本(2)GPU版本 4.验证5.安装其他所需模块(0)安装torch(1)安装Matplotlib(2)安装 pillow&#xff08;可能an…

高效稳压,YB2411 DCDC降压芯片助力高电压功率转换系统

在现代的科技发展中&#xff0c;高电压功率转换系统的需求越来越多。为满足市场需求&#xff0c;我们推出了一款高输入电压DCDC降压芯片——YB2411。 YB2411系列 1>昱灿 YB2411R SOT23-6 DC-DC高压降压 36V 0.6A 2>昱灿 YB2411SR SOT23-6 DC-DC高压降压 60V 0.8A YB241…

【simulink仿真模型】Buck变换器闭环控制,电力电子仿真模型

摘要 本文介绍了基于Simulink的Buck变换器闭环控制系统的设计与仿真。通过对Buck变换器的数学模型进行建模&#xff0c;并引入PI控制器对输出电压进行实时调节&#xff0c;实现了系统的稳态控制。仿真结果显示&#xff0c;该闭环控制系统能够快速响应负载变化&#xff0c;保持…