-
Notifications
You must be signed in to change notification settings - Fork 38.6k
Description
When an API requires multiple values, the number of arguments for HTTP service method can be a bit painful. For example:
interface ExampleService {
@PostMapping(path = "/example", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
void example(@RequestBody Object example, @RequestHeader("X-Custom-Requirement") String foo,
@RequestHeader("X-Another-Custom-Requirement") String bar,
@RequestParam String one,
@RequestParam String two,
@RequestParam String three);
}
The service method can be simplified to:
@PostMapping(path = "/example", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
void example(@RequestBody Object example, @RequestHeader HttpHeaders httpHeaders, @RequestParam MultiValueMap<String, Object> params);
Which is better, but I think it would be easier or nicer even to allow a single argument that encapsulates all of the request details.
That argument could also override the predefined ones such as the consumes
or path
from the exchange annotation. This is more or less what UriBuilderFactory
does today for the URL.
If I'm not mistaken, RestTemplate
provided something similar through RequestEntity
.
Note I know that you can drop down to the 'lower level' RestClient
to fully customize the request as indicated in this table in the documentation. But providing that control at the HTTP interface level I think will be a welcome addition.
Looking at Framework's code, HttpServiceArgumentResolver
is responsible for consuming the various annotations and applying those values to HttpRequestValues
. So a HttpServiceArgumentResolver
that accepts a HttpRequestValues
or similar may work I think.
My company has variety of modern and legacy APIs and Web services. While we try to conform to some standard when modernizing legacy services or APIs, there still exists APIs that require a lot of information in their request.
To work around this, I started wrapping the request details into an intermediary object and then delegate to the service method. This leads to cleaner code (IMO). For example:
record RequestSpecification(Object body, String param, String header) {}
interface ExampleService {
@PostMapping(path = "/example", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
void example(@RequestBody Object example, @RequestHeader HttpHeaders httpHeaders, @RequestParam MultiValueMap<String, Object> params);
default void example(RequestSpecification specification) {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Custom-Requirement", specification.header());
MultiValueMap<String, Object> params = MultiValueMap.fromSingleValue(Map.of("one", specification.param()));
example(specification.body(), headers, params);
}
}