我需要提前道歉,我可能没有给你足够的食谱,但这是我对你的问题的看法。
你提到了设计模式,但你也用domain-driven design标签标记了你的问题,所以我假设你的意思是在你的项目中使用 DDD 战术模式。我想强调的是,DDD 不是一组设计模式,也不是设计模式或架构风格,它是系统在设计时考虑到业务的方式。
在 DDD 中,最有用的模式之一是聚合模式。聚合是形成一致性边界的一组实体,它拥有足够的信息来自行做出任何业务决策,假设该组实体的所有业务规则都组装在一个地方。
当我们查看应用程序端时,我们经常使用应用程序服务来检索聚合状态,调用聚合根上的一个或多个方法并将新状态持久化回来。重要的是要意识到所有这一切都发生在一次交易中,因此工作单元模式间接地应用在这里。我之所以写间接只是因为聚合本身是一个事务边界,而您的应用程序执行的一个命令是工作单元。聚合持久化可以使用存储库模式来完成,这就是我们所说的实现细节。
现在,我将尝试解构您的具体观点。
数据验证,应该在哪里做?
我倾向于将 验证 视为确保您的应用程序从外部世界接收的命令在某种程度上是有效的。这涉及控制所需的命令字段不为空并且包含正确的值类型,就像您不能为数字字段发送字母并且电子邮件字段确实包含看起来像有效电子邮件的内容。您可以实施多层验证,让最明显的验证更靠近客户端,以便您的用户获得快速反馈。您可以在 UI 端和边缘(在您的情况下为 REST API)进行这些简单检查。
当命令传递给应用程序服务时,它需要确保它获得正确的域实体(聚合)状态,因此至少您通过尝试从数据库中获取实体来检查实体是否存在。您还可以使用值对象,例如电子邮件或地址,并从作为命令属性传递的原始类型构造它们。值对象是放置一些业务规则的理想场所,并且通过尝试构建它们自然会发生验证。
最后一个防御层是您的域模型。从前面的所有步骤中,您可以确定您已经拥有有效的值对象和有效的域实体可以使用。聚合保护其不变量,根据定义,不应将聚合置于无效状态。我通常不称之为验证。
事件?这应该在哪里触发?示例:当用户注册时,我想向他们发送电子邮件。
如果您谈论的是域事件,并且您不使用事件溯源,则必须确保您的域事件在同一事务中发布到您的交付媒介。当您的应用程序发布域事件但未能保留修改后的实体状态时,您应该避免这种可能性,反之亦然。当这种故障发生时,您的整个系统将处于不一致的状态。
业务层应该采用DTO还是Domain模型?
我没有完全理解这个问题,但您的业务层是您的领域模型。领域模型通常由实体、值对象和领域服务组成。您调用域模型要求它做某事,在这个阶段不涉及 DTO。
从 DTO 到域模型的转换应该去哪里?
我将完全避免在这种情况下使用 DTO 术语,因为您的实体状态可以被视为 DTO 以及您的 API 模型和事件。如果你在谈论你的 API 合约,它是一个命令并且它没有得到“转换”,你的 API 从 API 获取命令并调用你的域模型。
什么应该进入控制器?
API 控制器只是您的应用程序的边缘。 API 负责传输(HTTP 或其他)、序列化、身份验证和一些授权问题以及异常处理。其主要目的是确保 API 请求看起来合法并将其传递给应用服务。
如果您正在寻找使用 WebAPI、EF 并实现一些战术 DDD 模式的示例应用程序,您可能需要查看 Packt 存储库 https://github.com/PacktPublishing/Hands-On-Domain-Driven-Design-with-.NET-Core/tree/master/Chapter09/ef-core 中的工作示例