我应该将REST响应放在哪里?控制器还是服务?

5
我正在使用Spring MVC构建一个Web应用程序,其中包括控制器层和服务层。我的响应被包装在自定义的包装器中,其中包含有效载荷和元数据,以描述调用的结果(有效载荷是否成功、是否仅包含部分数据等),以便我可以返回HTTP 200,并让元数据描述调用的结果给客户端。
最初,我是在被调用的服务中封装响应,并让控制器只传递响应。然而,当我需要从其他服务中调用我的服务时,这种模式并不适用,因为我必须首先解开数据,对其进行一些处理,然后再次封装并将其发送回控制器。因此,我开始在控制器中构建响应。
问题在于,我希望尽可能保持我的控制器小,以便它们清晰易读地映射URL到服务。
控制器是否是构建REST响应包装器的正确位置,还是应该由服务来完成?
3个回答

6
通过将与HTTP相关的部分放入控制器中,您将获得更好的关注点分离。这是我实现Spring Web服务的方式,对我非常有效。根据我的经验,在此处分离后,重构服务变得非常容易。
您提到您喜欢尽可能保持控制器的小巧。控制器往往非常简单,而服务代码往往更复杂。因此,您可能会发现您的服务代码更容易维护,代价是在控制器中有更多的代码。复杂性更加均匀分布。
将与HTTP相关的代码放入控制器有助于解决以下问题:
- 异常处理 - 您可能不希望通过在返回的对象中放置特殊标志来处理错误,但这就是HTTP的工作方式。Spring MVC提供了一个很好的机制,通过@ExceptionHandler注释返回HTTP错误时的故障。这是这种分离的一个巨大优势。 - HTTP头 - 有时,响应对象的一部分比作为响应JSON的一部分更适合作为HTTP头。使用POJO操作内部客户端要容易得多,而不是知道他们必须从HTTP头提取一些信息。 - 返回列表 - 有时API应仅返回列表。但是,作为规则,我从不将JSON数组作为HTTP服务的根JSON对象返回。如果需要返回更多内容,则重构内部代码比制作新URL或打破现有客户端要容易得多。您可以在控制器级别将列表包装在JSON对象中。 - Spring解耦 - 我们的服务不依赖于Spring Framework。唯一的例外是RestTemplate,它不是Spring MVC的一部分。如果希望,我们的代码可以轻松过渡到另一个Web框架。(当然还会有其他大的更改,比如应用程序上下文)。
我只遇到过一个情况,需要让这些界限更加模糊。那就是当我需要区分200(OK)和201(已创建)时。控制器代码通常调用一个否则不会返回任何东西的服务方法(仅在失败时抛出异常)。我不得不添加返回值来区分更新和创建。这不会使内部客户端代码更复杂,因为调用者可以忽略返回值。但是,在仅针对控制器的情况下使用返回对象似乎有些不理想。

1
控制器是否是构建REST响应包装器的适当位置,还是应该由服务处理?
是的,这就是有不同层的目的:分离不同的关注点。控制器只是服务层的一个消费者,虽然大部分时间是唯一的消费者。服务必须不知道消费者正在使用其数据做什么。想象一下Meta-Services消耗和处理几个服务的数据。如果这些服务接收到包装的数据,它们将不得不解包数据才能进行操作。这是糟糕设计的迹象。
控制器(在Spring @Controller下)是与Web世界交互的接口。这是在任何格式中包装数据的正确位置:它通过网络发送,因此必须以某种方式包装。消费者期望封装的数据。
因此,您有控制器,它们启动检索并包装结果,以及检索数据的服务:明确的关注点分离。

1
考虑以下情况:假设你正在返回JSON的REST控制器中,某个新的外部数据消费者需要完全相同的数据,但只能处理XML。你更愿意做什么?编写一个新的服务层,如果当前服务层返回JSON,则返回XML,还是只编写一个新的控制器方法,从通用服务层返回XML?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接