Search code examples
springspring-bootweb-servicesarchitecture

Should the controller call a single function from service layer or call multiple service functions based on required sequence?


Here is a typical Login web service I wrote. I am using the layered architecture (Controller , Service , Model).

Controller :

@PostMapping("/login")
public ResponseEntity<Map<String , Object>>login(@RequestBody LoginRequest loginReq){
    String validationFailedMsg = service.validateLogin(loginReq.getEmail() , loginReq.getPassword());
    if(validationFailedMsg != null) {
        BodyBuilder response= ResponseEntity.status(409);
        Map<String , Object> m = new HashMap<>();
        m.put("err", validationFailedMsg);
        return response.body(m);
    }
    AbstractMap.SimpleEntry<String, String> tokens = service.loginUser(loginReq.getEmail() , loginReq.getPassword());
    if(tokens == null) {
        BodyBuilder response= ResponseEntity.status(409);
        Map<String , Object> m = new HashMap<>();
        m.put("err", "Password is incorrect");
        return response.body(m);
    }
    Map<String , Object> m = new HashMap<>();
    m.put("access_token", tokens.getKey());
    m.put("refresh_token", tokens.getValue());
    return ResponseEntity.ok(m);
}

I am first validating the request eg : checking if user exists and all fields are present. After that I am calling the loginUser in service layer. If any errors are encountered eg : Password is incorrect, I handle it in the controller.

Here is the register route where I am calling more functions from the service layer. Does this logic come under business logic and should I move it into the service layer?

@PostMapping("/register")
public ResponseEntity<Map<String, Object>> register(@RequestBody Person person) {
    String validationFailedMsg = service.validateRegister(person);
    
    if(validationFailedMsg != null) {
        System.out.print(validationFailedMsg);
        BodyBuilder response= ResponseEntity.status(409);
        Map<String , Object> m = new HashMap<>();
        m.put("err", validationFailedMsg);
        return response.body(m);
    }
    person.setPassword(service.hashPassword(person.getPassword()));
    Person newPerson = service.createPerson(person);
    service.emailVerificationSendOTP(newPerson.getEmail());
    Map<String , Object> response = new HashMap<>();
    response.put("msg" , "Enter OTP sent by mail");
    response.put("Person", newPerson);
    return ResponseEntity.ok(response);
}

Basically I want to know what really is business logic and it would be great if it could be explained with the help of login and register routes. Thanks.


Solution

  • I hope I can help a bit. As said in comments, the idea of the controller is just to be a bridge from the outside world to logic defined in the service, This also add reuse-ability.

    If you put too much logic in the Controller, you will end with what it's called a Fat Controller

    For validation, spring provides validator that you can use in your controller with the @Valid annotation, and this will providde you with a BindingResults (that's the validation results). Please notice that the order is very important, the BindingResult must be after the @Valid. Here you can learn more

    And you can throw an exception if validation fails. Rewrite your method as follow:

    @PostMapping("/register")
    public ResponseEntity<Map<String, Object>> register(@Valid @RequestBody Person person, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            // Validations failed!
            throw new InvalidInputException(bindingResult);
        }
    
        ...
    }
    

    To use that validation mechanism you can add some annotation to your Person, for example:

    public class Person{
        @NotNull
        @NotEmpty
        String username;
    
        @NotEmpty
        String pass;
    }
    

    You can learn more about validation here

    This could be a basic first filter for some input validadtion, then if you need more checks (like for example, the user must not already exits) that should be done in the service layer. How granular you want your service to be is just up to you and your needs.