Rest 服务层应该接受来自控制器的DTO或自定义请求对象吗?

Rest 服务层应该接受来自控制器的DTO或自定义请求对象吗?,rest,dto,service-layer,Rest,Dto,Service Layer,正如标题所示,设计服务层时的最佳实践是什么?。我知道服务层应该始终返回DTO,以便在服务层中保留域(实体)对象。但是控制器对服务层的输入应该是什么呢 我提出以下三点建议: 方法1: 在此方法中,域对象(项)保留在服务层中 class Controller { @Autowired private ItemService service; public ItemDTO createItem(IntemDTO dto) { // service la

正如标题所示,设计服务层时的最佳实践是什么?。我知道服务层应该始终返回DTO,以便在服务层中保留域(实体)对象。但是控制器对服务层的输入应该是什么呢

我提出以下三点建议:

方法1: 在此方法中,域对象(项)保留在服务层中

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);
    }
}
方法2: 这是服务层接收自定义请求对象的地方。我在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);
    }
}
方法3: 服务层接受DTO并返回域对象。我不喜欢这种方法。但它在我的工作场所被广泛使用

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);
    }
}

如果以上三种方法都不正确或不是最好的方法,请就最佳做法向我提供建议。

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

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

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

如果我们从沃恩·弗农(Vaughn Vernon)那里了解到,他的应用程序服务方法契约是用Java本机类型定义的。他的应用程序服务命令方法也不会产生任何结果,因为他使用了CQR,但我们可以看到它返回应用程序/服务层包中定义的DTO

在上面列出的3种方法中,哪些是正确的/错误的

#1和#2非常相似,从依赖关系的角度来看可能是正确的,只要在应用层包中定义了
ItemDto
CreateItemRequest
,但我更倾向于#2,因为输入数据类型是根据用例命名的,而不仅仅是它所处理的实体类型:实体命名焦点更适合CRUD,因此您可能会发现很难为在同一类型实体上运行的其他用例方法的输入数据类型找到好的名称#2也已通过CQR(命令通常发送到命令总线)普及,但并非CQR独有。沃恩·弗农也使用这种方法。请注意,您所称的请求通常称为命令

然而,#3并不理想,因为它将控制器(表示层)与域相耦合

例如,某些方法接收4或5个参数。根据Eric Evans在Clean Code中的观点,必须避免使用此类方法

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

PS:Robert C.Martin写的是干净的代码,而不是以蓝皮书闻名的Eric Evans。

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

在这种情况下,我们必须将参数/状态从应用程序层传递到服务层,然后从服务层返回结果,我倾向于遵循关注点分离。服务层不需要知道应用层/控制器的
请求
参数。类似地,从服务层返回的内容不应与从控制器返回的内容耦合。这些是不同的层次、不同的需求、不同的关注点。我们应该避免紧密耦合

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

class Controller
{
     @Autowired
     private ItemService service;

     public ItemResponse createItem(CreateItemRequest request)
     {
        var creatItemDto = GetDTo(request);
        var itemDto = service.createItem(createItemDto);
        return GetItemResponse(itemDto);
    }
}
这可能需要更多的工作,因为现在需要编写额外的代码来转换不同的对象。然而,这给了您很大的灵活性,使代码更易于维护。例如:
CreateItemDto
CreateItemRequest
相比,可能具有额外的/计算字段。在这种情况下,您不需要在
请求
对象中公开这些字段。您只向客户机公开您的
数据契约
,仅此而已。类似地,您只将相关字段返回给客户机,而不是从服务层返回的字段

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);
    }
}

如果您想避免在
Dto
Request
对象之间进行手动映射,C#具有类似
AutoMapper
的库。在java世界中,我确信应该有一个等价物。可能会有帮助。

我不确定这个问题是否与
领域驱动设计有关。你能详细说明一下DDD是如何出现在这里的吗?对不起。我已从标签列表中删除DDD,因此我在理解您的答案时遇到问题。在上面列出的3种方法中,哪些是正确的/错误的?。我还查看了您链接的代码示例。我不能完全肯定其中的一些做法。例如,某些方法接收4或5个参数。根据Eric Evans在《干净代码》中的说法,必须避免使用此类方法。@Vino FYI-自从你发表评论后,我更新了答案。谢谢你的见解。是的,我把作者弄混了