后端开发实践系列之二——领域驱动设计(DDD)编码实践 (3)

可以看到,我们专门创建了一个model包用于放置所有与Order聚合根相关的领域对象;另外,基于同类型相聚原则,创建command包和exception包分别用于放置请求类和异常类。

领域模型的门面——应用服务

UML中有用例(Use Case)的概念,表示的是软件向外提供业务功能的基本逻辑单元。在DDD中,由于业务被提到了第一优先级,那么自然地我们希望对业务的处理能够显现出来,为了达到这样的目的,DDD专门提供了一个名为应用服务(ApplicationService)的抽象层。ApplicationService采用了门面模式,作为领域模型向外提供业务功能的总出入口,就像酒店的前台处理客户的不同需求一样。

后端开发实践系列之二——领域驱动设计(DDD)编码实践

在编码实现业务功能时,通常用2种工作流程:

自底向上:先设计数据模型,比如关系型数据库的表结构,再实现业务逻辑。我在与不同的程序员结对编程的时候,总会是听到这么一句话:“让我先把数据库表的字段设计出来吧”。这种方式将关注点优先放在了技术性的数据模型上,而不是代表业务的领域模型,是DDD之反。

自顶向下:拿到一个业务需求,先与客户方确定好请求数据格式,再实现Controller和ApplicationService,然后实现领域模型(此时的领域模型通常已经被识别出来),最后实现持久化。

在DDD实践中,自然应该采用自顶向下的实现方式。ApplicationService的实现遵循一个很简单的原则,即一个业务用例对应ApplicationService上的一个业务方法。比如,对于上文提到的“修改Order中Product的数量”业务需求实现如下:

实现OrderApplicationService:

@Transactional public void changeProductCount(String id, ChangeProductCountCommand command) { Order order = orderRepository.byId(orderId(id)); order.changeProductCount(ProductId.productId(command.getProductId()), command.getCount()); orderRepository.save(order); }

OrderController调用OrderApplicationService:

@PostMapping("/{id}/products") public void changeProductCount(@PathVariable(name = "id") String id, @RequestBody @Valid ChangeProductCountCommand command) { orderApplicationService.changeProductCount(id, command); }

此时,order.changeProductCount()和orderRepository.save()都没有必要实现,但是由OrderController和OrderApplicationService所构成的业务处理的架子已经搭建好了。

可以看到,“修改Order中Product的数量”用例中的OrderApplicationService.changeProductCount()方法实现中只有不多的3行代码,然而,如此简单的ApplicationService却存在很多讲究。

ApplicationService需要遵循以下原则:

业务方法与业务用例一一对应:前面已经讲到,不再赘述。

业务方法与事务一一对应:也即每一个业务方法均构成了独立的事务边界,在本例中,OrderApplicationService.changeProductCount()方法标记有Spring的@Transactional注解,表示整个方法被封装到了一个事务中。

本身不应该包含业务逻辑:业务逻辑应该放在领域模型中实现,更准确的说是放在聚合根中实现,在本例中,order.changeProductCount()方法才是真正实现业务逻辑的地方,而ApplicationService只是作为代理调用order.changeProductCount()方法,因此,ApplicationService应该是很薄的一层。

与UI或通信协议无关:ApplicationService的定位并不是整个软件系统的门面,而是领域模型的门面,这意味着ApplicationService不应该处理诸如UI交互或者通信协议之类的技术细节。在本例中,Controller作为ApplicationService的调用者负责处理通信协议(HTTP)以及与客户端的直接交互。这种处理方式使得ApplicationService具有普适性,也即无论最终的调用方是HTTP的客户端,还是RPC的客户端,甚至一个Main函数,最终都统一通过ApplicationService才能访问到领域模型。

接受原始数据类型:ApplicationService作为领域模型的调用方,领域模型的实现细节对其来说应该是个黑盒子,因此ApplicationService不应该引用领域模型中的对象。此外,ApplicationService接受的请求对象中的数据仅仅用于描述本次业务请求本身,在能够满足业务需求的条件下应该尽量的简单。因此,ApplicationService通常处理一些比较原始的数据类型。在本例中,OrderApplicationService所接受的Order ID是Java原始的String类型,在调用领域模型中的Repository时,才被封装为OrderId对象。

应用服务(ApplicationService)是领域模型的门面

业务的载体——聚合根

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpyjgw.html