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

时间:2018-12-29 03:22:10

标签: rest dto service-layer

标题表明设计服务层时的最佳实践是什么?我确实知道服务层应始终返回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);
    }
}

如果以上三种方法都不正确或不是最佳方法,请就最佳实践向我提出建议。

2 个答案:

答案 0 :(得分:2)

我来自C#背景,但是这里的概念仍然相同。

在这种情况下,我们必须将参数/状态从应用程序层传递到服务层,然后从服务层返回结果,我倾向于遵循关注点分离。服务层无需了解应用程序层/控制器的Request参数。同样,从服务层返回的信息不应与从控制器返回的信息相结合。这些是不同的层,不同的要求,不同的关注点。我们应该避免紧密耦合。

对于上述示例,我将执行以下操作:

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可能具有其他/计算字段。在这种情况下,您无需在Request对象中公开那些字段。您只将Data Contract暴露给客户,仅此而已。同样,您只将相关字段返回给客户端,而不是从服务层返回相关字段。

如果要避免在DtoRequest之间进行手动映射,objects C#具有类似AutoMapper的库。在Java世界中,我确定应该有一个等效项。可能ModelMapper可以提供帮助。

答案 1 :(得分:0)

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

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

您想要一个解决方案,其中应用程序层方法协定(参数和返回类型)以任何Java本机类型或在服务层边界中定义的类型表示。

如果我们从沃恩·弗农(Vaughn Vernon)那里获得an IDDD sample,我们可以看到他的应用程序服务方法契约是用Java本机类型定义的。考虑到他使用CQRS,他的应用程序服务命令方法也不会产生任何结果,但是我们可以看到query methods确实返回了在应用程序/服务层包中定义的DTO。

  

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

#1和#2两者都非常相似,并且从依赖关系的角度来看可能是正确的,只要在应用程序层程序包中定义了ItemDtoCreateItemRequest,但是我更喜欢#2因为输入数据类型是根据用例命名的,而不是简单地根据它处理的实体的类型来命名的:实体命名焦点更适合CRUD,因此您可能会发现很难找到输入数据类型的好名称在相同种类的实体上运行的其他用例方法。 #2也已通过CQRS进行了普及(通常将命令发送到命令总线),但CQRS并不仅限于此。沃恩·弗农(Vaughn Vernon)在IDDD samples中也使用了这种方法。请注意,您称为 request 的内容通常称为 command

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

  

例如,某些方法接收4或5个参数。埃里克·埃文斯(Eric Evans)在《清洁规范》中指出,必须避免这种方法。

这是一个很好的遵循准则,我并不是说样本无法得到改进,但是请记住,在DDD中,重点是根据通用语言(UL)命名事物并遵循尽可能紧密地因此,仅出于将参数组合在一起而将新概念强加到设计中可能会造成危害。具有讽刺意味的是,尝试这样做的过程可能仍会提供一些很好的见解,并允许发现可以丰富UL的被忽略且有用的领域概念。

PS:罗伯特·C·马丁(Robert C. Martin)写的是Clean Code,而不是以蓝皮书而闻名的埃里克·埃文斯(Eric Evans)。