spring-boot icon indicating copy to clipboard operation
spring-boot copied to clipboard

Consider deprecating @ControllerEndpoint and @RestControllerEndpoint

Open mhalbritter opened this issue 2 years ago • 11 comments

We should decide if we want to deprecate and later remove @ControllerEndpoint and @RestControllerEndpoint from the actuator. Using them ties the user to WebMVC or WebFlux and they were meant to ease the upgrade path to the weblayer-abstracting @Endpoint with @ReadOperation, etc.

Getting rid of those would pave the way for https://github.com/spring-projects/spring-boot/issues/20290.

If you're seeing this ticket and object to this idea, please comment, your feedback is very valuable. Please also explain your use case and why this use case can't be solved with @Endpoint or @WebEndpoint.

mhalbritter avatar Jul 15 '22 11:07 mhalbritter

We have some endpoints where we wanted to be able to include app and/or actuator links in the response. The low friction way we found was to move to @RestControllerEndpoint and get the HttpServletRequest and use getRequestURL() to build them. It was a while ago so I don't know if we missed some way to do that with a @WebEndpoint then or added since then...

Or if the idea of those links is an anathema to the endpoint abstraction entirely... they are strange endpoints I won't defend in general so no objections to deprecation, just a data point...

jeffbswope avatar Jul 15 '22 20:07 jeffbswope

We have quite a few @RestControllerEndpoint annotations used here.

Main use cases that I think would be useful and helpful to support going forward would be:

  • Support the convenience of @GetMapping, @PostMapping, @ResponseBody, etc.

I think most of these could likely be converted and written to conform to the new way of doing this.

  • Ability to read the servlet request body in raw form without mapping the request body it to an object, using HttpServletRequest#getInputStream()

It might be doable to do this today, but I wasn't able to find an alternative.

mmoayyed avatar Jul 21 '22 13:07 mmoayyed

Thank you, @mmoayyed.

Support the convenience of @GetMapping, @PostMapping, @ResponseBody

In Actuator endpoint terms, @GetMapping is a @ReadOperation and @PostMapping is a @WriteOperation. There's also @DeleteOperation which is the equivalent of @DeleteMapping.

Can you expand a bit on the need for @ResponseBody? The value returned from an endpoint operation is already written to the body of the response.

Ability to read the servlet request body in raw form … It might be doable to do this today

We don't support this at the moment. Any body is read up front as a source of arguments for the endpoint operation's method parameters. There's an implicit assumption here that the body can be converted into key-value pairs.

wilkinsona avatar Jul 21 '22 13:07 wilkinsona

Thank you Andy.

The main example that I found for @ResponseBody is dealing with exporting data out in form of a zip file. The endpoint creates a zip file Resource, and returns it in the form of new ResponseEntity<>(resource, headers, HttpStatus.OK) where the header contains content-disposition entries, etc. The endpoint itself is tagged as

@GetMapping(path = "/export", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody

Likewise, the reverse of this operation is handled as an import endpoint in the same component that reads the request.getInputStream(), and stores the processed data somewhere, returning some http status endpoint. The body could contain on object or a collection of objects (as would be the classical import use case)

mmoayyed avatar Jul 21 '22 13:07 mmoayyed

You can return a Resource from a @ReadOperation as we do in Boot's LogFileWebEndpoint:

https://github.com/spring-projects/spring-boot/blob/35c49afd97dd353020b96df9c537bb44d819ef7e/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/logging/LogFileWebEndpoint.java#L53-L54

I guess a missing piece is the ability to set a Content-Disposition header which WebEndpointResponse does not allow you to do at the moment.

Likewise, the reverse of this operation is handled as an import endpoint in the same component that reads the request.getInputStream(), and stores the processed data somewhere, returning some http status endpoint

On first impressions, this doesn't sound like something that you'd do as part of operating an app that's running in production so it isn't necessarily something that we'd expect to support in Actuator. Can you expand a bit on the use case please?

wilkinsona avatar Jul 21 '22 15:07 wilkinsona

Sure. I'd have to go back a few steps, so please bear with me.

This project ships a number auto-starters that control the implementation of a particular repository type. The ultimate build script (put together by an adopter in form of a overlay), allows an adopter to include the appropriate starter in their build and let that repository manage their data. I suppose there are scenarios where one would want to take data from one repository in one version, and move it over to another repository in the next version. Let's say JSON to Cassandra, Mongo to DynamoDb, etc. So the import/export operations provide a facility to do that as a point of convenience, behind an actuator endpoint that of course needs to be enabled, protected, used likely once or twice in dev, and then turned off in production as you note. Certainly, it's not something to actively use in production.

I suppose one could always use native tooling, if and when available, to handle the data migration task, or fall back onto dedicated libraries and frameworks that take care of such things. While all should be better options in theory, (and surely there is nothing stopping the advanced user to play around with such things) they add additional points of complexity and learning curve that in this particular context, a very select group of users would want to deal with. The overwhelming preference generally is to deal with an endpoint that handles such things (IIRC, there is a command-line utility available that does this as well in form a spring shell command, but I digress).

mmoayyed avatar Jul 21 '22 16:07 mmoayyed

We currently use the management endpoints as a means of providing an internal API that is served on a different port. These annotations allow us to actually provide dedicated endpoints on the management port as well as handling security on these differently.

This way we can provide an Kubernetes ingress for the public API on one port while providing management actions an internal API without ingress on another.

frederic-kneier avatar Jul 25 '22 22:07 frederic-kneier

Thanks, @frederic-kneier. Would it be fair to say that you're using an Actuator endpoint as a convenient way of exposing the API on a different port rather than because it's used as part of operating the application in production?

wilkinsona avatar Jul 26 '22 06:07 wilkinsona

Not entirely, but regarding @ControllerEndpoint and @RestControllerEndpoint I think that is true. It would be ok to use simple @Controller and @RestController annotations if we could specify the actual endpoint/port to be used and if security could be configured based on the endpoint/port. There are still management endpoints that do not match this description, but I think they could be implemented using the normal endpoints / operations.

frederic-kneier avatar Jul 26 '22 06:07 frederic-kneier

We have a few in spring cloud. I'll get details in a bit

spencergibb avatar Jul 27 '22 17:07 spencergibb

One other thing that might be useful here: @RestControllerEndpoint allows for overloading methods that address different requirements. For example, one could have multiple @GetMapping methods that handle different concerns, with different parameters or path variables. This seems more natural vs one @ReadOperation with 2-3 parameters some marked as selectors and some marked as not. Perhaps the replacement could provide the same programming model.

mmoayyed avatar Aug 03 '22 10:08 mmoayyed