当前位置: 首页 > 知识库问答 >
问题:

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

汪坚
2023-03-14

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

我提出以下三点建议:

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

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个答案

司空温书
2023-03-14

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

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

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

class Controller
{
     @Autowired
     private ItemService service;

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

这可能感觉像更多的工作,因为现在您需要编写额外的代码来转换不同的对象。然而,这给了您很大的灵活性,并使代码更容易维护。例如:与CreateItemDto相比,CreateItemDto可能具有额外的/计算字段。在这种情况下,您不需要在请求对象中公开这些字段。您只向客户端公开您的数据合同,仅此而已。类似地,您只将相关字段返回给客户端,而不是从服务层返回的字段。

如果您想避免手动映射Dto请求对象C#具有类似于AutoMapper的库。在java世界里,我相信应该有一个等价物。可能是ModelMapper可以帮助。

司空浩邈
2023-03-14

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

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

您需要一个应用层方法收缩(参数)的解决方案

如果我们从Vaughn Vernon获取一个IDDD示例,我们可以看到他的应用程序服务方法契约是用Java本机类型定义的。他的应用程序服务命令方法也不会产生任何结果,因为他使用了CQR,但我们可以看到查询方法确实返回了在应用程序/服务层包中定义的DTO。

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

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

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

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

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

PS:Robert C.马丁写了《清洁守则》,而不是以蓝皮书闻名的埃里克·埃文斯。

 类似资料:
  • 我明白,对于处理器,我只需在文件夹下的中编写代码,然后从中创建一个nar文件。但是在控制器服务的情况下,我有4个文件夹生成。我可以看到两个java文件。 > 显示在文件夹下 显示在文件夹下 现在,为什么在自定义控制器服务的情况下生成两个java文件,而在自定义处理器的情况下只生成一个java文件。另外,由于我试图模仿服务,其中的java文件中有两个我应该复制服务的原始源代码。 请从头开始指导我创建

  • 问题内容: 我正在尝试允许javascript与Node.js服务器通信。 POST请求(Web浏览器) 现在,Node.js服务器代码如下所示。在用于GET请求之前。我不确定如何使其与POST请求一起使用。 服务器(Node.js) 在此先感谢您的帮助。 问题答案: 以下代码显示了如何从HTML表单读取值。正如@pimvdb所说,您需要使用request.on(’data’…)来捕获正文的内容。

  • 我看到了Apache NiFi的这个示例(https://medium.com/hashmapinc/creating-custom-processors-and-controllers-in-apache-nifi-e14148740ea),但仍然无法理解如何为自定义处理器和控制器服务实现打包结构。 我的目标是创建一个自定义控制器服务来使用我的多个自定义处理器。我的maven结构应该是什么样子?

  • 我编写了一个自定义的NiFi处理器,用于在BigQuery上执行一些任务。我希望能够重用与NiFi捆绑在一起的GCP凭据提供者控制器服务,以用于身份验证。这可能吗?我尝试将nifi-gcp-processors maven依赖项添加到我的项目中,它成功构建,但当我尝试用我的nar启动NiFi时,它失败了,出现了这个错误消息。

  • 问题内容: 是否可以在JAX-RS下以REST方法访问Request对象? 我刚刚发现 问题答案: 在JAX- RS上,必须使用@Context注释Request参数: 您也可以选择注入: UriInfo HttpHeaders 安全上下文 HttpServletRequest

  • 问题内容: 在课堂上,我们现在学习如何构建Spring应用程序,即使没有直接涉及spring,我们也学习了如何为DAO和服务层对象创建接口。 如果我错了,请纠正我:DAO层是非常抽象的:它仅包含CRUD操作,并进一步用于读取数据(即:获取所有对象,获取特定对象等) 服务层:包含用于创建事物和删除事物的服务,这是业务逻辑应该存在的地方。 现在,所有这些对于服务层来说都是有意义的。除了“更新”对象。你