Skip to content

Document custom HttpServiceArgumentResolver usage #34227

@ciscoo

Description

@ciscoo

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);
    }
}

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: documentationA documentation task

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions