从概念上讲,您希望能够跨表示层并通过不同的访问端口重用服务/应用程序层(例如控制台应用程序通过 Web 套接字与您的应用程序通信)。此外,您不希望每个域更改都冒泡到应用程序层之上的层中。
控制器在概念上属于表示层。因此,您不希望应用程序层耦合到控制器定义在同一概念层中定义的契约上。您也不希望控制器依赖于域,否则当域时它可能必须更改变化。
您需要一个解决方案,其中应用程序层方法契约(参数和返回类型)以任何 Java 本机类型或服务层边界中定义的类型表示。
如果我们采取IDDD 样本 https://github.com/VaughnVernon/IDDD_Samples/blob/master/iddd_collaboration/src/main/java/com/saasovation/collaboration/application/calendar/CalendarEntryApplicationService.java从Vaughn Vernon,我们可以看到他的应用程序服务方法契约是用Java原生类型定义的。鉴于他使用了 CQRS,他的应用程序服务命令方法也不会产生任何结果,但我们可以看到查询方式 https://github.com/VaughnVernon/IDDD_Samples/blob/master/iddd_collaboration/src/main/java/com/saasovation/collaboration/application/calendar/CalendarEntryQueryService.java返回应用程序/服务层包中定义的 DTO。
上面列出的 3 种方法哪些是正确的/错误的?
#1 和 #2 都非常相似,从依赖性的角度来看可能是正确的,只要ItemDto
and CreateItemRequest
在应用程序层包中定义,但我更喜欢#2,因为输入数据类型是根据用例命名的,而不是简单地根据它处理的实体类型命名:实体命名焦点更适合 CRUD,因此您可能会发现很难为在同一类型实体上运行的其他用例方法的输入数据类型找到好的名称。 #2 也通过 CQRS(命令通常发送到命令总线)而得到普及,但并非 CQRS 所独有。沃恩·弗农 (Vaughn Vernon) 在IDDD样本 https://github.com/VaughnVernon/IDDD_Samples/blob/master/iddd_identityaccess/src/main/java/com/saasovation/identityaccess/application/AccessApplicationService.java。请注意你所说的request通常被称为command.
然而,#3 并不理想,因为它将控制器(表示层)与域耦合在一起。
例如,某些方法接收 4 或 5 个参数。根据埃里克·埃文斯(Eric Evans)在《清洁代码》中的说法,必须避免这种方法。
这是一个很好的遵循准则,我并不是说这些示例无法改进,但请记住,在 DDD 中,重点是根据通用语言 (UL) 命名事物并尽可能严格地遵循它。因此,仅仅为了将论点组合在一起而将新概念强加到设计中可能是有害的。具有讽刺意味的是,尝试这样做的过程仍然可能提供一些好的见解,并允许发现可以丰富 UL 的被忽视和有用的领域概念。
PS:《Clean Code》的作者是 Robert C. Martin,而不是因蓝皮书而闻名的 Eric Evans。