当前位置: 首页 > news >正文

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

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

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 orderthis.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,没有setterpublic String getStreet() {return street;}public String getCity() {return city;}public String getState() {return state;}public String getZipCode() {return zipCode;}//重写equals,hashCode,toString()@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(street, city, state, zipCode);}@Overridepublic 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.mrgr.cn/news/46306.html

相关文章:

  • 基于Python的COM库控制CANoe同时打开多个.cfg工程方法案例
  • 爬虫常用正则表达式用法
  • Apache SeaTunnel 9月份社区发展记录
  • Linux:多线程中的生产消费模型
  • 决策树随机森林-笔记
  • 基于Android11简单分析audio_policy_configuration.xml
  • Linux网络编程 -- 网络套接字预备与udp
  • Lombok的@Builder注解
  • ES操作指南
  • Run the FPGA VI 选项的作用
  • AI改变一切,包括你的毕业论文!如何应对?
  • 十年网络安全工程师谈学习网络安全的正确顺序
  • 希亦超声波清洗机值得购买吗?清洁技术之王多维度测评大揭秘!
  • 基于邮箱的域名欺骗攻击:利用解析器绕过访问控制
  • 面对多种可燃气体,哪种传感器最适合你的应用场景?
  • vite+vue3实现动态路径导入
  • 电力电子技术03 AC-DC整流器(2)---单相半波整流器 二极管不控整流
  • 行盒的截断样式 box-decoration-break
  • 视频汇聚平台EasyCVR支持云端录像丨监控存储丨录像回看丨录像计划丨录像配置
  • vite搭建vue3项目