简体   繁体   中英

3 tiers architecture best practice using Spring Boot

I work with 3-tier architecture in a Spring Boot app. I created 3 packages (model, service, controller), but what I did, service calls a repo function with try catch, and then I call it in controller

Example:

Service:

public ResponseEntity<List<Customer>> getAllCustomers() {
    try {
        List<Customer> customers = new ArrayList<Customer>();
        cutomerRepository.findAll().forEach(customers::add);
        if (customers.isEmpty()) {
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
        return new ResponseEntity<>(customers, HttpStatus.OK);
        } catch (Exception e) {
        return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Controller

@GetMapping("/viewList")
private ResponseEntity<?> getAllCustomers()
{
    try{
        return customerService.getAllCustomers();
    }catch (Exception exception){
        return new ResponseEntity<String>("Customers is not found", HttpStatus.METHOD_FAILURE);

    }
}

Is that correct? I think I should put in services only customerRepository.findAll() without any other logic or code, but I'm not sure. Any idea?

The Service layer should contain logic, so that is OK.

But it should not contain any classes from the Controller layer, as this would leak information from an "upper" layer into a "lower" layer. This means your Service should not return a ResponseEntity as this is from the Controller layer. Instead it should return simply a list of Customers and let the Controller construct the ResponseEntity out of it.
Otherwise your Service will always be limited to be called by this specific Controller. It would not be reusable to be called by another service of a different type of Controller, that does not use an HTTP ResponseEntity.

The best approach in my opinion is the following.

Your Service layer should not return ResponseEntity<List<Customer>> as it currently does. It should instead return List<Customer> .

This is already in the above answer but wanted to answer to extend the content a bit more.

The service also when modified to return List<Customer> should handle the exceptions with Application specific exceptions. So you create your own exception for your application, the model for this exception and also you create an Exception Advice class where all those application exceptions are handled in a general way. So your service will just throw the exception, the controller will not catch it and it will be handled by the Advice class (annotated with @ControllerAdvice ) which will handle all the uncaught exceptions and return appropriate responses. There are also some more options to handle exceptions in generic way in Spring.

I am attaching the following code as an example

Class to handle all exceptions that bubble up from controllers.

@ControllerAdvice
public class ErrorHandler {

    @ExceptionHandler(ApplicationException.class)
    public ResponseEntity handleApplicationException(ApplicationException e) {
        return ResponseEntity.status(e.getCustomError().getCode()).body(e.getCustomError());
    }
}

Some application specific exception (The name could be more specific)

@Getter
@Setter
public class ApplicationException extends RuntimeException  {

    private CustomError customError;

    public ApplicationException(CustomError customError){
        super();
        this.customError = customError;
    }
}

An Error object to be returned to the client when exception happens

@Getter
@Setter
@NoArgsConstructor
public class CustomError {

    private int code;
    private String message;
    private String cause;

    public CustomError(int code, String message, String cause) {
        this.code = code;
        this.message = message;
        this.cause = cause;
    }

    @Override
    public String toString() {
        return "CustomError{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", cause='" + cause + '\'' +
                '}';
    }
}

Your Service

public List<Customer> getAllCustomers() {
     try {
        List<Customer> customers = new ArrayList<Customer>();
        cutomerRepository.findAll().forEach(customers::add);
        if (customers.isEmpty()) {
            throw new ApplicationException(new CustomError(204, "No Content", "Customers do not exist"));
        }
        return new ResponseEntity<>(customers, HttpStatus.OK);
        } catch (Exception e) {
        throw new ApplicationException(new CustomError(500, "Server Error", "Disclose to the client or not what the cause of the error in the server was"));
    }
}

The controller it self could also inspect the input information that it receives and if needed could throw it self an application specific exception or just return an appropriate response with what is false in the input.

This way the Controller is just handling the input/output between the user and the service layer.

The Service is just handling input/output of data from persistent layer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM