简短的问题。
从 DDD 的角度来看(或者只是可维护的架构和常识),我应该在哪里放置一个没有域对象可操作但进行一些“智能”数据准备和编排一堆副作用调用的业务逻辑层?我所说的“智能”是指它有一些规则和操作的顺序限制,而不仅仅是构建 DTO。在每个调用方法之前进行数据准备的幼稚实现看起来很混乱,并且随着逻辑变得更加复杂,情况变得更糟。此外,如果不“模拟整个宇宙”,这种实现是不可测试的。
一个长而具体的例子。
我有一个与 Order API 通信的订单 Web 应用程序。My Web App 有一个在订单抛出 API 中更新订单位置的操作。从前端获取数据并保存的实现方法包括三个阶段:
- 删除现有职位
- 添加新职位
- 重新申请折扣
这是 Order API 的一个奇怪实现,我无法控制它。代码看起来像这样
public async Task SaveOrderPosition(PositionViewModel positionViewModel)
{
var deleteDto = _dtoBuilder.BuildForDeletion(positionViewModel);
var deleteResult = await api.Delete(deleteDto);
if(deleteResult.IsFailure)
{
throw new Exception();
}
var addDto = _dtoBuilder.BuildForAdding(positionViewModel); //generates some new ids and data. the "smart" part
var addResult = await api.Add(addDto);
if(addResult.IsFailure)
{
throw new Exception();
}
var discountsDto = _dtoBuilder.BuildDiscounts(addDto, positionViewModel); //also "smart" part as it has dependency on added data
var discountsResult = await api.ApplyDiscounts(addDto);
if(discountsResult.IsFailure)
{
throw new Exception();
}
}
正如我所看到的,这段代码 a) 无法通过单元测试进行测试,b) 一些重要的逻辑位于 dto builder 中,c) 随着新规则的出现或 API 的更改,它只会变得更加混乱。(也许我错了,没关系)
我的想法
所以在这里我看不到任何域模型对象可以用来使代码更具可测试性和可扩展性。
- 第一个想法 - 构建订单对象并执行类似的操作
order.UpdatePosition(position);
await Save(order);
但是创建整个 Order 对象是一项昂贵的操作,并且 ViewModel 以外的数据实际上对于 Save 操作并不是必需的
- (愚蠢的一)实现位置域模型。
public async Task SaveOrderPosition(PositionViewModel positionViewModel)
{
var position = new Position(positionViewModel); //actually creates inconsistent objects
position.Save();
await Save(position.Events);
}
class Position
{
public List<PEvent> Events {get;}
public void Save()
{
Events.Add(new DeleteEvent());
// do some "smart" staff
Events.Add(new AddEvent());
// do some discounts staff
Events.Add(new ApplyDiscountEvent());
}
}
好的部分是一些业务逻辑与应用层解耦并且是可测试的。但是位置模型(因为它在构造后具有不相关的状态)或其 Save 方法也没有现实意义,因为位置不会自我保存。照我看来
- 创建域服务。看起来像我需要的东西 - 包含不属于任何实体的操作的东西。但是也有人说域服务应该在几个聚合上运行,我一个都没有。另外,在这种情况下,这种服务的方法应该返回什么?在 API 调用中使用的一堆事件或类似 DTO 元组的东西?
所以实际上我正在努力将代码从程序方式转移到面向对象和 DDD 方式,因为我的域中没有对象负责所需的操作。(或者我只是没有看到)