DDD(领域驱动设计)模式个人理解

传统的JavaWeb服务中,后端基本上可以分为三层:

Interfaces层:用于直接与前端交互,没有任何的业务逻辑,功能是定位到具体的后续处理逻辑中。

Application层:用于编写业务逻辑、与数据库交互的层。在Spring MVC里,Application层实际上就是Service层+DAO(mapper)层+一些封装数据对象的包。

Infra层:基础设施层,比如DataBase、第三方的一些Library。

以上这个结构模式会存在一些问题,Application层会过于庞大臃肿,因为所有的业务代码都写在这里。如果以后想对某一段业务逻辑进行修改,很难定位到具体的代码块以及一些结构和依赖关系。简单来说就是结构不够清晰,代码量过于臃肿难以维护。还有就是Application层对Infra层具有强依赖的关系,假如说以后想对DataBase进行升级,或者是升级,更换调用第三方的轮子,就会对业务代码造成一些阻碍,可能很多东西都要修改。

SpringMVC架构对其拆封成Service层跟Mapper层,稍微削减了Application层过于庞大的问题,并且一定程度上解耦了Application层与基础设施层;Spring官方也对很多第三方的中间件或者是组件进行了封装,可以直接使用Spring提供的接口进行调用而不具体调用,但还是无法根本解决这个问题。

DDD(Domain,Driven,Design)三层架构提出对这传统的三层架构进行更新:

首先就是在Application层和Infra层之间增加了Domain层。这一层用来存放基本上不会变的核心业务逻辑,比如说银行系统,在Domain这一层就会存放一些存取的业务逻辑。因为存钱和取钱的业务逻辑基本上是不会变的。可以说,Domain这一层存放的就是根本的业务的本身。

那么之前的Application层呢,存放的就是对于不同的user case,来协调调用Domain层的业务逻辑。

Domain

Entity

首先从最核心的Domain层说起。

Domain层存放有Entity,Entity是一个不仅包含了Po的属性,还封装了与自身相关的业务逻辑的类。传统的贫血模型中,我们通常只使用只包含数据字段还有getter() setter()方法的类,这些类是“哑巴”类,并不知道自己能做什么,所有与这个类相关的业务逻辑都散列在Service层当中,不仅能达到高内聚、低耦合的效果,同时也一定程度上增强了可读性还有可维护性,因为Entity本身就可以有业务文档的效果了,对应的业务逻辑不需要再到其它地方去改。

Entity通常还会有唯一标识符(但是在我接触的项目里似乎没有发现),通常可以通过UUID或者雪花算法生成,用来标识不同实体。Entity的标识符创建之后是永久不会改变的,用来唯一标识一个实体。比如说一个订单Entity,就算其中某一个字段值被改变了,但是Entity标识符没变,订单还是那个订单。

ValueObject

Domain层一般还会有值对象ValueObject。ValueObject与Entity的核心区别就是没有唯一标识符,因此判断两个值对象相等不相等,就是判断值对象的字段值相不相等。值对象的核心设计思想就是将多个相关的属性组合在一起,组成一个有业务意义的整体概念。比如说Money是一个值对象,它其中有ammount,currency两个属性值,还有一些其它的getter()方法以及业务逻辑(ValueObject一般没有setter()方法,值都是final)。这样封装之后,就可以把像BigDecimal这样的基本类型提升为Money这样的领域概念,让代码语言一定程度上提升为通用语言的直接表达。ValueObject通常作为Entity的属性,极大丰富了Entity的表现力和内聚性。

Aggregate & Aggregate Root

聚合是一族相关联的对象(比如说一组Entity,Entity中包含着ValueObject)的集合,这些对象被当作一个整体来处理,有统一的数据修改边界。而聚合根是访问这个聚合的唯一入口。

用人话来说就是,聚合根本身是一个Entity,在他的内部有着一系列的Entity和valueObject。这个整体就称为聚合,聚合根是这个聚合的一部分,也是这个聚合的“代表”。用一段代码举例:

/**

订单聚合的完整构成
*/
public class Order extends AggregateRoot { //  聚合根(是Entity) // 内部实体(也是Entity,但受聚合根管理)
private List orderItems; //  内部Entity // 值对象
private Address shippingAddress; //  ValueObject
private Money totalAmount; //  ValueObject // 聚合根自己的属性
private OrderId id; //  聚合根的标识
private OrderStatus status; 
// 所有这些组合在一起 = 订单聚合
}

聚合的核心作用,首先就是维护业务规则的一致性。在我现在接触到的项目中,Service中的业务规则散落在各处,业务规则很复杂的话可能会导致某些业务规则忘记去处理。使用聚合的话,就能把整体的业务规则封装在聚合的内部,使得更方便维护,不会有遗落的情况。比如说修改某个订单,会在一个业务逻辑内一并修改,保证了业务规则的一致性和数据一致性。

其次,就是封装复杂性,提供简单接口。聚合只对外暴露清晰的业务方法,使用起来很方便。

聚合也有明确的访问限制,只能通过聚合根进行访问,只能访问聚合根中的方法(一般聚合内部值对象与Entity都会private),保证了安全性。

在微服务中,上游服务发送消息到下游进行处理,聚合在这个过程中可以是一个天然的发布信息的裹挟者,因为所有需要发送的消息在这个聚合内部都应该会有保存。

Domain Service

Domain层自己的业务方法,它用来处理那些不适合放在实体或者值对象中的业务逻辑

比方说转账业务,它可能需要涉及到多个聚合,因此就不适合放在单个聚合中作为实体的业务方法。

再比如一些不包含业务状态的业务逻辑,本身就与聚合内的成员耦合关系不强,因此可以放在Domain Service中。

Domain Event

领域事件,在我的理解中就是采用了类似微服务远程调用方式的事件,即一个单体架构的项目采用微服务远程调用消息订阅那样的方式进行聚合之间的解耦。

因为不同的聚合之间解耦了,那么涉及到多个聚合之间的操作就必须要保证最终一致性,这个时候就需要用到领域事件。

Repository

仓储接口是Domain层与DAO层之间的抽象层,他通过对外只暴露接口的方式,隐藏具体的数据持久化细节,让领域代码专注于业务逻辑。

仓储接口的设计原则:

1.只针对聚合根,不针对单个实体或值对象

2.使用领域术语,比如说findByEmail,而不是selectByEmail

3.返回完整的聚合根,而不是DTO或数据库实体。

限界上下文

以上的这些,都可以看作是一种“战术设计”,这些“战术设计”最终就是为了一个“战略设计”的落地。这个战略设计就是限界上下文。

像是Entity VO Aggregate这些概念,都是在一个边界内的如何去构建模型的指导方针,而所有的这些边界,都存在于一个更大的“边界”之内。这个大的边界,就是限界上下文。

这里举一个例子,假如说我定义了一个Product的聚合根,它包含了SKUPriceInventoryDescription等属性,以及ReduceInventory()等方法。

现在,考虑以下场景:

  1. 商品管理团队需要维护这个Product,他们关心CategorySupplierCostPrice
  2. 营销团队在做促销,他们需要给Product打上Discount标签,并有一个CalculatePromotionalPrice()的方法。
  3. 订单团队在下单时,需要将Product作为一个OrderItem,他们关心下单瞬间的SnapshotPriceSnapshotProductName,并且绝对不能修改库存(库存由专门的库存服务处理)。
  4. 物流团队在发货时,他们眼中的Product只关心WeightDimensionsWarehouseLocation

如果你试图用 “一个” 庞大的Product聚合来满足所有上述需求,会发生什么?

  • 模型臃肿Product类会变成一个“上帝对象”,充斥着各种无关的属性与方法。
  • 职责混乱:一个修改商品分类的操作,和一个扣减库存的操作,竟然在同一个聚合里,它们的业务规则和变更频率完全不同。
  • 语义冲突:对于营销团队,“价格”是随时可变的促销价;对于订单团队,“价格”是下单后永恒的快照。它们能共用同一个Price字段吗?绝对不能!

这就是限界上下文要解决的核心问题:它定义了你的聚合、实体、值对象和领域服务所适用的“语义环境”。

一般来说,在单体项目中(DDD模式,单个模块),一个服务的包就是一个限界上下文,比如说支付服务,商品服务,订单服务。而在微服务的架构中,限界上下文的体现更加明显,通常一个微服务就是一个限界上下文。

限界上下文之间的通信可以通过领域事件来进行。

Applicaiton

之前提到过,Application层主要职责就是对于不同的use case通过Domain中的业务逻辑进行编排。

Service

Application Service是用例的入口点,负责协调领域对象、基础设施服务来完成特定的业务用例。有以下特征:

1.不包含业务逻辑,只负责协调。

public OrderId createOrder(CreateOrderCommand command) {
        // 1. 验证输入(基本校验)
        validateCommand(command);
        
        // 2. 获取领域对象
        Customer customer = customerRepository.findById(command.getCustomerId());
        List<Product> products = productRepository.findAllById(command.getProductIds());
        
        // 3. 调用领域服务执行业务逻辑
        Order order = OrderFactory.createOrder(customer, products, command.getShippingAddress());
        
        // 4. 协调基础设施服务
        PaymentResult payment = paymentService.authorizePayment(order.getTotalAmount());
        order.linkPayment(payment.getPaymentId());
        
        // 5. 持久化领域对象
        orderRepository.save(order);
        
        // 6. 发布事件或调用外部系统
        notificationService.sendOrderConfirmation(customer.getEmail(), order.getId());
        
        return order.getId();
    }
    
    // ❌ 错误:在Application Service中包含业务逻辑
    private void validateOrderBusinessRules(Order order) {
        // 业务规则应该放在Domain层
        if (order.getItems().size() > 10) {
            throw new BusinessException("订单商品数量不能超过10个");
        }
    }

2.每个用例的方法通常都是一个事务边界,要么全成功,要么全失败。

3.通常不鼓励Applicaiton Service调用另一个Applicaiton Service,可以调用Infrastructure Service和Domain 层。

DTO

主要用于Interfaces层与Application层之间的数据传输,即Controller和Application之间,位于Applicaiton包下。

Interfaces

包含Controller,用于接受请求和对应的参数,传递到下游的服务中,返回给前端的VO对象。通常在还会有一些Intercepter做登录校验之类的。

Infrastructure

技术基础,用于给Application层 Domain层提供服务与实现

DAO

数据访问层,包含了一系列的Mapper,对Repository中方法的实现,直接与底层数据库交互。通常还包含有对应数据库表中的Po实体类。

第三方Api客户端

像是一些短信服务,支付网关,阿里云端呃OSS,都会在Infrastructure层,具体的命名一般是xxxxService。

微服务客户端

比如说Feign客户端,一般命名就是xxxServiceClient

中间件

比如说RabbitMQ,Redis之类的。

配置管理

对数据库、中间件的环境配置管理,一般都在这一层中

工具类

比如全局ID生成器,还有一些常用的轮子。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇