1. 概述

领域驱动设计(Domain-Driven Design,简称 DDD)是一套帮助我们构建高效软件架构、提升业务价值的原则与工具集。其中,“限界上下文(Bounded Context)” 是核心模式之一,它通过将整个应用领域划分为多个语义一致的模块,有效避免系统退化为“大泥球(Big Ball Of Mud)”。

与此同时,Java 9 引入了模块系统(JPMS),使我们能够创建强封装的模块。

本文将通过一个简单的电商系统示例,演示如何结合 Java 9 模块机制,为 DDD 的限界上下文建立清晰的物理边界。


2. DDD 限界上下文

现代软件系统早已不是简单的 CRUD 应用。典型的单体企业系统往往包含大量遗留代码和新增功能,随着变更不断叠加,维护成本急剧上升,最终可能变得完全不可维护。

2.1 限界上下文与通用语言

DDD 提出的“限界上下文”正是为了解决这一问题。限界上下文是一个逻辑边界,在这个边界内,特定术语和规则保持一致。在该边界内,所有术语、定义和概念共同构成“通用语言(Ubiquitous Language)”。

✅ 通用语言的核心价值在于:让不同背景的团队成员(如开发、产品、测试)围绕特定业务领域达成统一认知。

⚠️ 同一个事物在不同上下文中可能具有不同含义。例如,“订单”在“下单”和“发货”上下文中,其关注点和数据结构可能完全不同。

General Bounded Context

2.2 订单上下文

我们从“订单上下文”开始实现。该上下文包含两个实体:OrderItemCustomerOrder

OrderContext

CustomerOrder 是一个聚合根(Aggregate Root):

public class CustomerOrder {
    private int orderId;
    private String paymentMethod;
    private String address;
    private List<OrderItem> orderItems;

    public float calculateTotalPrice() {
        return orderItems.stream().map(OrderItem::getTotalPrice)
          .reduce(0F, Float::sum);
    }
}

注意:calculateTotalPrice 是一个典型的业务方法。实际项目中可能更复杂,比如需要计算折扣、税费等。

接着定义 OrderItem

public class OrderItem {
    private int productId;
    private int quantity;
    private float unitPrice;
    private float unitWeight;
}

实体定义完成后,需要暴露服务接口。创建 CustomerOrderService

public class CustomerOrderService implements OrderService {
    public static final String EVENT_ORDER_READY_FOR_SHIPMENT = "OrderReadyForShipmentEvent";

    private CustomerOrderRepository orderRepository;
    private EventBus eventBus;

    @Override
    public void placeOrder(CustomerOrder order) {
        this.orderRepository.saveCustomerOrder(order);
        Map<String, String> payload = new HashMap<>();
        payload.put("order_id", String.valueOf(order.getOrderId()));
        ApplicationEvent event = new ApplicationEvent(payload) {
            @Override
            public String getType() {
                return EVENT_ORDER_READY_FOR_SHIPMENT;
            }
        };
        this.eventBus.publish(event);
    }
}

关键点:

  • placeOrder 处理订单逻辑。
  • 订单处理完成后,通过 EventBus 发布事件,实现上下文间解耦。
  • 该服务实现了 OrderService 接口:
public interface OrderService extends ApplicationService {
    void placeOrder(CustomerOrder order);

    void setOrderRepository(CustomerOrderRepository orderRepository);
}

服务依赖 CustomerOrderRepository 进行持久化:

public interface CustomerOrderRepository {
    void saveCustomerOrder(CustomerOrder order);
}

⚠️ 该接口不在本上下文实现,而是由基础设施模块提供,下文会说明。

2.3 发货上下文

接下来定义“发货上下文”,包含三个实体:ParcelPackageItemShippableOrder

ShippingContext

ShippableOrder 实体:

public class ShippableOrder {
    private int orderId;
    private String address;
    private List<PackageItem> packageItems;
}

注意:它没有 paymentMethod 字段,因为发货上下文不关心支付方式。

Parcel 是发货上下文的聚合根:

public class Parcel {
    private int orderId;
    private String address;
    private String trackingId;
    private List<PackageItem> packageItems;

    public float calculateTotalWeight() {
        return packageItems.stream().map(PackageItem::getWeight)
          .reduce(0F, Float::sum);
    }

    public boolean isTaxable() {
        return calculateEstimatedValue() > 100;
    }

    public float calculateEstimatedValue() {
        return packageItems.stream().map(PackageItem::getWeight)
          .reduce(0F, Float::sum);
    }
}

最后是 ParcelShippingService

public class ParcelShippingService implements ShippingService {
    public static final String EVENT_ORDER_READY_FOR_SHIPMENT = "OrderReadyForShipmentEvent";
    private ShippingOrderRepository orderRepository;
    private EventBus eventBus;
    private Map<Integer, Parcel> shippedParcels = new HashMap<>();

    @Override
    public void shipOrder(int orderId) {
        Optional<ShippableOrder> order = this.orderRepository.findShippableOrder(orderId);
        order.ifPresent(completedOrder -> {
            Parcel parcel = new Parcel(completedOrder.getOrderId(), completedOrder.getAddress(), 
              completedOrder.getPackageItems());
            if (parcel.isTaxable()) {
                // 计算额外税费
            }
            // 发货逻辑
            this.shippedParcels.put(completedOrder.getOrderId(), parcel);
        });
    }

    @Override
    public void listenToOrderEvents() {
        this.eventBus.subscribe(EVENT_ORDER_READY_FOR_SHIPPMENT, new EventSubscriber() {
            @Override
            public <E extends ApplicationEvent> void onEvent(E event) {
                shipOrder(Integer.parseInt(event.getPayloadValue("order_id")));
            }
        });
    }

    @Override
    public Optional<Parcel> getParcelByOrderId(int orderId) {
        return Optional.ofNullable(this.shippedParcels.get(orderId));
    }
}

关键点:

  • 使用 ShippingOrderRepository 查询可发货订单。
  • ✅ 通过订阅 OrderReadyForShipmentEvent 事件响应订单状态变化,实现上下文间通信。
  • 为简化,使用 HashMap 存储已发货包裹。

3. 上下文映射(Context Maps)

我们已定义两个上下文,但尚未明确它们的关系。DDD 提出“上下文映射”来描述上下文间的协作关系。

上下文映射是系统中各上下文关系的可视化表达,它展示了不同部分如何协同工作。

常见的五种关系类型:

  • 合作伙伴(Partnership):两个团队目标相互依赖,需紧密协作。
  • 共享内核(Shared Kernel):多个上下文共享部分核心代码,减少重复。
  • 客户-供应商(Customer-Supplier):上游生产数据,下游消费数据,双方需良好沟通。
  • 遵奉者(Conformist):下游被动适配上游的接口,无议价能力。
  • 防腐层(Anticorruption Layer):用于隔离遗留系统,作为适配器防止污染新架构。

本文采用“共享内核”模式,但主要用于事件通信的中介,不包含具体实现。

共享内核接口

EventBus 接口:

public interface EventBus {
    <E extends ApplicationEvent> void publish(E event);

    <E extends ApplicationEvent> void subscribe(String eventType, EventSubscriber subscriber);

    <E extends ApplicationEvent> void unsubscribe(String eventType, EventSubscriber subscriber);
}

基础服务接口,提供默认事件方法:

public interface ApplicationService {

    default <E extends ApplicationEvent> void publishEvent(E event) {
        EventBus eventBus = getEventBus();
        if (eventBus != null) {
            eventBus.publish(event);
        }
    }

    default <E extends ApplicationEvent> void subscribe(String eventType, EventSubscriber subscriber) {
        EventBus eventBus = getEventBus();
        if (eventBus != null) {
            eventBus.subscribe(eventType, subscriber);
        }
    }

    default <E extends ApplicationEvent> void unsubscribe(String eventType, EventSubscriber subscriber) {
        EventBus eventBus = getEventBus();
        if (eventBus != null) {
            eventBus.unsubscribe(eventType, subscriber);
        }
    }

    EventBus getEventBus();

    void setEventBus(EventBus eventBus);
}

各上下文的服务接口继承 ApplicationService,获得统一的事件能力。


4. Java 9 模块化

Java 9 模块系统(JPMS)鼓励构建更可靠、强封装的模块,非常适合实现限界上下文的物理隔离。

最终模块结构如下:

ModulesDiagram

4.1 SharedKernel 模块

无外部依赖,仅导出接口:

module com.baeldung.dddmodules.sharedkernel {
    exports com.baeldung.dddmodules.sharedkernel.events;
    exports com.baeldung.dddmodules.sharedkernel.service;
}

4.2 OrderContext 模块

依赖 SharedKernel,导出模型、服务和仓库接口,并提供 OrderService 的默认实现:

module com.baeldung.dddmodules.ordercontext {
    requires com.baeldung.dddmodules.sharedkernel;
    exports com.baeldung.dddmodules.ordercontext.service;
    exports com.baeldung.dddmodules.ordercontext.model;
    exports com.baeldung.dddmodules.ordercontext.repository;
    provides com.baeldung.dddmodules.ordercontext.service.OrderService
      with com.baeldung.dddmodules.ordercontext.service.CustomerOrderService;
}

4.3 ShippingContext 模块

结构与 OrderContext 类似:

module com.baeldung.dddmodules.shippingcontext {
    requires com.baeldung.dddmodules.sharedkernel;
    exports com.baeldung.dddmodules.shippingcontext.service;
    exports com.baeldung.dddmodules.shippingcontext.model;
    exports com.baeldung.dddmodules.shippingcontext.repository;
    provides com.baeldung.dddmodules.shippingcontext.service.ShippingService
      with com.baeldung.dddmodules.shippingcontext.service.ParcelShippingService;
}

4.4 Infrastructure 模块

提供具体实现,包括 EventBus 和仓库。

SimpleEventBus 实现:

public class SimpleEventBus implements EventBus {
    private final Map<String, Set<EventSubscriber>> subscribers = new ConcurrentHashMap<>();

    @Override
    public <E extends ApplicationEvent> void publish(E event) {
        if (subscribers.containsKey(event.getType())) {
            subscribers.get(event.getType())
              .forEach(subscriber -> subscriber.onEvent(event));
        }
    }

    @Override
    public <E extends ApplicationEvent> void subscribe(String eventType, EventSubscriber subscriber) {
        Set<EventSubscriber> eventSubscribers = subscribers.get(eventType);
        if (eventSubscribers == null) {
            eventSubscribers = new CopyOnWriteArraySet<>();
            subscribers.put(eventType, eventSubscribers);
        }
        eventSubscribers.add(subscriber);
    }

    @Override
    public <E extends ApplicationEvent> void unsubscribe(String eventType, EventSubscriber subscriber) {
        if (subscribers.containsKey(eventType)) {
            subscribers.get(eventType).remove(subscriber);
        }
    }
}

持久化模型(统一存储):

public static class PersistenceOrder {
    public int orderId;
    public String paymentMethod;
    public String address;
    public List<OrderItem> orderItems;

    public static class OrderItem {
        public int productId;
        public float unitPrice;
        public float itemWeight;
        public int quantity;
    }
}

InMemoryOrderStore 实现两个仓库接口:

public class InMemoryOrderStore implements CustomerOrderRepository, ShippingOrderRepository {
    private Map<Integer, PersistenceOrder> ordersDb = new HashMap<>();

    @Override
    public void saveCustomerOrder(CustomerOrder order) {
        this.ordersDb.put(order.getOrderId(), new PersistenceOrder(order.getOrderId(),
          order.getPaymentMethod(),
          order.getAddress(),
          order
            .getOrderItems()
            .stream()
            .map(orderItem ->
              new PersistenceOrder.OrderItem(orderItem.getProductId(),
                orderItem.getQuantity(),
                orderItem.getUnitWeight(),
                orderItem.getUnitPrice()))
            .collect(Collectors.toList())
        ));
    }

    @Override
    public Optional<ShippableOrder> findShippableOrder(int orderId) {
        if (!this.ordersDb.containsKey(orderId)) return Optional.empty();
        PersistenceOrder orderRecord = this.ordersDb.get(orderId);
        return Optional.of(
          new ShippableOrder(orderRecord.orderId, orderRecord.orderItems
            .stream().map(orderItem -> new PackageItem(orderItem.productId,
              orderItem.itemWeight,
              orderItem.quantity * orderItem.unitPrice)
            ).collect(Collectors.toList())));
    }
}

模块定义:

module com.baeldung.dddmodules.infrastructure {
    requires transitive com.baeldung.dddmodules.sharedkernel;
    requires transitive com.baeldung.dddmodules.ordercontext;
    requires transitive com.baeldung.dddmodules.shippingcontext;
    provides com.baeldung.dddmodules.sharedkernel.events.EventBus
      with com.baeldung.dddmodules.infrastructure.events.SimpleEventBus;
    provides com.baeldung.dddmodules.ordercontext.repository.CustomerOrderRepository
      with com.baeldung.dddmodules.infrastructure.db.InMemoryOrderStore;
    provides com.baeldung.dddmodules.shippingcontext.repository.ShippingOrderRepository
      with com.baeldung.dddmodules.infrastructure.db.InMemoryOrderStore;
}

✅ 使用 requires transitive 使依赖可传递,简化主模块配置。

4.5 Main 模块

应用入口模块:

module com.baeldung.dddmodules.mainapp {
    uses com.baeldung.dddmodules.sharedkernel.events.EventBus;
    uses com.baeldung.dddmodules.ordercontext.service.OrderService;
    uses com.baeldung.dddmodules.ordercontext.repository.CustomerOrderRepository;
    uses com.baeldung.dddmodules.shippingcontext.repository.ShippingOrderRepository;
    uses com.baeldung.dddmodules.shippingcontext.service.ShippingService;
    requires transitive com.baeldung.dddmodules.infrastructure;
}

⚠️ uses 声明所需的服务接口,供 ServiceLoader 在运行时查找实现,不要求编译时存在具体实现


5. 运行应用

5.1 项目结构

基于 Maven 多模块项目:

ddd-modules (根目录)
pom.xml
|-- infrastructure
|-- mainapp
|-- ordercontext
|-- sharedkernel
|-- shippingcontext

5.2 主方法

public static void main(String args[]) {
    Map<Class<?>, Object> container = createContainer();
    OrderService orderService = (OrderService) container.get(OrderService.class);
    ShippingService shippingService = (ShippingService) container.get(ShippingService.class);
    shippingService.listenToOrderEvents();

    CustomerOrder customerOrder = new CustomerOrder();
    int orderId = 1;
    customerOrder.setOrderId(orderId);
    List<OrderItem> orderItems = new ArrayList<>();
    orderItems.add(new OrderItem(1, 2, 3, 1));
    orderItems.add(new OrderItem(2, 1, 1, 1));
    orderItems.add(new OrderItem(3, 4, 11, 21));
    customerOrder.setOrderItems(orderItems);
    customerOrder.setPaymentMethod("PayPal");
    customerOrder.setAddress("北京市朝阳区xxx街道123号");
    orderService.placeOrder(customerOrder);

    if (orderId == shippingService.getParcelByOrderId(orderId).get().getOrderId()) {
        System.out.println("订单已成功处理并发货");
    }
}

5.3 使用 ServiceLoader 实现依赖注入

无 Spring 环境下,使用 ServiceLoader 发现服务实现:

public static Map<Class<?>, Object> createContainer() {
    EventBus eventBus = ServiceLoader.load(EventBus.class).findFirst().get();

    CustomerOrderRepository customerOrderRepository = ServiceLoader.load(CustomerOrderRepository.class)
      .findFirst().get();
    ShippingOrderRepository shippingOrderRepository = ServiceLoader.load(ShippingOrderRepository.class)
      .findFirst().get();

    ShippingService shippingService = ServiceLoader.load(ShippingService.class).findFirst().get();
    shippingService.setEventBus(eventBus);
    shippingService.setOrderRepository(shippingOrderRepository);
    OrderService orderService = ServiceLoader.load(OrderService.class).findFirst().get();
    orderService.setEventBus(eventBus);
    orderService.setOrderRepository(customerOrderRepository);

    HashMap<Class<?>, Object> container = new HashMap<>();
    container.put(OrderService.class, orderService);
    container.put(ShippingService.class, shippingService);

    return container;
}

⚠️ 踩坑点:InMemoryOrderStore 实现了两个接口,若不加控制,ServiceLoader 会创建两个实例。使用 provider 方法实现单例:

public class InMemoryOrderStore implements CustomerOrderRepository, ShippingOrderRepository {
    private volatile static InMemoryOrderStore instance = new InMemoryOrderStore();

    public static InMemoryOrderStore provider() {
        return instance;
    }
}

ServiceLoader 优先调用 provider() 方法获取实例,否则使用无参构造函数。


6. 总结

本文通过一个电商示例,展示了:

  • ✅ 如何使用 DDD 的“限界上下文”划分业务边界。
  • ✅ 如何利用 Java 9 模块系统实现物理隔离。
  • ✅ 如何通过“共享内核”和事件机制实现上下文通信。
  • ✅ 如何使用 ServiceLoader 实现轻量级依赖注入。

⚠️ 注意:DDD 和模块化虽好,但并非银弹。应根据项目复杂度权衡使用,避免过度设计。

完整源码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/patterns-modules/ddd-contexts


原始标题:DDD Bounded Contexts and Java Modules