深入实践DDD:以DSL驱动复杂软件开发
上QQ阅读APP看书,第一时间看更新

1.4.2 限界上下文与微服务

那么,限界上下文与微服务架构[1](MSA)中的那个微服务之间有什么关系?有人还使用过“物理限界上下文”这个术语,甚至把一个可独立部署的微服务称为物理限界上下文,这个叫法合理吗?

让我们来设想一下,现在要给一个餐厅开发一个在线外卖(Takeout)应用。我们使用了微服务架构,决定在后端开发两个可以独立部署的微服务:

·Order Service。与订单管理相关的服务,它与前端App进行交互,处理消费者的下单请求。

·Kitchen Service。处理后厨相关的业务逻辑。

事实上要创建一个真实的外卖应用,可能要处理的后端业务逻辑远比这里列出的多,比如可能我们还需要用Consumer Service来处理消费者的信息,需要用Accounting Service来处理支付逻辑等。这里为了简化问题,先忽略它们。

假设这两个微服务是以发布/订阅事件的方式——也就是使用所谓的基于协作的Saga(Choreography-based Saga)——来完成业务事务的,“下单”事务处理的正常路径如下:

1)Order Servcie接收到客户端App的下单请求,创建一个处于PENDING状态的订单,然后发布OrderCreated(订单已创建)事件。

2)Kitchen Service订阅、消费OrderCreated事件,验证订单信息,检查后厨的食材、物料、人员等信息,创建后厨工单(Ticket),并发布TicketCreated(后厨工单已创建)事件。

3)Order Service订阅、消费TicketCreated事件,将订单状态置为APPROVED,并发布OrderApproved事件。

假设开发人员编写了以上事件(OrderCreated、TicketCreated)以及其他领域对象的静态类型——Java开发人员称之为POJO,.NET开发人员称之为POCO,PHP开发人员称之为POPO——的代码,然后把这些代码都放到一个叫作common-api的类库项目中——我们可以构建这个项目,打包出jar、dll、phar之类格式的归档包文件,供其他项目使用。另外两个“服务”项目Order Service和Kitchen Service都依赖这个common-api类库项目,它们会直截了当地使用里面的静态类型,使用它们的时候并不会经过什么转换,而且会毫不在意地对其他部分(比如客户端)暴露它们的名字和概念。现在,请问我们是有“一个”限界上下文还是有“两个”限界上下文?

有些讲述在微服务架构中实践DDD的文章,对于这样的情形会判断为两个限界上下文,每个微服务对应一个上下文。

从限界上下文原本的概念出发,笔者认为这里只存在一个上下文。在这个例子里,两个服务都依赖同一个领域对象的类库,大家都说统一的语言(Ubiquitous Language),使用同一套术语——OrderCreated、TicketCreated等——来进行沟通,彼此之间并没有什么隔阂,显然大家都处于同一个限界上下文中。

限界上下文是概念的边界,微服务是物理的软件组件。微服务架构(MSA)在实践中可能会产生很多细粒度的微服务,这些微服务的物理边界与DDD的聚合边界或领域服务的边界对齐是比较常见的情况。如果一个微服务与其他微服务共享很多相同的概念,那么虽然它具有独立的物理边界,也很难称得上是一个限界上下文。

[1] 见https://www.martinfowler.com/articles/microservices.html。