I'm trying to learn spring and to achieve that i'm building a REST application from scratch. I'm confused where should I check constraints in my application: Controller layer vs. Service layer.
For example, in create user method I want to check if there is any other user with the same email, since email is unique in my database. I also want to check if password matches(password and "confirm password" fields) etc.
Currently, in my implementation, all this things are verified in Controller layer so I can return a ResponseEntity for every approach.
#PostMapping("/signUp")
public ResponseEntity<Object> createUser(#RequestBody RegisterUserDto user) {
if (userService.getUserByEmail(user.getEmailAddress()) != null) {
return ResponseEntity.badRequest().body("email already exists");
}
if (!user.getPassword().equals(user.getConfirmPassword())) {
return ResponseEntity.badRequest().body("passwords are not the same");
}
User savedUser = null;
try {
savedUser = userService.createUser(userDtoConversions.convertToEntityRegister(user));
} catch (ParseException e) {
e.printStackTrace();
}
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(savedUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
Create user method in Service layer:
#Override
#Transactional
public User createUser(User newUser) {
newUser.setDateCreated(new Date());
return userRepository.save(newUser);
}
So which approach is better? If I checks constraints and validations in Service layer, what should I return so I would know in my controller why create user fails?
In my mind the best place to handle exceptions is the service layer. For my a REST controller method should, at most, handle the request and pass it over to a service method.
With this approach you have very clearly defined layers that do a very clearly defined job. For example your service layer will handle the validation of the request, the persisting action and also will provide (if needed) a return object to the controller, which then will wrap into the appropriate response object (ResponseEntity in you case).
With that in mind, there is nothing stopping you to throw any kind of exceptions in the service layer and have translated into proper responses. Spring has a very neat and powerful mechanism that does precisely that which is called an exception handler.
So in your case for the password checking action you could do something like:
if (!user.getPassword().equals(user.getConfirmPassword())) {
throw new PasswordMismatchException("Passwords are not the same for user:: " + user.getName());
}
Where the PasswordMismatchException is a RuntimeException. With something like that, you can then go ahead and setup an ExceptionHandler along with the appropriate method to intercept this and translate it into a response. A simple example would be:
#RestControllerAdvice
public class ApplicationExceptionHandler {
#ExceptionHandler(PasswordMismatchException.class)
public ResponseEntity<String> handleBadPasswords(PasswordMismatchException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
You can read up for more on this on Spring's documentation:
Spring ExceptionHandler
Exception Handling in Spring
Related
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.
I am implementing Spring Data REST in one of my project. I have to write a few custom Repository in order to write custom queries. I am using a Controller in front of my REST Repository. In order to get a HATEOAS response, I am using PersistentEntityResourceAssembler in my controller. This controller works fine for a single entity but in case of a list, I get an exception "PersistentEntity must not be null!"
#RequestMapping(value="/employmentType", method=RequestMethod.GET, produces="application/hal+json")
#ResponseBody
public ResponseEntity<?> getEmploymentTypes(HttpServletRequest request, HttpServletResponse response,PersistentEntityResourceAssembler resourceAssembler) throws TenantUnavailableException, TenantInvalidException
{
try
{
List<EmploymentType> employmentTypeList = employmentTypeRepository.findAll();
if(null==employmentTypeList || employmentTypeList.size()==0)
return new ResponseEntity<ApiResponse>(new ApiResponse(false, ENTITY_NOT_FOUND),
HttpStatus.NOT_FOUND);
// Accessing the 0th index works fine
//In case of a full list, it throws "Persistant Entity must not be null !" exception
return ResponseEntity.ok(resourceAssembler.toResource(employmentTypeList.get(0)));
}
catch (Exception e)
{
e.printStackTrace();
return new ResponseEntity<ApiResponse>(new AppResponse(false, REQUEST_NOT_PROCESSED),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I am trying to leverage maximum spring functionality with minimum coding support from my end. I do not want to write a ResourceAssembler for each and every persistent entity in my project.
Please suggest if anyone has any ideas.
To work with list of 'resources' you can use class Resources, for example, like this:
List<EmploymentType> types = employmentTypeRepository.findAll();
Resources<Resource<EmploymentType>> resources = Resources.wrap(types);
resources.add(/* you can add some links here */);
return ResponseEntity.ok(resources);
From Resources javadoc:
General helper to easily create a wrapper for a collection of entities.
I am checking out my project code and found below method in controller. On the net I found that controller is for receive request and provide response. Service Layer is for Business Logic and Dao layer is for data CRUD related operation.
In below method I can see business logic. Now I am not getting which code should be moved to service layer or below is fine.
I am reviewing code so I need to provide comments but I am confused.
#RequestMapping(value = "/admin/app", method = RequestMethod.POST)
public ModelAndView saveApp(
#ModelAttribute("application") #Validated Application application,
BindingResult result) {
ModelAndView model = new ModelAndView();
ApplicationFormValidator formValidation = new ApplicationFormValidator();
boolean messageFlag = false;
String operationalStatus = null;
formValidation.validate(application, result);
if (result.hasErrors()) {
model.addObject(APPLICATION, application);
model.setViewName(ADD_APP);
} else {
if(checkActive(application)){
status = FormBeanValidator.encodeStatus(application.getStatus());
application.setStatus(status);
// calling service layer and convert model into entity
messageFlag = applicationService.addApp(application);
if (messageFlag) {
Application applicationForm = new Application();
applicationForm.setSuccessMessage(PropertyHandler.getPropertyInstance().getPropertyValue(Constants.SUCCESS_MESSAGE));
model.addObject(APPLICATION, applicationForm);
model.setViewName(ADD_APP);
} else {
application.setErrorMessage(PropertyHandler.getPropertyInstance().getPropertyValue(Constants.ERROR_MESSAGE));
model.addObject(APPLICATION, application);
model.setViewName(ADD_APP);
}
}
else{
application.setErrorMessage(PropertyHandler.getPropertyInstance().getPropertyValue(Constants.OTHER));
model.addObject(APPLICATION, application);
model.setViewName(ADD_APP);
}
}
return model;
}
The code looks fine,but I would suggest some modifications:
1) Your checkActive(application) seems to check something about your business object(application),so move it to the service layer.You could merge your checkActive() method logic by moving the checkActive() method into service layer and calling it inside your applicationService.addApp(application) as a local method.
2) You have been setting the view name as same in both the if as well as else block.Try and move this code out of the if-else block as it becomes redundant.
3) It is a practice to send only the required data from the controller to the view.This is done by creating a DTO(Data Transfer Object) and making a Converter class which maps your business object fields to the DTO.You can look for example use case for DTO here
Everything is fine if you are checking for null or incorrect format validation inside your ApplicationFormValidator
I am building a simple web applications with 3 layers - DAO, Service, MVC. When in my Controller I want to delete menu group and it contains menus I am getting ConstraintViolationException.
Where should I handle this exception? In DAO, Service, or in Controller? Currently I am handling the exception in Controller.
My code below.
DAO method for deleting menu groups:
#Override
public void delete(E e){
if (e == null){
throw new DaoException("Entity can't be null.");
}
getCurrentSession().delete(e);
}
Service method for deleting menu groups:
#Override
#Transactional(readOnly = false)
public void delete(MenuGroupEntity menuGroupEntity) {
menuGroupDao.delete(menuGroupEntity);
}
Controller method for deleting menu groups in Controller:
#RequestMapping(value = "/{menuGroupId}/delete", method = RequestMethod.GET)
public ModelAndView delete(#PathVariable Long menuGroupId, RedirectAttributes redirectAttributes){
MenuGroupEntity menuGroupEntity = menuGroupService.find(menuGroupId);
if (menuGroupEntity != null){
try {
menuGroupService.delete(menuGroupEntity);
redirectAttributes.addFlashAttribute("flashMessage", "admin.menu-group-deleted");
redirectAttributes.addFlashAttribute("flashMessageType", "success");
} catch (Exception e){
redirectAttributes.addFlashAttribute("flashMessage", "admin.menu-group-could-not-be-deleted");
redirectAttributes.addFlashAttribute("flashMessageType", "danger");
}
}
return new ModelAndView("redirect:/admin/menu-group");
}
You should handle exceptions in service layer only, as part of design unless required. Think of the requirement where you need a same functionality deleteMenu for some other mapping too.
From any design point of view. Keep controller very specific to handling model attributes only serving the request mapping to business logic. Keep a method in service layer to take menuGroupId and throw exception from that service if parameter is thrown or DB error has occurred.
Refer more: Model-View-Controller, what every part really does?
I have started using JAX-RS to created a simple restful interface to my web application. Currently, it is only being used (read only) by one internal client which has access to all application data, and I am using http basic authentication for access. I would like to start using it as part of the view layer of my app, and certain operations will only be allowed if a user is logged in via the web app. I am struggling to find a pattern that allows me to use both forms of authentication in an elegant way, without repeating a lot of code. Here is roughly what I have come up with:
First a util class for loading an application session, which is stored in the database.
public class RestUtil {
public static AppSession getAuthenticatedSession(HttpServletRequest request) {
AppSession session;
String remoteUser = request.getRemoteUser();
if (remoteUser != null) {
session = SessionRepository.loadSessionByRemoteUser(remoteUser);
} else {
session = SessionRepository.loadSessionById(request.getSession().getId());
}
return session;
}
}
Here's our resource, with one method that is only accessible to an authenticated user, or our http basic auth client:
#Path("/protected/resource")
public class ProtectedResource {
#GET
#Produces(MediaType.TEXT_JSON)
#Path("{userId}")
public String getProtectedResourceJson(#Context HttpServletRequest request, #PathParam("userId") Integer userId) {
// Return Charity List XML
AppSession session = RestUtil.getAuthenticatedSession(request);
if (session.canAccessUser(userId)) //get Json...
}
}
Here's the most basic view of the AppSession, for the purpose of this question:
public class AppSession {
User authenticatedUser;
String remoteUser;
public boolean canAccessUser(Integer userId) {
if (remoteUser != null) {
//this client has access to all users
return true;
} else if (authenticatedUser.getId().equals(userId)) {
//this is local client, calling the service from a view
//only has access to authenticatedUser
return true;
} else {
return false;
}
}
}
Furthermore, for services that do not require any sort of authentication, how do I prevent unauthorized third parties from just pointing at the url, and grabbing the data at their leisure?
You're getting to the point when it is worth looking into using aspect-oriented programming to split the security side of things from your business logic. If you're already using Spring to assemble the pieces of your app (which I recommend for complex servers) then it's just a matter of adding in Spring AOP to inject the security logic. Otherwise, use AspectJ directly. The actual logic to handle the multiple login modes will probably have to be custom, but at least you can keep it quarantined.
If using Spring, consider using Spring Security; that builds on top of Spring AOP and supplies you with much more of the solution.