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需要进行领域驱动的实现,因此实现难度较大。

适用场景:

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


相关文章
|
3月前
|
设计模式 架构师 数据建模
DDD建模系列(四)
DDD建模系列(四)
DDD建模系列(四)
|
3月前
|
存储 前端开发 中间件
DDD建模系列(二)
DDD建模系列(二)
|
3月前
|
敏捷开发 架构师
DDD建模系列(三)
DDD建模系列(三)
|
3月前
|
架构师
DDD建模系列(一)
DDD建模系列(一)
|
3月前
|
设计模式 前端开发 Java
DDD建模系列(五)
DDD建模系列(五)
|
7月前
|
数据库
DDD架构浅谈
DDD架构浅谈
174 4
|
设计模式 存储 缓存
初探DDD
基于学习《殷浩详解DDD:领域层设计规范》后的动手实践,简单总结,以及个人理解
|
存储 安全 大数据
一文揭秘DDD到底解决了什么问题(3)
一文揭秘DDD到底解决了什么问题
71 0
一文揭秘DDD到底解决了什么问题(3)
|
消息中间件 存储 架构师
一文揭秘DDD到底解决了什么问题(1)
一文揭秘DDD到底解决了什么问题
173 0
|
存储 设计模式 运维
DDD的关键理解
当我们在学习DDD的过程中,感觉学而不得的时候,可能会问:我们还要学么?这的确引人深思。本文基于工作经验,尝试谈谈对DDD的一些理解。
DDD的关键理解