1. Overview
1.概述
Domain-Driven Design (DDD) is a set of principles and tools that helps us design effective software architectures to deliver higher business value. Bounded Context is one of the central and essential patterns to rescue architecture from the Big Ball Of Mud by segregating the whole application domain into multiple semantically-consistent parts.
领域驱动设计(DDD)是一套原则和工具,帮助我们设计有效的软件架构,以提供更高的商业价值。Bounded Context是将整个应用领域隔离成多个语义一致的部分,从而将架构从大泥球中拯救出来的核心和基本模式之一。
At the same time, with the Java 9 Module System, we can create strongly encapsulated modules.
同时,通过Java 9模块系统,我们可以创建强封装的模块。
In this tutorial, we’ll create a simple store application and see how to leverage Java 9 Modules while defining explicit boundaries for bounded contexts.
在本教程中,我们将创建一个简单的商店应用程序,看看如何利用Java 9模块,同时为有边界的上下文定义显式边界。
2. DDD Bounded Contexts
2.DDD有边界的背景
Nowadays, software systems are not simple CRUD applications. Actually, the typical monolithic enterprise system consists of some legacy codebase and newly added features. However, it becomes harder and harder to maintain such systems with every change made. Eventually, it may become totally unmaintainable.
如今,软件系统已经不是简单的CRUD应用程序。实际上,典型的单体企业系统由一些遗留的代码库和新添加的功能组成。然而,随着每一次改变,维护这样的系统变得越来越难。最终,它可能会变得完全无法维护。
2.1. Bounded Context and Ubiquitous Language
2.1.有界限的语境和泛在的语言
To solve the addressed issue, DDD provides the concept of Bounded Context. A Bounded Context is a logical boundary of a domain where particular terms and rules apply consistently. Inside this boundary, all terms, definitions, and concepts form the Ubiquitous Language.
为了解决这个问题,DDD提供了 “有界上下文 “的概念。“有界上下文 “是一个领域的逻辑边界,其中的特定术语和规则都是一致适用的。在这个边界内,所有术语、定义和概念都构成泛在语言。
In particular, the main benefit of ubiquitous language is grouping together project members from different areas around a specific business domain.
特别是,泛在语言的主要好处是将来自不同领域的项目成员围绕一个特定的业务领域组合在一起。
Additionally, multiple contexts may work with the same thing. However, it may have different meanings inside each of these contexts.
此外,多种语境可能对同一事物起作用。然而,它在每个语境中可能有不同的含义。
2.2. Order Context
2.2.订单背景
Let’s start implementing our application by defining the Order Context. This context contains two entities: OrderItem and CustomerOrder.
让我们通过定义订单上下文来开始实现我们的应用程序。这个上下文包含两个实体。OrderItem和CustomerOrder。
The CustomerOrder entity is an 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);
}
}
As we can see, this class contains the calculateTotalPrice business method. But, in a real-world project, it will probably be much more complicated — for instance, including discounts and taxes in the final price.
我们可以看到,这个类包含了calculateTotalPrice业务方法。但是,在一个真实世界的项目中,它可能会复杂得多–例如,在最终价格中包括折扣和税收。
Next, let’s create the OrderItem class:
接下来,让我们创建OrderItem类。
public class OrderItem {
private int productId;
private int quantity;
private float unitPrice;
private float unitWeight;
}
We’ve defined entities, but also we need to expose some API to other parts of the application. Let’s create the CustomerOrderService class:
我们已经定义了实体,但我们还需要向应用程序的其他部分公开一些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<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);
}
}
Here, we have some important points to highlight. The placeOrder method is responsible for processing customer orders. After an order is processed, the event is published to the EventBus. We’ll discuss the event-driven communication in the next chapters. This service provides the default implementation for the OrderService interface:
在这里,我们有一些重要的观点要强调。placeOrder方法负责处理客户订单。在订单被处理后,事件被发布到EventBus。我们将在接下来的章节中讨论事件驱动的通信。该服务提供了OrderService接口的默认实现。
public interface OrderService extends ApplicationService {
void placeOrder(CustomerOrder order);
void setOrderRepository(CustomerOrderRepository orderRepository);
}
Furthermore, this service requires the CustomerOrderRepository to persist orders:
此外,这项服务需要CustomerOrderRepository来持久化订单。
public interface CustomerOrderRepository {
void saveCustomerOrder(CustomerOrder order);
}
What’s essential is that this interface is not implemented inside this context but will be provided by the Infrastructure Module, as we’ll see later.
重要的是,这个接口不是在这个上下文中实现的,而是由基础设施模块提供的,我们将在后面看到。
2.3. Shipping Context
2.3.航运背景
Now, let’s define the Shipping Context. It will also be straightforward and contain three entities: Parcel, PackageItem, and ShippableOrder.
现在,让我们来定义航运上下文。它也将是简单明了的,包含三个实体。Parcel, PackageItem, 和ShippableOrder。
Let’s start with the ShippableOrder entity:
让我们从ShippableOrder实体开始。
public class ShippableOrder {
private int orderId;
private String address;
private List<PackageItem> packageItems;
}
In this case, the entity doesn’t contain the paymentMethod field. That’s because, in our Shipping Context, we don’t care which payment method is used. The Shipping Context is just responsible for processing shipments of orders.
在这种情况下,该实体不包含paymentMethod字段。这是因为,在我们的发货上下文中,我们并不关心使用哪种付款方式。航运上下文只是负责处理订单的发货。
Also, the Parcel entity is specific to the Shipping Context:
另外,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);
}
}
As we can see, it also contains specific business methods and acts as an aggregate root.
正如我们所看到的,它还包含具体的业务方法,并作为一个聚合根。
Finally, let’s define the ParcelShippingService:
最后,我们来定义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()) {
// Calculate additional taxes
}
// Ship parcel
this.shippedParcels.put(completedOrder.getOrderId(), parcel);
});
}
@Override
public void listenToOrderEvents() {
this.eventBus.subscribe(EVENT_ORDER_READY_FOR_SHIPMENT, 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));
}
}
This service similarly uses the ShippingOrderRepository for fetching orders by id. More importantly, it subscribes to the OrderReadyForShipmentEvent event, which is published by another context. When this event occurs, the service applies some rules and ships the order. For the sake of simplicity, we store shipped orders in a HashMap.
该服务同样使用ShippingOrderRepository来按id获取订单。更重要的是,它订阅了OrderReadyForShipmentEvent事件,该事件由另一个上下文发布。当该事件发生时,该服务应用一些规则并运送订单。为了简单起见,我们将已发货的订单存储在一个HashMap。
3. Context Maps
3.情境地图
So far, we defined two contexts. However, we didn’t set any explicit relationships between them. For this purpose, DDD has the concept of Context Mapping. A Context Map is a visual description of relationships between different contexts of the system. This map shows how different parts coexist together to form the domain.
到目前为止,我们定义了两个情境。然而,我们并没有在它们之间设置任何明确的关系。为此,DDD提出了上下文映射的概念。上下文地图是对系统中不同上下文之间关系的可视化描述。这个地图显示了不同部分是如何共存于一起形成领域的。
There are five main types of relationships between Bounded Contexts:
边界背景之间有五种主要的关系类型。
- Partnership – a relationship between two contexts that cooperate to align the two teams with dependent goals
- Shared Kernel – a kind of relationship when common parts of several contexts are extracted to another context/module to reduce code duplication
- Customer-supplier – a connection between two contexts, where one context (upstream) produces data, and the other (downstream) consume it. In this relationship, both sides are interested in establishing the best possible communication
- Conformist – this relationship also has upstream and downstream, however, downstream always conforms to the upstream’s APIs
- Anticorruption layer – this type of relationship is widely used for legacy systems to adapt them to a new architecture and gradually migrate from the legacy codebase. The Anticorruption layer acts as an adapter to translate data from the upstream and protect from undesired changes
In our particular example, we’ll use the Shared Kernel relationship. We won’t define it in its pure form, but it will mostly act as a mediator of events in the system.
在我们的特定例子中,我们将使用共享内核关系。我们不会以纯粹的形式来定义它,但它主要是作为系统中事件的调解人。
Thus, the SharedKernel module won’t contain any concrete implementations, only interfaces.
因此,SharedKernel模块将不包含任何具体的实现,只包含接口。
Let’s start with the EventBus interface:
让我们从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);
}
This interface will be implemented later in our Infrastructure module.
这个接口将在以后的基础设施模块中实现。
Next, we create a base service interface with default methods to support event-driven communication:
接下来,我们创建一个带有默认方法的基本服务接口,以支持事件驱动的通信。
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);
}
So, service interfaces in bounded contexts extend this interface to have common event-related functionality.
所以,在有边界的上下文中的服务接口扩展了这个接口,以具有共同的事件相关功能。
4. Java 9 Modularity
4.java 9的模块化
Now, it’s time to explore how the Java 9 Module System can support the defined application structure.
现在,是时候探讨Java 9模块系统如何支持所定义的应用结构了。
The Java Platform Module System (JPMS) encourages to build more reliable and strongly encapsulated modules. As a result, these features can help to isolate our contexts and establish clear boundaries.
Java平台模块系统(JPMS)鼓励建立更可靠和强封装的模块。因此,这些功能可以帮助我们隔离上下文并建立清晰的边界。
Let’s see our final module diagram:
让我们看看我们最后的模块图:
4.1. SharedKernel Module
4.1.共享内核模块
Let’s start with the SharedKernel module, which doesn’t have any dependencies on other modules. So, the module-info.java looks like:
让我们从SharedKernel模块开始,它对其他模块没有任何依赖性。所以,module-info.java看起来像。
module com.baeldung.dddmodules.sharedkernel {
exports com.baeldung.dddmodules.sharedkernel.events;
exports com.baeldung.dddmodules.sharedkernel.service;
}
We export module interfaces, so they’re available to other modules.
我们导出模块接口,所以它们对其他模块是可用的。
4.2. OrderContext Module
4.2.OrderContext模块
Next, let’s move our focus to the OrderContext module. It only requires interfaces defined in the SharedKernel module:
接下来,让我们把注意力转移到OrderContext模块上。它只需要在SharedKernel模块中定义的接口。
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;
}
Also, we can see that this module exports the default implementation for the OrderService interface.
另外,我们可以看到,这个模块导出了OrderService接口的默认实现。
4.3. ShippingContext Module
4.3.ShippingContext模块
Similarly to the previous module, let’s create the ShippingContext module definition file:
与之前的模块类似,我们来创建ShippingContext模块定义文件。
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;
}
In the same way, we export the default implementation for the ShippingService interface.
以同样的方式,我们导出ShippingService接口的默认实现。
4.4. Infrastructure Module
4.4.基础设施模块
Now it’s time to describe the Infrastructure module. This module contains the implementation details for the defined interfaces. We’ll start by creating a simple implementation for the EventBus interface:
现在是描述基础设施模块的时候了。这个模块包含了定义的接口的实现细节。我们将首先为EventBus接口创建一个简单的实现。
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);
}
}
}
Next, we need to implement the CustomerOrderRepository and ShippingOrderRepository interfaces. In most cases, the Order entity will be stored in the same table but used as a different entity model in bounded contexts.
接下来,我们需要实现CustomerOrderRepository和ShippingOrderRepository接口。在大多数情况下,Order实体将被存储在同一个表中,但在有界限的上下文中作为一个不同的实体模型使用。
It’s very common to see a single entity containing mixed code from different areas of the business domain or low-level database mappings. For our implementation, we’ve split our entities according to the bounded contexts: CustomerOrder and ShippableOrder.
很常见的情况是,单个实体包含来自业务领域不同领域的混合代码或低级数据库映射。对于我们的实现,我们根据有界的上下文来分割我们的实体。CustomerOrder和ShippableOrder。
First, let’s create a class that will represent a whole persistent model:
首先,让我们创建一个将代表整个持久化模型的类。
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;
}
}
We can see that this class contains all fields from both CustomerOrder and ShippableOrder entities.
我们可以看到,这个类包含了CustomerOrder和ShippableOrder两个实体的所有字段。
To keep things simple, let’s simulate an in-memory database:
为了保持简单,我们来模拟一个内存数据库。
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())));
}
}
Here, we persist and retrieve different types of entities by converting persistent models to or from an appropriate type.
在这里,我们通过将持久化模型转换为适当的类型,来持久化和检索不同类型的实体。
Finally, let’s create the module definition:
最后,我们来创建模块定义。
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;
}
Using the provides with clause, we’re providing the implementation of a few interfaces that were defined in other modules.
使用provides with子句,我们提供了其他模块中定义的一些接口的实现。
Furthermore, this module acts as an aggregator of dependencies, so we use the requires transitive keyword. As a result, a module that requires the Infrastructure module will transitively get all these dependencies.
此外,该模块充当了依赖关系的聚合器,所以我们使用了requires transitive关键字。因此,一个需要基础设施模块的模块将过渡性地获得所有这些依赖关系。
4.5. Main Module
4.5.主模块
To conclude, let’s define a module that will be the entry point to our application:
最后,让我们定义一个模块,它将是我们应用程序的入口点。
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;
}
As we’ve just set transitive dependencies on the Infrastructure module, we don’t need to require them explicitly here.
由于我们刚刚在基础设施模块上设置了横向依赖关系,我们不需要在这里明确要求它们。
On the other hand, we list these dependencies with the uses keyword. The uses clause instructs ServiceLoader, which we’ll discover in the next chapter, that this module wants to use these interfaces. However, it doesn’t require implementations to be available during compile-time.
另一方面,我们用uses关键字列出这些依赖关系。uses子句指示ServiceLoader(我们将在下一章发现),这个模块想要使用这些接口。然而,它并不要求实现在编译时可用。
5. Running the Application
5.运行应用程序
Finally, we’re almost ready to build our application. We’ll leverage Maven for building our project. This makes it much easier to work with modules.
最后,我们几乎已经准备好构建我们的应用程序了。我们将利用Maven来构建我们的项目。这使得模块的工作变得更加容易。
5.1. Project Structure
5.1.项目结构
Our project contains five modules and the parent module. Let’s take a look at our project structure:
我们的项目包含5个模块和父模块。让我们来看看我们的项目结构。
ddd-modules (the root directory)
pom.xml
|-- infrastructure
|-- src
|-- main
| -- java
module-info.java
|-- com.baeldung.dddmodules.infrastructure
pom.xml
|-- mainapp
|-- src
|-- main
| -- java
module-info.java
|-- com.baeldung.dddmodules.mainapp
pom.xml
|-- ordercontext
|-- src
|-- main
| -- java
module-info.java
|--com.baeldung.dddmodules.ordercontext
pom.xml
|-- sharedkernel
|-- src
|-- main
| -- java
module-info.java
|-- com.baeldung.dddmodules.sharedkernel
pom.xml
|-- shippingcontext
|-- src
|-- main
| -- java
module-info.java
|-- com.baeldung.dddmodules.shippingcontext
pom.xml
5.2. Main Application
5.2.主要应用
By now, we have everything except the main application, so let’s define our main method:
现在,除了主程序,我们已经拥有了一切,所以让我们定义我们的main方法。
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<OrderItem>();
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("Full address here");
orderService.placeOrder(customerOrder);
if (orderId == shippingService.getParcelByOrderId(orderId).get().getOrderId()) {
System.out.println("Order has been processed and shipped successfully");
}
}
Let’s briefly discuss our main method. In this method, we are simulating a simple customer order flow by using previously defined services. At first, we created the order with three items and provided the necessary shipping and payment information. Next, we submitted the order and finally checked whether it was shipped and processed successfully.
让我们简单地讨论一下我们的主要方法。在这个方法中,我们通过使用先前定义的服务来模拟一个简单的客户订单流程。首先,我们创建了包含三个项目的订单,并提供了必要的运输和支付信息。接下来,我们提交了订单,最后检查了订单是否被成功运送和处理。
But how did we get all dependencies and why does the createContainer method return Map<Class<?>, Object>? Let’s take a closer look at this method.
但是我们是如何得到所有的依赖关系的,为什么createContainer方法会返回Map<Class<?>,Object>?让我们仔细看看这个方法。
5.3. Dependency Injection Using ServiceLoader
5.3.使用ServiceLoader进行依赖注入
In this project, we don’t have any Spring IoC dependencies, so alternatively, we’ll use the ServiceLoader API for discovering implementations of services. This is not a new feature — the ServiceLoader API itself has been around since Java 6.
在这个项目中,我们没有任何Spring IoC依赖项,因此,我们将使用ServiceLoader API来发现服务的实现。这并不是一项新功能–ServiceLoader API 本身从 Java 6 开始就已经存在。
We can obtain a loader instance by invoking one of the static load methods of the ServiceLoader class. The load method returns the Iterable type so that we can iterate over discovered implementations.
我们可以通过调用ServiceLoader类中的一个静态load方法来获得一个加载器实例。load方法返回Iterable类型,这样我们就可以对发现的实现进行迭代。
Now, let’s apply the loader to resolve our dependencies:
现在,让我们应用加载器来解决我们的依赖关系。
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;
}
Here, we’re calling the static load method for every interface we need, which creates a new loader instance each time. As a result, it won’t cache already resolved dependencies — instead, it’ll create new instances every time.
在这里,我们为每一个我们需要的接口调用静态的load方法,每次都会创建一个新的加载器实例。因此,它不会缓存已经解决的依赖关系 – 相反,它每次都会创建新的实例。
Generally, service instances can be created in one of two ways. Either the service implementation class must have a public no-arg constructor, or it must use a static provider method.
一般来说,服务实例可以通过以下两种方式之一来创建。要么服务实现类必须有一个公共的无参数构造函数,要么它必须使用静态的provider方法。
As a consequence, most of our services have no-arg constructors and setter methods for dependencies. But, as we’ve already seen, the InMemoryOrderStore class implements two interfaces: CustomerOrderRepository and ShippingOrderRepository.
因此,我们的大多数服务都有无参数的构造器和设置器方法来处理依赖关系。但是,正如我们已经看到的,InMemoryOrderStore类实现了两个接口。CustomerOrderRepository和ShippingOrderRepository。
However, if we request each of these interfaces using the load method, we’ll get different instances of the InMemoryOrderStore. That is not desirable behavior, so let’s use the provider method technique to cache the instance:
然而,如果我们使用load方法请求这些接口中的每一个,我们将得到InMemoryOrderStore的不同实例。这不是理想的行为,所以让我们使用provider方法技术来缓存该实例。
public class InMemoryOrderStore implements CustomerOrderRepository, ShippingOrderRepository {
private volatile static InMemoryOrderStore instance = new InMemoryOrderStore();
public static InMemoryOrderStore provider() {
return instance;
}
}
We’ve applied the Singleton pattern to cache a single instance of the InMemoryOrderStore class and return it from the provider method.
我们应用Singleton模式来缓存InMemoryOrderStore类的单个实例,并从provider方法中返回它。
If the service provider declares a provider method, then the ServiceLoader invokes this method to obtain an instance of a service. Otherwise, it will try to create an instance using the no-arguments constructor via Reflection. As a result, we can change the service provider mechanism without affecting our createContainer method.
如果服务提供者声明了一个provider方法,那么ServiceLoader将调用该方法来获得服务的实例。否则,它将尝试通过反射使用无参数构造器来创建一个实例。因此,我们可以改变服务提供者的机制而不影响我们的createContainer方法。
And finally, we provide resolved dependencies to services via setters and return the configured services.
最后,我们通过设置器向服务提供已解决的依赖关系,并返回已配置的服务。
Finally, we can run the application.
最后,我们可以运行该应用程序。
6. Conclusion
6.结语
In this article, we’ve discussed some critical DDD concepts: Bounded Context, Ubiquitous Language, and Context Mapping. While dividing a system into Bounded Contexts has a lot of benefits, at the same time, there is no need to apply this approach everywhere.
在这篇文章中,我们已经讨论了一些关键的DDD概念。有界语境、泛在语言和语境映射。虽然将一个系统划分为 “有界上下文 “有很多好处,但同时也没有必要将这种方法应用于所有地方。
Next, we’ve seen how to use the Java 9 Module System along with Bounded Context to create strongly encapsulated modules.
接下来,我们已经看到了如何使用Java 9模块系统以及Bounded Context来创建强封装的模块。
Furthermore, we’ve covered the default ServiceLoader mechanism for discovering dependencies.
此外,我们已经介绍了用于发现依赖关系的默认ServiceLoader机制。
The full source code of the project is available over on GitHub.
该项目的完整源代码可在GitHub上获取。