非基于任务的接口和 DDD
Non-task-based interfaces and DDD
如果我们有 UI 不是基于任务且对应于我们的实体方法的任务,而这些任务又对应于无处不在的语言,我们应该怎么办?
例如,假设我们有一个 WorkItem
的域模型,它具有以下属性:StartDate, DueDate, AssignedToEmployeeId, WorkItemType, Title, Description, CreatedbyEmployeeId
.
现在,有些东西可以随着 WorkItem
的变化而分解,它归结为这样的方法:
WorkItem.ReassignToAnotherEmployee(string employeeId)
WorkItem.Postpone(DateTime newDateTime)
WorkItem.ExtendDueDate(DateTime newDueDate)
WorkItem.Describe(string description)
但是在我们的 UI 端只有一个表单,其字段对应于我们的属性和一个 Save
按钮。所以,CRUD UI。显然,这会导致像 PUT domain.com/workitems/{id}
.
这样的单个 CRUD REST API 端点
问题是:如何从域模型的角度处理到达此端点的请求?
选项 1
有CRUD之类的方法WorkItem.Update(...)
吗? (这显然违背了无处不在的语言和 DDD 的全部目的)
选项 2
端点控制器调用的应用程序服务有方法 WorkItemsService.Update(...)
但在该服务中我们调用与通用语言对应的每个域模型方法?类似于:
public class WorkItemService {
...
public Update(params) {
WorkItem item = _workItemRepository.get(params.workItemId);
//i am leaving out check for which properties actually changed
//as its not crucial for this example
item.ReassignToAnotherEmployee(params.employeeId);
item.Postpone(params.newDateTime);
item.ExtendDueDate(params.newDueDate);
item.Describe(params.description);
_workItemRepository.save(item);
}
}
或者第三种选择?
这里有什么经验法则吗?
[更新]
明确地说,问题可以用某种方式重新表述:是否应该像 CRUD WorkItem.Update()
一样成为我们模型的一部分,即使我们的领域专家以某种方式表达它 我们希望能够更新一个WorkItem 还是我们应该始终避免它并寻求 “更新”对业务的实际意义是什么?
正如您所说,选项 1 几乎违反了规则集。额外提供通用 update
对您的域实体的客户没有好处。
我会选择 2ish 选项:具有应用程序级服务但反映 UL。您的控制器需要调用一个有意义的应用程序服务方法,该方法具有有意义的 parameter/command 来更改域模型的状态。
我总是尝试从客户的角度思考我的 Service/Domain 模型代码。作为这个客户,我想知道我到底叫什么。拥有像 Update 这样的 CRUD 是违反直觉的,不会帮助您遵循 UL,而且会让客户更加困惑。他们需要知道该更新方法背后的代码才能知道他们正在更改什么。
对于您的更新:不,不包括通用更新(至少不使用名称 Update
)始终反映业务 rules/processes。你的代码的客户永远不会知道我做了什么。
如果这是从特定控制器 api 端点触发的特定业务流程,您可以这样称呼它。假设您的 Update
实际上是业务流程 DoAWorkItemReassignAndPostponeDueToEmployeeWentOnVacation()
那么您可以批量执行此操作但不要使用通用更新。始终反映 UL。
你的 domain/sub-domain 天生就是 CRUD 吗?
"if our domain experts express it in a way we want to be able update a
WorkItem"
如果您的 sub-domain 与 CRUD 很好地对齐,您不应该尝试强制使用域模型。 CRUD 不是 anti-pattern,实际上可以完美适合某些 sub-domain。当业务专家表达的丰富业务流程被开发人员错误地转换为 CRUD UI 和后端时,CRUD 就会出现问题,从而导致 code/UL 错位。
请注意,显式发现和建模业务流程的成本也很高。有时(例如缺乏资源)让那些生活在领域专家的头脑中是可以接受的。他们将从 paper-based 进程中驱动一个简单的 CRUD UI,而不是让系统引导他们。 CRUD 在这里可能非常好,因为虽然过程很复杂,但我们并没有试图在仍然简单的系统中对它们进行建模。
我无法判断您的域是否天生就是 CRUD,但我只是想指出,如果是,那就拥抱它并寻求更简单的业务逻辑模式(Active Record、Transaction Script 等。 ).如果您发现自己一直希望通过单个方法调用来映射每一位数据,那么您可能处于 CRUD 域中。
隔离腐败
如果您确定域模型将有利于您的模型,那么您应该尽早阻止腐败在系统中蔓延。这是通过 anti-corruption layer 完成的,在您的情况下,它负责解释 CRUD 调用并将它们转换为更有意义的业务流程。
anti-corruption 层应该位于您要保护的系统部分和 legacy/misbehaving/etc 部分之间。那将是选项#2。在这种情况下,anti-corruption 代码很可能必须将当前状态与新状态进行比较,以尝试找出所做的更改以及如何将这些更改与更明确的业务流程相关联。
如果我们有 UI 不是基于任务且对应于我们的实体方法的任务,而这些任务又对应于无处不在的语言,我们应该怎么办?
例如,假设我们有一个 WorkItem
的域模型,它具有以下属性:StartDate, DueDate, AssignedToEmployeeId, WorkItemType, Title, Description, CreatedbyEmployeeId
.
现在,有些东西可以随着 WorkItem
的变化而分解,它归结为这样的方法:
WorkItem.ReassignToAnotherEmployee(string employeeId)
WorkItem.Postpone(DateTime newDateTime)
WorkItem.ExtendDueDate(DateTime newDueDate)
WorkItem.Describe(string description)
但是在我们的 UI 端只有一个表单,其字段对应于我们的属性和一个 Save
按钮。所以,CRUD UI。显然,这会导致像 PUT domain.com/workitems/{id}
.
问题是:如何从域模型的角度处理到达此端点的请求?
选项 1
有CRUD之类的方法WorkItem.Update(...)
吗? (这显然违背了无处不在的语言和 DDD 的全部目的)
选项 2
端点控制器调用的应用程序服务有方法 WorkItemsService.Update(...)
但在该服务中我们调用与通用语言对应的每个域模型方法?类似于:
public class WorkItemService {
...
public Update(params) {
WorkItem item = _workItemRepository.get(params.workItemId);
//i am leaving out check for which properties actually changed
//as its not crucial for this example
item.ReassignToAnotherEmployee(params.employeeId);
item.Postpone(params.newDateTime);
item.ExtendDueDate(params.newDueDate);
item.Describe(params.description);
_workItemRepository.save(item);
}
}
或者第三种选择? 这里有什么经验法则吗?
[更新]
明确地说,问题可以用某种方式重新表述:是否应该像 CRUD WorkItem.Update()
一样成为我们模型的一部分,即使我们的领域专家以某种方式表达它 我们希望能够更新一个WorkItem 还是我们应该始终避免它并寻求 “更新”对业务的实际意义是什么?
正如您所说,选项 1 几乎违反了规则集。额外提供通用 update
对您的域实体的客户没有好处。
我会选择 2ish 选项:具有应用程序级服务但反映 UL。您的控制器需要调用一个有意义的应用程序服务方法,该方法具有有意义的 parameter/command 来更改域模型的状态。
我总是尝试从客户的角度思考我的 Service/Domain 模型代码。作为这个客户,我想知道我到底叫什么。拥有像 Update 这样的 CRUD 是违反直觉的,不会帮助您遵循 UL,而且会让客户更加困惑。他们需要知道该更新方法背后的代码才能知道他们正在更改什么。
对于您的更新:不,不包括通用更新(至少不使用名称 Update
)始终反映业务 rules/processes。你的代码的客户永远不会知道我做了什么。
如果这是从特定控制器 api 端点触发的特定业务流程,您可以这样称呼它。假设您的 Update
实际上是业务流程 DoAWorkItemReassignAndPostponeDueToEmployeeWentOnVacation()
那么您可以批量执行此操作但不要使用通用更新。始终反映 UL。
你的 domain/sub-domain 天生就是 CRUD 吗?
"if our domain experts express it in a way we want to be able update a WorkItem"
如果您的 sub-domain 与 CRUD 很好地对齐,您不应该尝试强制使用域模型。 CRUD 不是 anti-pattern,实际上可以完美适合某些 sub-domain。当业务专家表达的丰富业务流程被开发人员错误地转换为 CRUD UI 和后端时,CRUD 就会出现问题,从而导致 code/UL 错位。
请注意,显式发现和建模业务流程的成本也很高。有时(例如缺乏资源)让那些生活在领域专家的头脑中是可以接受的。他们将从 paper-based 进程中驱动一个简单的 CRUD UI,而不是让系统引导他们。 CRUD 在这里可能非常好,因为虽然过程很复杂,但我们并没有试图在仍然简单的系统中对它们进行建模。
我无法判断您的域是否天生就是 CRUD,但我只是想指出,如果是,那就拥抱它并寻求更简单的业务逻辑模式(Active Record、Transaction Script 等。 ).如果您发现自己一直希望通过单个方法调用来映射每一位数据,那么您可能处于 CRUD 域中。
隔离腐败
如果您确定域模型将有利于您的模型,那么您应该尽早阻止腐败在系统中蔓延。这是通过 anti-corruption layer 完成的,在您的情况下,它负责解释 CRUD 调用并将它们转换为更有意义的业务流程。
anti-corruption 层应该位于您要保护的系统部分和 legacy/misbehaving/etc 部分之间。那将是选项#2。在这种情况下,anti-corruption 代码很可能必须将当前状态与新状态进行比较,以尝试找出所做的更改以及如何将这些更改与更明确的业务流程相关联。