Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a better way to design an API for mutually exclusive request parameters?

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?

like image 908
Soeun Park Avatar asked Nov 03 '18 11:11

Soeun Park


1 Answers

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) { ... }

}
like image 140
Andrew Tobilko Avatar answered Nov 09 '22 10:11

Andrew Tobilko