DDD模型初探

简介: DDD模型初探

背景

面试官: DDD模型知道吗?

了不起: 知道,DDD叫领域驱动设计。

面试官: 在实际项目中使用过吗?

了不起: 没有使用过

面试官: 如果要求你用DDD来设计一个订单系统, 你会这么设计?

相关概念

聚合根: 它是一个实体对象,代表了一个业务上的整体,它可以包含多个实体对象和值对象。聚合根负责维护整个聚合内部的一致性,所有对聚合内部的操作都必须通过聚合根进行。在实现订单管理功能时,我们可以使用聚合根来维护订单和订单项之间的关系。

实体对象: 实体对象是具有唯一标识符的对象,它们具有生命周期和状态,并且可以与其他实体对象进行交互。在订单管理系统中,我们可以定义一个Order实体对象,它包含订单号、订单状态、订单金额等属性。

值对象: 值对象是没有唯一标识符的对象,它们通常用于描述实体对象的属性或特征。在订单管理系统中,我们可以定义一个Address值对象,它包含收货人姓名、收货地址、联系电话等属性。

限界上下文:在订单管理系统中,限界上下文是一个非常重要的概念。它定义了一个业务领域的边界,包括一组相关的实体对象和值对象,以及它们之间的关系。限界上下文通常是一个独立的模块,可以被独立地开发、测试和部署。

构建项目的步骤

确定限界上下文

在订单管理系统中,订单模块是一个独立的业务领域,它包含订单的创建、确认、发货、收货、取消等业务流程。因此,我们可以将订单模块定义为一个限界上下文。

  • 订单管理:这个上下文包括Order实体对象,以及相关的值对象,如Address和OrderItem。它还包括与订单管理相关的领域服务,如计算订单总额或更新订单状态。
  • 客户管理:这个上下文包括Customer实体对象,以及相关的值对象,如ContactInfo和PaymentMethod。它还包括与客户管理相关的领域服务,如创建新客户或更新客户信息。
  • 库存管理:这个上下文包括Product实体对象,以及相关的值对象,如ProductVariant和StockLevel。它还包括与库存管理相关的领域服务,如更新库存水平或为订单保留库存。

确定实体对象和值对象

在订单管理系统中,我们可以定义一个Order实体对象,它包含订单号、订单状态、订单金额等属性。我们还可以定义一个Address值对象,它包含收货人姓名、收货地址、联系电话等属性。另外,我们还可以定义一个OrderItem值对象,它包含商品名称、商品数量、商品单价等属性。

Order实体对象

public class Order {
    private String orderId;
    private OrderStatus orderStatus;
    private BigDecimal orderAmount;
    private List<OrderItem> orderItems;
    private Address shippingAddress;
    private Date orderDate;
    // 构造函数
    public Order(String orderId, OrderStatus orderStatus, BigDecimal orderAmount, List<OrderItem> orderItems, Address shippingAddress, Date orderDate) {
        this.orderId = orderId;
        this.orderStatus = orderStatus;
        this.orderAmount = orderAmount;
        this.orderItems = orderItems;
        this.shippingAddress = shippingAddress;
        this.orderDate = orderDate;
    }
    // getter和setter方法
    public String getOrderId() {
        return orderId;
    }
    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }
    .....
    // 计算订单总金额
    public BigDecimal calculateOrderAmount() {
        BigDecimal totalAmount = BigDecimal.ZERO;
        for (OrderItem item : orderItems) {
            totalAmount = totalAmount.add(item.getItemAmount());
        }
        return totalAmount;
    }
    // 更新订单状态
    public void updateOrderStatus(OrderStatus newStatus) {
        this.orderStatus = newStatus;
    }
}

Address和OrderItem值对象

public class Address {
    private String name;
    private String addressLine1;
    private String addressLine2;
    private String city;
    private String state;
    private String zipCode;
    private String phoneNumber;
    // 构造函数
    public Address(String name, String addressLine1, String addressLine2, String city, String state, String zipCode, String phoneNumber) {
        this.name = name;
        this.addressLine1 = addressLine1;
        this.addressLine2 = addressLine2;
        this.city = city;
        this.state = state;
        this.zipCode = zipCode;
        this.phoneNumber = phoneNumber;
    }
    // getter和setter方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    .....
}
public class OrderItem {
    private String itemName;
    private int quantity;
    private BigDecimal itemPrice;
    // 构造函数
    public OrderItem(String itemName, int quantity, BigDecimal itemPrice) {
        this.itemName = itemName;
        this.quantity = quantity;
        this.itemPrice = itemPrice;
    }
    // getter和setter方法
    public String getItemName() {
        return itemName;
    }
    public void setItemName(String itemName) {
        this.itemName = itemName;
    }
    //省略其他set/get方法
    ......
    // 计算订单项金额
    public BigDecimal getItemAmount() {
        return itemPrice.multiply(BigDecimal.valueOf(quantity));
    }
}

确定领域服务

在订单管理系统中,我们可以定义一个OrderService领域服务,它包含订单的创建、确认、发货、收货、取消等业务逻辑。这些业务逻辑通常涉及多个实体对象和值对象,因此我们可以将它们封装在一个领域服务中。

public interface OrderService {
    void placeOrder(Order order);
}
public class OrderServiceImpl implements OrderService {
    private OrderRepository orderRepository;
    public OrderServiceImpl(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    @Override
    public void placeOrder(Order order) {
        order.placeOrder();
        orderRepository.save(order);
    }
}

确定聚合根

在订单管理系统中,我们可以将OrderAggregate实体对象作为聚合根,它是一个有唯一标识符的实体对象,它包含多个实体对象和值对象。我们可以通过聚合根来管理整个订单模块的状态和行为。

public class OrderAggregate {
    private Order order;
    private List<OrderItem> orderItems;
    public OrderAggregate(Order order, List<OrderItem> orderItems) {
        this.order = order;
        this.orderItems = orderItems;
    }
    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
    }
    public void removeOrderItem(OrderItem orderItem) {
        orderItems.remove(orderItem);
    }
    public double calculateTotalPrice() {
        double totalPrice = 0;
        for (OrderItem orderItem : orderItems) {
            totalPrice += orderItem.getProduct().getPrice() * orderItem.getQuantity();
        }
        return totalPrice;
    }
    public void placeOrder() {
        if (order.getCustomer().canPlaceOrder()) {
            // do something
        } else {
            // do something else
        }
    }
}

确定仓储接口

在订单管理系统中,我们可以定义一个OrderRepository仓储接口,它包含对订单的增删改查等操作。我们可以通过仓储接口来实现对订单的持久化和查询。

public interface OrderRepository {
    void save(Order order);
}
public class OrderRepositoryImpl implements OrderRepository {
    @Override
    public void save(Order order) {
        // do something
    }
}

确定应用服务

在订单管理系统中,我们可以定义一个OrderApplicationService应用服务,它是一个面向用户的服务,它包含对订单的查询、创建、确认、发货、收货、取消等操作。我们可以通过应用服务来实现用户与订单模块的交互。

通过以上步骤,我们可以使用DDD来设计和实现订单模块,从而提高软件开发的效率和质量。最后项目整体结构如下:

充血模型和贫血模型

DDD中的充血模型是指将业务逻辑封装在实体对象中,实体对象不仅仅是数据的容器,还包含了业务逻辑的处理(比如Order)。这样可以避免业务逻辑散落在各个层次中,使得代码更加清晰和易于维护。充血模型和贫血模型是两种不同的设计模式,它们在处理业务逻辑时有着不同的优缺点。

  • 充血模型是指将业务逻辑封装在实体对象和领域服务中,使得实体对象具有更多的行为和状态。在充血模型中,实体对象不仅仅是数据的容器,它们还具有一些行为和状态,可以对外提供更加丰富的接口。充血模型的优点是可以将业务逻辑封装在实体对象和领域服务中,使得代码更加清晰和易于维护。缺点是实体对象的行为和状态可能会变得过于复杂,导致代码难以理解和维护。
  • 贫血模型是指将业务逻辑封装在服务层中,使得实体对象只是数据的容器,不具有任何行为和状态。在贫血模型中,实体对象只是一个简单的POJO对象,它们的行为和状态由服务层来管理。贫血模型的优点是实体对象的代码比较简单,易于理解和维护。缺点是服务层的代码可能会变得过于复杂,导致代码难以理解和维护。

充血模型和贫血模型都有各自的优缺点,具体使用哪种模型取决于具体的业务需求和开发团队的技术水平。在DDD中,通常使用充血模型来处理业务逻辑,因为充血模型可以更好地封装业务逻辑,使得代码更加清晰和易于维护。

总结

总结一下DDD方式的优缺点以及我们什么场景下采用DDD

优点:

  1. 更好地理解业务领域:DDD可以帮助我们更好地理解业务领域,将业务逻辑和技术实现分离,从而提高软件开发的效率和质量。
  2. 更好地管理复杂性:DDD可以帮助我们更好地管理复杂性,将复杂的业务逻辑分解成小的领域模型,从而降低系统的复杂度。
  3. 更好地支持变化:DDD可以帮助我们更好地支持变化,将变化隔离在领域模型内部,从而降低变化对系统的影响。

缺点:

  1. 学习成本高:DDD需要掌握一定的领域知识和技术实现,因此学习成本较高。
  2. 设计复杂度高:DDD需要进行领域建模和设计,因此设计复杂度较高。
  3. 实现难度大:DDD需要进行领域驱动的实现,因此实现难度较大。

适用场景:

  • 需要处理复杂业务逻辑的系统。
  • 需要支持变化的系统。
  • 需要更好地理解业务领域的系统。
  • 需要更好地管理复杂性的系统。
  • 需要更好地支持业务创新的系统。


相关文章
|
Java
Java中的抽象类:深入了解抽象类的概念和用法
Java中的抽象类是一种不能实例化的特殊类,常作为其他类的父类模板,定义子类行为和属性。抽象类包含抽象方法(无实现)和非抽象方法。定义抽象类用`abstract`关键字,子类继承并实现抽象方法。抽象类适用于定义通用模板、复用代码和强制子类实现特定方法。优点是提供抽象模板和代码复用,缺点是限制继承灵活性和增加类复杂性。与接口相比,抽象类可包含成员变量和单继承。使用时注意设计合理的抽象类结构,谨慎使用抽象方法,并遵循命名规范。抽象类是提高代码质量的重要工具。
807 1
|
存储 网络协议
TCP服务器 IO多路复用的实现:select、poll、epoll
TCP服务器 IO多路复用的实现:select、poll、epoll
523 0
|
Java
Java选择语句之switch
Java选择语句之switch
499 1
|
12月前
|
存储 Java Linux
详细地说一说零拷贝
我是小假 期待与你的下一次相遇 ~
684 1
详细地说一说零拷贝
|
Ubuntu NoSQL 关系型数据库
《docker基础篇:5.本地镜像发布到阿里云》
《docker基础篇:5.本地镜像发布到阿里云》
1207 79
|
消息中间件 供应链 测试技术
图解 DDD,这一篇总结太全面了!
DDD领取驱动是非常热的架构设计,微服务也有大量涉及,本文详细解析领域驱动设计(DDD),涵盖DDD原理、实践步骤及核心概念等,帮助更好地管理复杂业务逻辑。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 DDD,这一篇总结太全面了!
|
数据库
DDD架构浅谈
DDD架构浅谈
784 4
|
Java Spring
【Java】Spring boot快速上手(二):参数传递
【Java】Spring boot快速上手(二):参数传递
460 0
|
机器学习/深度学习 文字识别 前端开发
基于 Spring Boot 3.3 + OCR 实现图片转文字功能
【8月更文挑战第30天】在当今数字化信息时代,图像中的文字信息越来越重要。无论是文档扫描、名片识别,还是车辆牌照识别,OCR(Optical Character Recognition,光学字符识别)技术都发挥着关键作用。本文将围绕如何使用Spring Boot 3.3结合OCR技术,实现图片转文字的功能,分享工作学习中的技术干货。
2096 2
|
XML 分布式计算 资源调度
Hadoop配置文件问题
【7月更文挑战第15天】
455 8