原有的对DDD的学习笔记:

https://blog.csdn.net/qinwenjng120/article/details/150874301《面试之微服务架构》

https://blog.csdn.net/qinwenjng120/article/details/150557401《电商项目_微服务_架构》

项目背景:星海电商平台

公司最初是一个单体应用,随着业务发展,订单和物流模块变得异常复杂且难以维护。每次促销活动,系统都濒临崩溃,开发新功能(如“预约配送”、“冷链物流”)举步维艰。我们决定采用DDD对“订单”和“物流”进行重构。

说明:正常设计一个复杂的软件系统,想到的是微服务的拆分,那么拆分成哪些微服务?拆分微服务的原则是什么?DDD就是一个指导思想。 

第一步:战略设计 - 划定问题边界(确定 限界上下文)

目的:确认关键业务场景、关键实体、值对象,从而确定限界上下文。

1. 事件风暴与统一语言

我们组织产品、运营、业务、开发等人员一起进行“事件风暴”(业务关键场景确认)。

  • 我们列出了一些核心领域事件(Domain Events):

    • 订单已提交

    • 订单已支付

    • 订单已发货

    • 包裹已出库

    • 包裹已送达分拣中心

    • 包裹已开始派送

    • 订单已签收

  • 在这些事件中,我们提炼出统一语言(Ubiquitous Language):

    • 订单(Order): 用户的购买意向,包含商品、价格、收货地址。核心状态 是待支付、已支付、已取消。

    • 物流单(Shipping): 实际的物理包裹流转记录。核心状态是待出库、运输中、派送中、已签收。

    • 包裹(Parcel): 一个物流单可能包含多个包裹。

    • 分拣中心(SortingCenter): 物流网络中的关键节点。

2. 划定限界上下文(Bounded Context)

通过分析事件和命令,我们发现“订单”和“物流”关注的核心问题完全不同:

  • 订单上下文(Order Context): 关心钱和交易。它负责计算金额、管理支付状态、处理优惠券。它不关心包裹具体怎么运输。

  • 物流上下文(Shipping Context): 关心物和移动。它负责规划路线、更新包裹位置、预估送达时间。它不关心订单用了什么优惠券。

因此,我们明确地划分出两个限界上下文:Order Context 和 Shipping Context。

3. 上下文映射(Context Map)

这两个上下文紧密相关,那么它们如何协作呢?

我们采用 “客户-供应商”模式。

  • 客户: Shipping Context(物流上下文)

  • 供应商: Order Context(订单上下文)

物流系统需要从订单系统获取“收货地址”和“商品清单”来创建物流单。订单上下文作为供应商,需要提供一个稳定、明确的接口(如RESTful API或事件)给物流上下文消费。

第二步:战术设计 - 构建解决方案模型

现在,我们深入到每个限界上下文内部,进行精细的建模, 确定每个限界上下文内的 实体、值对象、聚合、聚合根、领域服务。

1. 在 Order Context 内部:
  1. 实体(Entity): Order(订单)是一个典型的实体,它有唯一的ID(orderId),并且它的状态会随时间变化(从待支付到已支付)。

  2. 值对象(Value Object): Address(收货地址)、OrderItem(订单项)。两个订单即使所有商品都一样,也是不同的订单(实体);但两个相同的地址(省市区街道一样)则可以认为是同一个值对象,可以互换。

  3. 聚合根(Aggregate Root): Order 本身就是聚合根。我们不会直接操作OrderItem,必须通过Order这个根来操作,比如 order.addItem(product, quantity)。这保证了订单数据的一致性(例如,添加商品时自动重新计算总价)。(判断能直接操作的实体是什么)

  4. 领域服务(Domain Service): OrderPaymentService(订单支付服务)。支付这个行为涉及与外部支付网关的交互,它不属于任何一个实体或值对象的固有职责,因此用一个领域服务来封装。

  5. 领域事件(Domain Event): OrderPaidEvent(订单已支付事件)。当支付成功后,订单聚合根会发布这个事件。这个事件是连接两个上下文的关键!

        代码实现上,所谓聚合根是能够直接操作的对象, 就是定义订单类, 定义其包括的实体和值对象,定义订单相关的操作。

// 聚合根
public class Order {
    private OrderId id;
    private Address shippingAddress;
    private List<OrderItem> items;
    private OrderStatus status;

    // 核心业务逻辑:提交订单
    public void submit() {
        // 验证逻辑...
        this.status = OrderStatus.PENDING_PAYMENT;
    }

    // 核心业务逻辑:支付成功
    public void paySuccess() {
        if (this.status != OrderStatus.PENDING_PAYMENT) {
            throw new IllegalOrderOperationException("订单状态异常,无法支付");
        }
        this.status = OrderStatus.PAID;
        // 发布领域事件!
        DomainEventPublisher.publish(new OrderPaidEvent(this.id, this.shippingAddress, this.items));
    }

    // 通过根来添加商品,保证业务规则
    public void addItem(Product product, int quantity) {
        // 检查商品、库存等...
        this.items.add(new OrderItem(product, quantity));
        this.calculateTotal(); // 内部重新计算总价
    }
}
2. 在 Shipping Context 内部:
  1. 实体(Entity): Shipping(物流单),有唯一ID shippingId。Parcel(包裹)也可能是实体。

  2. 值对象(Value Object): Location(位置信息)、RoutePlan(路线规划)。

  3. 聚合根(Aggregate Root): Shipping 是聚合根。它内部维护着包裹的当前状态和位置历史。

  4. 领域服务(Domain Service): RoutePlanningService(路线规划服务),它根据地址和物流网络计算最优路径,这是一个复杂的领域逻辑。

  5. 应用服务(Application Service): ShippingAppService。它是领域的门面,接收外部指令(如创建物流单)。

  6. 事件处理器(EventHandler): 它订阅来自Order Context的 OrderPaidEvent。

// 聚合根
public class Shipping {
    private ShippingId id;
    private OrderId orderId; // 关联的订单ID
    private Location currentLocation;
    private List<ShippingHistory> history;
    private ShippingStatus status;

    public void updateLocation(Location newLocation) {
        this.history.add(new ShippingHistory(this.currentLocation, Instant.now()));
        this.currentLocation = newLocation;

        // 可以根据规则更新状态,例如进入某个分拣中心后变为“运输中”
        if (newLocation.isSortingCenter()) {
            this.status = ShippingStatus.IN_TRANSIT;
        }
    }
}


// 应用服务
@Service
public class ShippingAppService {

    @Autowired
    private ShippingRepository repository;
    @Autowired
    private RoutePlanningService routePlanningService;

    // 事件处理器:当收到订单已支付事件时,创建物流单
    @EventListener
    public void handle(OrderPaidEvent event) {
        // 1. 调用基础设施层,通过API从Order Context获取详细的配送信息(防腐层)
        OrderDeliveryInfo deliveryInfo = orderServiceClient.getDeliveryInfo(event.getOrderId());

        // 2. 调用领域服务进行路线规划
        RoutePlan plan = routePlanningService.calculatePlan(deliveryInfo.getAddress());

        // 3. 创建物流聚合根
        Shipping shipping = new Shipping(
            ShippingId.generate(),
            event.getOrderId(),
            deliveryInfo,
            plan
        );

        // 4. 通过仓储持久化
        repository.save(shipping);
    }

    // 更新物流位置
    public void updateLocation(ShippingId id, Location newLocation) {
        Shipping shipping = repository.findById(id);
        shipping.updateLocation(newLocation); // 调用聚合根的方法
        repository.save(shipping);
    }
}

第三步:架构与实现

我们采用 微服务架构 来匹配限界上下文:

  • 订单服务 (Order Service): 对应 Order Context。

  • 物流服务 (Shipping Service): 对应 Shipping Context。

它们之间通过 消息中间件(如RabbitMQ/Kafka) 进行异步的事件驱动通信。

  1. 订单服务在支付成功后,发布 OrderPaidEvent 到消息队列。

  2. 物流服务监听该事件,然后创建物流单。

数据库分离: 每个服务拥有自己独立的数据库,订单服务不直接访问物流服务的表,反之亦然。这实现了真正的解耦。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐