Today i have a big discussion with one of our team member about validation of RESTful API input in Controller and Service layer and i feel it is a bad day for making bigger argument. So we have a spring boot microservice application with layered architecture
Controller --> Service --> Repository
The argument is having the validation at each layer or just having the validation at Controller layer, for example we have Controller with POST request and using JSR-380 validation for input request body
Controller :
#PostMapping(value = "/", consumes = {"application/json"}, produces = {"application/json"})
public ResponseEntity<Detail> createConfig(
#NotNull(message = "{error.message.config_detail}")
#RequestBody #Validated Config config) {
return new ResponseEntity<>(configService.create(config.getConfigId(), config.getTaskId()), HttpStatus.CREATED);
}
Config : Request Body
public class Config {
#ApiModelProperty(name = "config_id", example = "1", required = true)
#NotNull(message = "{error.message.config_id}")
private Long configId;
#ApiModelProperty(name = "task_id", example = "11", required = true)
#NotNull(message = "{error.message.task_id}")
#Min(value = 0, message = "{error.message.task_id}")
#Max(value = 9999, message = "{error.message.task_id}")
private Integer taskId;
// bunch of addition fields with validations
}
If the validation success then calling the Service method with some properties from Config
Service :
public Detail create(#Valid #NotNull Long configId, #NotNull Integer taskId) {
// some business logic to convert to entity and saving to database
return repository.save(entity));
}
So if we see the above code, same validation is done at Controller and Service, So i argued that there is no need of validating in Service layer, perform the validation at controller layer and if input is wrong then throw 400 or 500 to user. But another person in team also suggest having validation in each block for whatever is used in the block, so that individual piece of code is safe (focusing on the unit instead of the integration path).
I know i might be wrong in this case, but still not able to understand the validation at each layer,(null check i agree) so it is recommended approach having validation at each level
Controller --> validation call service
Service ---> validation and call business
Business ---> validation and call repository
Repository --> save
But what is the prefered way of validation ? according to me if Controller input is valid call Service and perform business logic and call Repository. correct me if i'm wrong so that i can follow the recommended pattern
You are right : the validation should be placed in controller, if possible. And it's make no sense in my opinion to validate the data more then 1 time.
See also DRY principle https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
It is situational. Some validation needs to be performed in the service layer. Say, you will need to change a state of an object, but only if certain conditions are met etc.
A part from that, as a rule of thumb i follow these guidelines:
If your services are publicly exposed to other services via RMI, then service layer validation is mandatory, if not go with controller level validation.
Unless you are using the services and / or repositories as libraries in other projects, too, it does not make sense to do the validation multiple times.
If the controllers are the only classes accessing the additional layers there is no need for additional validation as you and your controller are the only one to access the Spring beans. You and the controller are in control of accessing the services with valid parameters.
Doing several validations has the disadvantage of writing more complex services - including unit tests.
It is slower.
Changing the validation will take longer as multiple layers have to be changed - including unit tests.
The validations on several layers can and will differ in a matter of time.
The preferred way is to do the validation once in (or even before) the controller and writing integration tests to ensure its functionality.
Related
I'm starting to play around with Spring Boot to learn how it functions for a MVC web application.
At a high-level the goal is to have a controller that will handle GET requests by issuing a request to an external gRPC server which will return a Order protobuf message. The Order data will be added to the Model and served via a template with Thymeleaf.
I am new to the Spring framework as a whole so the approach is likely incorrect but what I was doing is:
#Controller
public class OrderController {
#GetMapping("/order")
public String getOrder(#RequestParam(name = "order_number") String orderNumber, Model model) {
// Code for getting Order proto message from external server here
model.addAttribute("name", Order.getDate());
model.addAttribute("total", Order.getTotal());
model.addAttribute("number", Order.getNumber());
....
return "order";
}
}
However, this seems pretty tedious (especially for larger messages). Is there something I am missing that would allow me to add these fields en-masse?
Sorry, I cannot comment on question (SO restriction), hence asking here - cannot you just put your order object in model (rather than individual fields) and access it's fields/methods in view (Thymeleaf)?
In controller
model.addAttribute("order", order);
in view, I am not sure as I have not used Thymeleaf but should be simillar to
<span th:text="${order.getName()}" />
I have looked at a few tutorials, and am not quite sure how to proceed with writing a test case for my controller method using JUnit 5. I have read up on TestRestTemplate and Mock functions, but still feel at a loss as to how to begin. I am using a MySQL database, the application contains two classes Product and Feedback, it has a OneToMany relationship, with the owning entity being Feedback.
How do I write a test for the method below?
#PostMapping("/view/{id}")
public ModelAndView addReview(#PathVariable("id") int id, #RequestParam("review") String review, HttpServletRequest request) {
Product product = dao.findById(id).get();
Feedback feedback = new Feedback(review);
product.getFeedbacks().add(feedback);
feedback.setProduct(product);
dao.save(product);
List<Feedback> fs = product.getFeedbacks();
Collections.sort(fs, new FeedbackComparator());
HttpSession session = request.getSession();
session.setAttribute("fs", fs);
return new ModelAndView("/view").addObject("product", product);
}
There are multiple ways to write a test for your Spring MVC controller endpoints:
Use #WebMvcTest and MockMvc to test only your web-layer in isolation.
For such tests, you usually mock all collaborators (in your case your dao) of your controller. Using MockMvc you can then fire requests against a mocked Servlet environment that Spring Boot creates for you. This way you can ensure e.g. your controller endpoints are properly protected (by Spring Security), your path variables and query parameters are mapped as expected, you're returning the correct HTTP response and headers, your Model contains the default attributes, etc.
You can find further information on how to write tests with MockMvc as part of this guide.
Use #SpringBootTest to populate your whole application context, start the real Servlet container (e.g. Tomcat) and invoke your endpoints with the TestRestTemplate or the WebTestClient over HTTP.
This setup requires all your external infrastructure components (database, messaging queues, external systems, etc.) to be available during test execution. This usually takes quite some time and such tests are not as fast as the first variant. Testcontainers can help you a lot here. This way you can ensure the whole use case is working as expected and write integration tests for your application.
So as a general recommendation you should have both kinds of tests and at least ensure your important happy-paths are working as expected with an integration test. For more low-level checks for your controller, the #WebMvcTest annotation comes quite handy.
I have a Spring Boot application which uses Spring Security to Authenticate and Authorise requests using a JWT. However, some requests should only be able to be executed by a particular user. For example:
GET /users/{id}/orders should only return the list of orders if {id} is the current user or the current user is ADMIN
PUT /orders/{id} should only edit the order if the its payer is the current user
PUT /representation-requests/{id}/accept should only work if the current user is the target of the representation request
Because of the usage of JWTs, the way I get the current user's ID is by
String userId = ((DecodedJWT) SecurityContextHolder.getContext().getAuthentication().getDetails()).getSubject();
I've implemented this in the various methods of the services responsible for handling each API call. My question is if there is a more general way to do this using Spring Boot and Spring Security? Or is this not a standard use case?
I have looked at #PreAuthorize annotation in controllers, but it does not suite my needs as the URL parameters are not enough to check the nested entities. #PostAuthorize in controllers seems closer, but because of the JWT I couldn't get it to work (and it also seems a bit clunky to do long code in annotations, not sure it is better than doing it in the service itself). I appreciate a pointer in the right direction.
You'll have to play around with it a bit (I can't give you 100% specifics for JWT), but you basically could use SpEL to achieve what you want. (I'm unsure why you think #PostAuthorize could be a better fit, though)
Either like so, for simple checks (# denotes a method parameter)
#PreAuthorize("principal?.id == #id")
public List<Order> ordersForUsers(Integer id) {
// do someting
}
Or like so, delegating to a custom bean (# denotes a bean name, hash the method param, 'admin' the role you want, or any other parameter, really)
#PreAuthorize("#yourBean.hasPermission(principal, #id, 'admin')")
public List<Order> ordersForUsers(Integer id) {
// do someting
}
I want that all my rest services has as an input parameter HttpServletRequest httpRequest that I need later for some loggin purposes. This parameter sometimes is forgotten to be added and some methods are not logged. As are all rest services, and I am using Spring, all of them has some very specific annotations. I was thinking on using checkstyles to force the parameter to be present.
A little more of explanation of want I want to achieve. I am developing some rest servicies, and I am interested on logging some header that are sent to the rest services with some extra information. For this purpose, I have added HttpServletRequest request to each rest services as follows:
#GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public Status get(HttpServletRequest request, #PathVariable("id") Integer id) {
....
}
This paremeter is correctly retrieved and I can read the headers correctly (everything automated using AspectJ). My problem now is that is for a new rest service, I forgot to add the parameter, no logs will be shown. As the parameter is optional (you can or cannot add to the rest service without any error) and all logging is automated by AspectJ, is possible that I can forget it for future rest services and no notice the miss until late.
The scope is to ensure that always is present in all my rest services. My first thought was using checkstyle as I am already using for other different purposes.
Is it possible using checkstyle or any similar tool to force that a parameter is present on any method that has an annotation? If not, there is any other different way to achive my objective?
I have a model with jsr 303 annotations and service
public class User {
#NotNull
#Size(min = 2)
private String username;
#NotNull
private String email;
...
}
#Component
public class UserServiceImpl extends UserService {
public void createNewUser(#Valid User user) {
...
}
}
Is there any build-in way to raise validation exceptions (if any) whenever I call method on this service , like in Controller layer?
...
userService->createNewUser(user) <- Here I want validation exceptions to be thrown
...
P.S: I can do it with spring AOP, but I wonder if there is build in way.
The problem is in Spring the validation is integrated with your controller, so that validation messages get propagated back to the view, and you see something like for example: invalid email next to an email text box. The other place validation is used is in persistence, when you have constraints for when you are going to commit, update and delete. You cannot do these actions if the corresponding validation constraint is violated.
So it's clear a controller will show the error in your view, and a DAO won't commit to your database when validation fails. Now what do you expect to happen in the service layer? I can only think of that you would want to throw an exception if you call a method with the incorrect arguments. I guess you could make a case like you have for using aspect orientated programming to throw exceptions when this happens as you already know.
But I don't think anyone finds it too useful to have parameter validation at this level because you can't automatically display it back on the view like you can when you have validation in your request parameters in a controller. It would be better to find these violations in the controller so that the user can see these back in the view.
If you do choose to go down the aspect path then you could catch these exceptions and display a certain page with ControllerAdvice, so in a way you could integrate exceptions there back to this view, although it's never really occurred to me to do this, but you might find this helpful, it's just you might be in a service within a service within a service and the parameters might not mean much to the user who made some initial request.
But in general it would be the simplest to do this validation in the controller if you can.