领域驱动设计(DDD)是一组原则和工具,可帮助我们设计有效的软件体系结构以提供更高的业务价值。通过将整个应用程序域分离为多个语义一致的部分,Bounded Context是从架构的泥潭中拯救体系结构的主要模式之一。同时,借助Java 9 Module System,我们可以创建高度封装的模块。
在本教程中,我们将创建一个简单的商店应用程序,并了解如何在定义有限上下文的显式边界时利用Java 9模块。(一个有界上下文对应一个Java9模块)
DDD有界上下文
如今,软件系统已不是简单的CRUD应用程序。实际上,典型的单体式企业系统由一些旧代码库和新添加的功能组成。但是,在进行每次更改后维护此类系统变得越来越困难。最终,它可能变得完全无法维护。
为了解决问题,DDD提供了“边界上下文”的概念。有界上下文是特定条款和规则一致适用的域的逻辑边界。在此边界内,所有术语,定义和概念都构成了通用语言。
特别是,统一通用语言的主要好处是将来自特定业务领域不同领域的项目成员团聚在一起。此外,多个上下文可能对同一事物起作用。但是,在每个上下文中它可能具有不同的含义。
订单上下文
让我们通过定义Order Context开始实现我们的应用程序。该上下文包含两个实体:OrderItem和CustomerOrder。(banq注:DDD关键是如何得到Order Context,这里没有讨论,也不是重点,主要讨论实现)
public class CustomerOrder {
private int orderId;
private String paymentMethod;
private String address;
private List 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;
}
我们已经定义了实体,但是我们还需要向应用程序的其他部分公开一些API。让我们创建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 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);
}
至关重要的是,该接口不是在此上下文中实现的,而是由基础架构模块提供的,我们将在后面看到。
运输上下文
运输上下文包含三个实体:Parcel,PackageItem和ShippableOrder。
public class ShippableOrder {
private int orderId;
private String address;
private List packageItems;
}
在这种情况下,实体不包含paymentMethod字段。这是因为,在我们的运输上下文中,我们不在乎使用哪种付款方式。运输上下文仅负责处理订单装运。
特定于运输上下文的包裹Parcel实体:
public class Parcel {
private int orderId;
private String address;
private String trackingId;
private List packageItems;
public float calculateTotalWeight() {
return packageItems.stream().map(PackageItem::getWeight)
.reduce(0F, Float::sum);
}
public boolean isTaxable() {
return calculateEstimatedValue() > 100;
}
public float calculateEstimatedValue() {