专栏名称: 逸言
文学与软件,诗意地想念。
目录
相关文章推荐
直播海南  ·  刚刚,三亚辟谣! ·  4 天前  
51好读  ›  专栏  ›  逸言

菱形对称架构的运用

逸言  · 公众号  ·  · 2024-02-20 08:00

正文

在《 菱形对称架构的演进、定义和价值 》一文中,我已经完整地介绍了菱形对称架构的今世前身,也说明了它在领域驱动战略设计中起到的价值。

现在,我们通过一个简化的提交订单业务场景来说明在菱形对称架构下,限界上下文之间以及内部各个设计元素是如何协作的。

若希望获得更加详尽而完整的代码,可以移步 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@Localpublic 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@DomainServicepublic 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@Localpublic 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@DomainServicepublic 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@Localpublic class OrderAppService { @Autowired private OrderService orderService; @Autowired private OrderPlacedEventPublisher orderPlacedEventPublisher;
private static final Logger logger = LoggerFactory.getLogger(OrderAppService.class);
@Transactional






请到「今天看啥」查看全文