在《
菱形对称架构的演进、定义和价值
》一文中,我已经完整地介绍了菱形对称架构的今世前身,也说明了它在领域驱动战略设计中起到的价值。
现在,我们通过一个简化的提交订单业务场景来说明在菱形对称架构下,限界上下文之间以及内部各个设计元素是如何协作的。
若希望获得更加详尽而完整的代码,可以移步
GitHub,通过访问地址:
https://github.com/agiledon/diamond,即可获得。
案例背景
参与协作的限界上下文包括订单上下文、仓储上下文、通知上下文与客户上下文。
假定每个限界上下文以微服务形式部署,处于不同的进程。
提交订单业务场景的执行过程如下所示:
-
客户向订单上下文发送提交订单的客户端请求
-
订单上下文向库存上下文发送检查库存量的客户端请求
-
库存上下文查询库存数据库,返回库存信息
-
若库存量符合订单需求,则订单上下文访问订单数据库,插入订单数据
-
订单上下文调用库存上下文的锁定库存量服务,对库存量进行锁定
-
提交订单成功后,发布
OrderPlacedEvent
事件到事件总线
-
通知上下文订阅
OrderPlaced
事件,调用客户上下文获得该订单的客户信息,组装通知内容
-
通知上下文调用短信服务,发送短信通知客户
订单上下文的内部协作
客户要提交订单,通过前端UI向订单上下文远程服务
OrderController
提交请求,然后将请求委派给本地服务
OrderAppService
:
package xyz.zhangyi.diamond.demo.ordercontext.north.remote.controller;
@RestController
@RequestMapping(value="/orders")
@Remote(RemoteType.Controller)
public class OrderController
{
@Autowired
private OrderAppService orderAppService;
@PostMapping
public void placeOrder(PlacingOrderRequest request) {
orderAppService.placeOrder(request);
}
}
package xyz.zhangyi.diamond.demo.ordercontext.north.local.appservices;
@Service
@Local
public class OrderAppService {
@Autowired
private OrderService orderService;
@Autowired
private OrderPlacedEventPublisher orderPlacedEventPublisher;
private static final Logger logger = LoggerFactory.getLogger(OrderAppService.class);
@Transactional(rollbackFor = ApplicationException.class)
public void placeOrder(PlacingOrderRequest request) {
}
}
远程服务与本地服务操作的消息契约模型定义在
north.message
包中,可同时支持本地服务与远程服务:
package xyz.zhangyi.diamond.demo.ordercontext.north.message;
import java.io.Serializable;
import xyz.zhangyi.diamond.demo.foundation.stereotype.Direction;
import xyz.zhangyi.diamond.demo.foundation.stereotype.MessageContract;
import xyz.zhangyi.diamond.demo.ordercontext.domain.order.Order;
@MessageContract(Direction.North)
public class PlacingOrderRequest implements Serializable {
public Order to() {
return new Order();
}
}
这些消息契约模型都定义了如
to()
和
from()
之类的转换方法,用于消息契约模型与领域模型之间的互相转换。
订单上下文与库存上下文的协作
订单上下文的本地服务
OrderAppService
收到
PlacingOrderRequest
请求后,在将该请求对象转换为
Order
领域对象后通过领域服务
OrderService
提交订单。提交订单时,需要验证订单的有效性,再检查库存量。验证订单的有效性由领域模型对象
Order
聚合根承担,库存量的检查则通过南向网关的客户端端口
InventoryClient
:
package xyz.zhangyi.diamond.demo.ordercontext.domain.order;
@Service
@DomainService
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryClient inventoryClient;
@Autowired
private ShoppingCartService shoppingCartService;
public void placeOrder(Order order) {
order.validate();
InventoryReview inventoryReview = inventoryClient.check(order);
if (!inventoryReview.isAvailable()) {
throw new NotEnoughInventoryException();
}
orderRepository.add(order);
shoppingCartService.removeItems(order.customerId(), order.purchasedProducts());
inventoryClient.lock(order);
}
}
由于南向网关的客户端端口
InventoryClient
是面向领域模型的,因此端口的接口不能掺杂任何与领域模型无关的内容,故而接口方法操作的对象应为领域模型对象:
package xyz.zhangyi.diamond.demo.ordercontext.south.port.clients;
@Port(PortType.Client)
public interface InventoryClient {
InventoryReview check(Order order);
void lock(Order order);
}
客户端适配器
InventoryClientAdapter
实现了端口接口,在其内部则需要将领域模型对象转换为上游远程服务能够识别的消息契约对象:
package xyz.zhangyi.diamond.demo.ordercontext.south.adapter.clients;
@Component
@Adapter(PortType.Client)
public class InventoryClientRemoteAdapter implements InventoryClient {
private static final String INVENTORIES_RESOURCE_URL = "http://inventory-service/inventories";
@Autowired
private RestTemplate restTemplate;
@Override
public InventoryReview check(Order order) {
CheckingInventoryRequest inventoryRequest = CheckingInventoryRequest.from(order);
InventoryReviewResponse reviewResponse = restTemplate.postForObject(INVENTORIES_RESOURCE_URL, inventoryRequest, InventoryReviewResponse.class);
return reviewResponse.to();
}
@Override
public void lock(Order order) {
LockingInventoryRequest inventoryRequest = LockingInventoryRequest.from(order);
restTemplate.put(INVENTORIES_RESOURCE_URL, inventoryRequest);
}
}
由于订单上下文与库存上下文位于不同的进程,需要各自定义消息契约,故而需要在订单上下文的南向网关中定义对应的消息契约模型
CheckingInventoryRequest
和
InventoryReviewResponse
:
package xyz.zhangyi.diamond.demo.ordercontext.south.message;
@MessageContract(Direction.South)
public class CheckingInventoryRequest implements Serializable {
public static CheckingInventoryRequest from(Order order) {
return null;
}
}
@MessageContract(Direction.South)
public class InventoryReviewResponse implements Serializable {
public static InventoryReviewResponse from(InventoryReview inventoryReview) {
return null;
}
public InventoryReview to() {
return null;
}
}
库存上下文的内部协作
当下游的订单上下文发起对库存上下文远程服务
InventoryResource
的调用时,它又会通过本地服务
InventoryAppService
调用领域服务
InventoryService
,然后经由端口
InventoryRepository
与适配器
InventoryRepositoryAdapter
访问库存数据库,获得库存量的检查结果:
package xyz.zhangyi.diamond.demo.inventorycontext.north.remote.resource;
@RestController
@RequestMapping(value="/inventories")
@Remote(RemoteType.Resource)
public class InventoryResource {
@Autowired
private InventoryAppService inventoryAppService;
@PostMapping
public ResponseEntity check(CheckingInventoryRequest request) {
InventoryReviewResponse reviewResponse = inventoryAppService.checkInventory(request);
return new ResponseEntity<>(reviewResponse, HttpStatus.OK);
}
}
package xyz.zhangyi.diamond.demo.inventorycontext.north.local.appservice;
@Service
@Local
public class InventoryAppService {
@Autowired
private InventoryService inventoryService;
public InventoryReviewResponse checkInventory(CheckingInventoryRequest request) {
InventoryReview inventoryReview = inventoryService.reviewInventory(request.to());
return InventoryReviewResponse.from(inventoryReview);
}
}
package xyz.zhangyi.diamond.demo.inventorycontext.domain;
@Service
@DomainService
public class InventoryService {
@Autowired
private InventoryRepository inventoryRepository;
public InventoryReview reviewInventory(List<PurchasedProduct> purchasedProducts) {
List productIds = purchasedProducts.stream().map(p -> p.productId()).collect(Collectors.toList());
List products = inventoryRepository.productsOf(productIds);
List availabilities = products.stream().map(p -> p.checkAvailability(purchasedProducts)).collect(Collectors.toList());
return new InventoryReview(availabilities);
}
}
package xyz.zhangyi.diamond.demo.inventorycontext.south.port.repository;
@Repository
@Port(PortType.Repository)
public interface InventoryRepository {
List<Product> productsOf(List<String> productIds);
}
领域服务
OrderService
在确认了库存量满足订单需求后,通过端口
OrderRepository
以及适配器
OrderRepositoryAdapter
访问订单数据库,插入订单数据。
一旦订单插入成功,订单上下文的领域服务
OrderService
还要调用库存上下文的远程服务
InventoryResource
锁定库存量。订单上下文的本地服务
OrderAppService
会在
OrderService
成功提交订单之后,组装
OrderPlacedEvent
应用事件,调用端口
EventPublisher
,经由适配器
EventPulbisherAdapter
将事件发布到事件总线:
package xyz.zhangyi.diamond.demo.ordercontext.north.local.appservices;
@Service
@Local
public class OrderAppService {
@Autowired
private OrderService orderService;
@Autowired
private OrderPlacedEventPublisher orderPlacedEventPublisher;
private static final Logger logger = LoggerFactory.getLogger(OrderAppService.class);
@Transactional