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)”。
✅ 通用语言的核心价值在于:让不同背景的团队成员(如开发、产品、测试)围绕特定业务领域达成统一认知。
⚠️ 同一个事物在不同上下文中可能具有不同含义。例如,“订单”在“下单”和“发货”上下文中,其关注点和数据结构可能完全不同。
2.2 订单上下文
我们从“订单上下文”开始实现。该上下文包含两个实体:OrderItem
和 CustomerOrder
。
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 发货上下文
接下来定义“发货上下文”,包含三个实体:Parcel
、PackageItem
和 ShippableOrder
。
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)鼓励构建更可靠、强封装的模块,非常适合实现限界上下文的物理隔离。
最终模块结构如下:
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