What is a better way to design an API on a @Controller method when request parameters are mutually exclusive?
Let's say that there is an API to provide a List of Users that matches the request parameters.
Code is:
public ResponseEntity getList(@RequestParam(required = false) Integer userId,
@RequestParam(required = false) User.Type userType,
@RequestParam(required = false) Integer age) {
List<User> userList = null;
if (userId != null) {
//logic
userList = getUserByUserId()
} else if (userType != null) {
//logic
userList = getUserByType()
} else if (age != null) {
//logic
userList = getListByAge()
} else {
userList = getAllWithoutCondition();
}
return ResponseEntity.ok(userList);
}
Here is the point:
The user cannot query with more than one request parameter. Only one request parameter or no request parameter is valid (only one of userId
, age
, or type
should exist in a request).
I am not sure what the better way to design an API for this situation is. Can you give me some advice?
I like the approach suggested by the guys in the comments:
@RequestMapping(value = "...", params = {"!userType", "!userAge"})
public ResponseEntity<List<User>> getListByUserId(@RequestParam Integer userId) { ... }
// similarly, define two more
It looks robust and feasible until you start managing restrictions for each endpoint. It looks tedious and hard to maintain. Furthermore, I am not sure how the endpoint that takes no params would react. Would it get called or be shadowed by other methods?
Instead of writing restrictions, I suggest introducing conditions - the requirements for each endpoint. It could be a Map<String, Function<String, List<User>>>
in the next format:
<param name> -> <action to get a list>
I also advise you collect all the incoming request params into a single Map<String, String>
to validate it by size.
public class Controller {
private Map<String, Function<String, List<User>>> handlers = new HashMap<>();
{
handlers.put("userId", id -> getUsersById(Integer.valueOf(id)));
handlers.put("userType", type -> getUsersByType(User.Type.valueOf(type)));
handlers.put("userAge", age -> getUsersByAge(Integer.valueOf(age)));
}
@RequestMapping("...")
public ResponseEntity<List<User>> getList(@RequestParam Map<String, String> params) {
if (params.size() > 1) {
return ResponseEntity.unprocessableEntity().build();
}
if (params.size() == 0) {
return ResponseEntity.ok(getAllWithoutCondition());
}
Map.Entry<String, String> paramEntry = params.entrySet().iterator().next();
return ResponseEntity.ok(handlers.get(paramEntry.getKey()).apply(paramEntry.getValue()));
}
private List<User> getAllWithoutCondition() { ... }
private List<User> getUsersById(Integer id) { ... }
private List<User> getUsersByType(User.Type type) { ... }
private List<User> getUsersByAge(Integer age) { ... }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With