服务层是否应该接受来自控制器的 DTO 或自定义请求 object?

Should service layer accept a DTO or a custom request object from the controller?

如标题所示,设计服务层时的最佳实践是什么?。我确实理解服务层应该始终 return DTO,以便域(实体)object 保留在服务层中。但是控制器对服务层的输入应该是什么?

我在下面提出三点自己的建议:

方法一: 在此方法中,域 object(项目)保留在服务层中。

class Controller
{
    @Autowired
    private ItemService service;

    public ItemDTO createItem(IntemDTO dto)
    {
        // service layer returns a DTO object and accepts a DTO object
        return service.createItem(dto);
    }
}

方法二: 这是服务层接收自定义请求的地方 object。我在 AWS Java SDK 和 Google Cloud Java API

中广泛地看到了这种模式
class Controller
{
    @Autowired
    private ItemService service;

    public ItemDTO createItem(CreateItemRequest request)
    {
        // service layer returns a DTO object and accepts a custom request object
        return service.createItem(request);
    }
}

方法三: 服务层接受 DTO 和 return 域 object。我不喜欢这种方法。但它在我的工作场所被广泛使用。

class Controller
{
    @Autowired
    private ItemService service;

    public ItemDTO createItem(CreateItemRequest request)
    {
        // service layer returns a DTO object and accepts a DTO object
        Item item = service.createItem(request);
        return ItemDTO.fromEntity(item);
    }
}

如果以上 3 种方法都不正确或不是最好的方法,请告诉我最佳做法。

从概念上讲,您希望能够跨表示层并通过不同的访问端口(例如,控制台应用程序通过网络套接字与您的应用程序通信)重用 service/application 层。此外,您不希望每个域更改都冒泡到应用程序层之上的层中。

控制器在概念上属于表现层。因此,您不希望应用程序层耦合到在定义控制器的同一概念层中定义的契约上。您也不希望控制器依赖于域,或者它可能必须在域时更改变化。

您需要一个解决方案,其中应用层方法契约(参数和 return 类型)以任何 Java 本机类型或服务层边界中定义的类型表示。

如果我们采用 an IDDD sample from Vaughn Vernon, we can see that his application service method contracts are defined in Java native types. His application service command methods also do not yield any result given he used CQRS, but we can see query methods 执行 return 在 application/service 层包中定义的 DTO。

In the above listed 3 methods which ones are correct/wrong?

两者,#1 和#2 非常相似,从依赖的角度来看可能是正确的,只要 ItemDtoCreateItemRequest 在应用层包中定义,但我更喜欢#2 因为输入数据类型是根据用例命名的,而不仅仅是它处理的实体类型:entity-naming-focus 更适合 CRUD,因此您可能会发现很难为输入数据找到好的名称在同一类实体上运行的其他用例方法的类型。 #2 也通过 CQRS(命令通常发送到命令总线)得到普及,但并非 CQRS 独有。 Vaughn Vernon 在 IDDD samples 中也使用了这种方法。请注意,你所谓的request通常被称为command.

但是,#3 并不理想,因为它将控制器(表示层)与域耦合。

For example, some methods receive 4 or 5 args. According to Eric Evans in Clean Code, such methods must be avoided.

这是一个很好的指导方针,我并不是说示例无法改进,但请记住,在 DDD 中,重点放在根据通用语言 (UL) 和以下内容命名事物它尽可能接近。因此,仅仅为了将论点组合在一起而将新概念强加到设计中可能是有害的。具有讽刺意味的是,尝试这样做的过程可能仍然会提供一些很好的见解,并允许发现可以丰富 UL 的被忽视和有用的领域概念。

PS: Robert C. Martin 写的是 Clean Code,不是 Eric Evans 写的蓝皮书。

我来自 C# 背景,但概念在这里保持不变。

在这种情况下,我们必须将 parameters/state 从应用层传递到服务层,然后 return 从服务层得到结果,我倾向于遵循分离-担忧。服务层不需要知道应用层/控制器的 Request 参数。同样,您从服务层 return 获取的内容不应与您从控制器获取的内容 return 耦合。这些是不同的层、不同的需求、不同的关注点。我们应该避免紧耦合。

对于上面的例子,我会这样做:

class Controller
{
     @Autowired
     private ItemService service;

     public ItemResponse createItem(CreateItemRequest request)
     {
        var creatItemDto = GetDTo(request);
        var itemDto = service.createItem(createItemDto);
        return GetItemResponse(itemDto);
    }
}

这可能感觉需要更多工作,因为现在您需要编写额外的代码来转换不同的对象。但是,这为您提供了极大的灵活性,并使代码更易于维护。例如:与 CreateItemRequest 相比,CreateItemDto 可能有额外的/计算字段。在这种情况下,您不需要在 Request 对象中公开这些字段。您仅将 Data Contract 暴露给客户端,仅此而已。同样,您只 return 客户端的相关字段,而不是您 return 来自服务层的内容。

如果您想避免 DtoRequest 之间的手动映射 objects C# 有像 AutoMapper 这样的库。在 java 世界中,我相信应该有一个等价物。可能 ModelMapper 可以提供帮助。