
本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!
定义软件要完成的任务,并指挥表达领域概念的对象来解决问题。该层对业务意义重大,也是与其他系统的应用层交互的必要渠道。
要尽量简单,不包含业务规则或知识,而只为下一层中的领域对象协调任务,分配工作,使它们协作。
UML中有用例(Use Case)的概念,表示软件向外提供业务功能的基本逻辑单元。DDD中的业务是第一优先级,自然希望对业务的处理能显现出来,DDD提供称为应用服务(ApplicationService)的抽象层。
ApplicationService采用门面模式,作为领域模型向外提供业务功能的总出入口,就像酒店的前台处理客户的不同需求。
编码实现业务功能时,通常有2种工作流程:
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()方法实现只有几行代码,然而,如此简单的ApplicationService却很多讲究:
即每个业务方法均构成独立的事务边界 ,案例中,OrderApplicationService.changeProductCount()方法标记有Spring的@Transactional。
业务逻辑应该放在领域模型中实现,更准确的说是放在聚合根中实现,本例中,order.changeProductCount()方法才是真正实现业务逻辑的地方,而ApplicationService只是作为代理调用order.changeProductCount(),因此,ApplicationService应是很薄一层。
ApplicationService的定位并不是整个软件系统的门面,而是领域模型的门面,这意味着ApplicationService不应该处理诸如UI交互或者通信协议之类的技术细节。在本例中,Controller作为ApplicationService的调用者负责处理通信协议(HTTP)以及与客户端的直接交互。
这种处理方式使得ApplicationService具有普适性,也即无论最终的调用方是HTTP的客户端,还是RPC的客户端,甚至一个Main函数,最终都统一通过ApplicationService才能访问到领域模型。
接受原始数据类型:ApplicationService作为领域模型的调用方,领域模型的实现细节对其来说应该是个黑盒子,因此ApplicationService不应该引用领域模型中的对象。此外,ApplicationService接受的请求对象中的数据仅仅用于描述本次业务请求本身,在能够满足业务需求的条件下应尽量简单。因此,ApplicationService通常处理一些比较原始的数据类型。在本例中,OrderApplicationService所接受的Order ID是Java原始的String类型,在调用领域模型中的Repository时,才被封装为OrderId对象。
用户登录时序图:
sequenceDiagram
box Purple 用户侧
actor 用户
participant 微信小程序
end
box Gray 内部服务
participant 交易上下文
participant 用户上下文
end
box Blue
participant 微信平台服务
end
Note right of 微信平台服务: 含登录、用户信息、支付等接口服务
用户->>微信小程序: 扫描货柜机二维码打开
微信小程序->>+交易上下文: 打开货柜机(柜门机)柜门 <br> <token>
交易上下文-->>-微信小程序: 未认证
微信小程序->>微信小程序: wx.login()
微信小程序->>+用户上下文: 登录smartrm系统 (js_code)
用户上下文->>+微信平台服务: code2session(校验身份)
微信平台服务-->>-用户上下文: sessionKey、 <br> appId、unionId等
用户上下文-->>-微信小程序: 登录结果 (JWT+result_code, 未签免密)
微信小程序->>微信小程序: 签署免密扣款协议
微信小程序->>+微信平台服务: 支付协议签署(微信内部协议)
微信平台服务-->>-用户上下文: 支付协议签署结果 <br> (contract+id)
微信小程序->>+用户上下文: (再次)登录smartrm(js_code)
用户上下文-->>-微信小程序: 登录结果 (JWT+result_code, 已签免密)
微信小程序->>+交易上下文: 打开货柜机柜门 <br> token
交易上下文-->>-微信小程序: 打开结果之前这里的处理有一定问题,没有保证此段代码可靠性和事务性,一旦处理过程失败,用户可能就无法获得退款,用户体验差。应放到调度器里执行。
AppTradeService.java
public void onDeviceFailure(DeviceFailureEvent event) {
if (event.getMachineType() == VendingMachineType.SLOT) {
SlotVendingMachine machine = machineRepository
.getSlotVendingMachineById(event.getMachineId());
if (machine.getState() == SlotVendingMachineState.Trading
&& machine.getCurOrder().getOrderId() == event.getOrderId()) {
machine.cancelOrder();
} else {
Order order = orderRepository.getOrderById(event.getOrderId());
order.cancel();
}
}
}重构如下:
public void onDeviceFailure(DeviceFailureEvent event) {
if (event.getMachineType() == VendingMachineType.SLOT) {
Map<String, Object> params = Maps.newHashMap();
params.put("event", event);
scheduler.scheduleRetry(DeviceFailureExecutor.class, params, 0, 1000);
}
}类型 | 核心职责 | 说明 |
|---|---|---|
应用服务 | 事务控制 访问权限 任务调度 调用领域层 | 所有协调性工作,不能包含业务逻辑 |
领域服务 | 业务逻辑 | 只含“无处安放”的业务逻辑 |
应用层是调用领域模型完成用户需求的地方。应用层的实现:
Q:ddd考虑domainservice和应用service区别
Q:啥时应用service直接调用 repository?
在领域驱动设计(DDD)中,ApplicationService可能会直接调用Repository而非DomainService的情况通常包括以下几种:
Repository与数据库进行交互,无需复杂的领域逻辑。Repository来实现。ApplicationService可能会直接调用Repository来确保操作的原子性。通常,ApplicationService会启动一个事务,执行多个Repository调用,并在成功后提交事务。
以下是一些具体场景:ApplicationService可以直接调用Repository。DomainService,因为操作不需要复杂的业务逻辑处理。Repository进行数据操作,避免额外的服务调用开销。ApplicationService可能需要编排多个简单的数据访问操作,这些操作可能不需要通过DomainService。
以下是一个示例:public class CustomerApplicationService {
private final CustomerRepository customerRepository;
public CustomerApplicationService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
public Customer findCustomerById(String customerId) {
// 直接通过Repository查询客户信息,不涉及领域逻辑
return customerRepository.findById(customerId);
}
public void createCustomer(CreateCustomerCommand command) {
// 启动事务
// 创建客户实体,并直接保存到数据库
Customer customer = new Customer(command.getCustomerId(), command.getName());
customerRepository.save(customer);
// 提交事务
}
}在这个例子中,findCustomerById方法直接通过Repository查询客户信息,没有复杂的业务逻辑需要处理,因此没有必要通过DomainService。同样,createCustomer方法直接通过Repository保存新的客户实体,尽管这可能涉及到简单的验证逻辑,但它通常不足以需要DomainService的介入。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。