I´m beginner in REST API and Spring Boot. I have a doubt about how to handle the different responses a request can have. For example if I post data of a credit card
{
"number": "3434 3434 3434 3434",
"date_expiration": "09-19",
"identification_card": 23232323
}
then in #RestController
#PostMapping("/card")
public ResponseEntity<CreditCard> payCard(#Valid #RequestBody CreditCard creditCard){
CreditCard creC = cardService.registerCard(creditCard);
return new ResponseEntity<>(creC, HttpStatus.OK);
}
In this case I return an object of ResponseEntity.
What happens if the date_expiration is expired or the identification_card doesn't correspond to the client? They are logical validations that are resolved in the service and can trigger different responses. How should I handle them?
Here you are using same object as your Request body and Response body. That is not a standard practice.
You should have separate objects for Request/Response. In request object you should have only the information you need from the user. But in the response object you should have information you want to send in response and also validation information like Error Details which includes error code and error description which you can use to display validation error to the user.
Hope this helps.
Well, if date_expiration is expired or identification_card doesn't behave to the customer, this is a business failure.
I like to represent Business Errors with an HTTP 422 - Unprocessable Entity. See here
You can change the return object from ResponseEntity<CreditCard> to ResponseEntity<Object> if you want to return diferent objects in your controller, although I prefer to use a ExceptionHandler in a ControllerAdvice annotated method if the purpose is to return errors.
As I said, this situation is a business failure (the credit card is expired or doesn't behave to the current user).
Here's an example. Would be something like this:
CardService.java
#Service
public class CardService {
// ..
public CreditCard registerCard(CreditCard card) throws BusinessException {
if(cardDoesntBehaveToUser(card, currentUser()))) // you have to get the current user
throw new BusinessException("This card doesn't behave to the current user");
if(isExpired(card)) // you have to do this logic. this is just an example
throw new BusinessException("The card is expired");
return cardRepository.save(card);
}
}
CardController.java
#PostMapping("/card")
public ResponseEntity<Object> payCard(#Valid#RequestBody CreditCard creditCard) throws BusinessException {
CreditCard creC = cardService.registerCard(creditCard);
return ResponseEntity.ok(creC);
}
BusinessException.java
public class BusinessException extends Exception {
private BusinessError error;
public BusinessError(String reason) {
this.error = new BusinessError(reason, new Date());
}
// getters and setters..
}
BusinessError.java
public class BusinessError {
private Date timestamp
private String reason;
public BusinessError(String Reason, Date timestamp) {
this.timestamp = timestamp;
this.reason = reason;
}
// getters and setters..
}
MyExceptionHandler.java
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
// .. other handlers..
#ExceptionHandler({ BusinessException.class })
public ResponseEntity<Object> handleBusinessException(BusinessException ex) {
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(ex.getError());
}
}
If the credit card is expired, the JSON will be rendered as:
{
"timestamp": "2019-10-29T00:00:00+00:00",
"reason": "The card is expired"
}
Related
So I've been using Spring and Java for a while to build microservices. I am concerned by the way I am currently handling service layer results which uses "business exception"
Controller
#RestController
public class PurchaseController {
#Autowired
private PurchaseService purchaseService;
#PostMapping("/checkout")
public ResponseEntity<?> checkout(#RequestBody CheckoutRequest body) {
try {
SomeDTO dto = purchaseService.doCheckout(body);
return ResponseEntity.ok(dto);
}
catch (UnauthorizedException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
catch (CustomBusinessException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
}
Service
#Service
public class PurchaseService {
// ...
public DTO doCheckout(CheckoutRequest request) {
// this one calls another microservice
if (!isUserValid(request.userId)) {
// current handling of business rules violation (1)
throw new UnauthorizedException("User not valid");
}
if (request.total < 10) {
// current handling of business rules violation (2)
throw new CustomBusinessException("Minimum checkout at 20 dollars");
}
// ... do actual checkout
return new DTO(someDTOData);
}
}
I was comfortable at using this "pattern" because I do not need to "if" the business result in the controller level to return the appropriate HttpStatusCode, but since I've found some articles saying that exception is expensive specifically in Java, I doubt what I was doing is good for the long run.
Is there another correct way to gracefully handles the business result layer?
The problem with ResponseEntity in Spring is that they are typed with the result object you want to return when the endpoint is called successfully, so you can't return another body different from the happy path one, that in your case would be SameDTO. One way to address this issue is to use ? as the type of the response entity, as you have done but it is not the most recommended way.
So the best way to do this is precisely to use exceptions when there is a situation when you can't return the expected object and you have to return another object or status code, but instead of using a try-catch in the controller you should use an exception handler (Controller Advice) https://www.baeldung.com/exception-handling-for-rest-with-spring.
This controller advice would catch any exception thrown in your application and depending on the exception type it could return a different response class or status code without affecting the main controller. One example of how can be your controller advice would be:
#ControllerAdvice
public class ErrorHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleInternal(final RuntimeException ex) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ex.getMessage());
}
#ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<ResponseDto> identityClientException(UnauthorizedException e) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(e.getMessage());
}
#ExceptionHandler(CustomBusinessException.class)
public ResponseEntity<ResponseDto> identityClientException(CustomBusinessException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(e.getMessage());
}
And your controller woulb be much more clean without exception handling logic:
#RestController
public class PurchaseController {
#Autowired
private PurchaseService purchaseService;
#PostMapping("/checkout")
public ResponseEntity<SomeDTO> checkout(#RequestBody CheckoutRequest body){
SomeDTO dto = purchaseService.doCheckout(body);
return ResponseEntity.ok(dto);
}
}
I've been trying to figure out what the best practice is for form submission with spring and what the minimum boilerplate is to achieve that.
I think of the following as best practise traits
Validation enabled and form values preserved on validation failure
Disable form re-submission F5 (i.e. use redirects)
Prevent the model values to appear in the URL between redirects (model.clear())
So far I've come up with this.
#Controller
#RequestMapping("/")
public class MyModelController {
#ModelAttribute("myModel")
public MyModel myModel() {
return new MyModel();
}
#GetMapping
public String showPage() {
return "thepage";
}
#PostMapping
public String doAction(
#Valid #ModelAttribute("myModel") MyModel myModel,
BindingResult bindingResult,
Map<String, Object> model,
RedirectAttributes redirectAttrs) throws Exception {
model.clear();
if (bindingResult.hasErrors()) {
redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.myModel", bindingResult);
redirectAttrs.addFlashAttribute("myModel", myModel);
} else {
// service logic
}
return "redirect:/thepage";
}
}
Is there a way to do this with less boilerplate code or is this the least amount of code required to achieve this?
First, I wouldn't violate the Post/Redirect/Get (PRG) pattern, meaning I would only redirect if the form is posted successfully.
Second, I would get rid of the BindingResult style altogether. It is fine for simple cases, but once you need more complex notifications to reach the user from service/domain/business logic, things get hairy. Also, your services are not much reusable.
What I would do is pass the bound DTO directly to the service, which would validate the DTO and put a notification in case of errors/warning. This way you can combine business logic validation with JSR 303: Bean Validation.
For that, you can use the Notification Pattern in the service.
Following the Notification Pattern, you would need a generic notification wrapper:
public class Notification<T> {
private List<String> errors = new ArrayList<>();
private T model; // model for which the notifications apply
public Notification<T> pushError(String message) {
this.errors.add(message);
return this;
}
public boolean hasErrors() {
return !this.errors.isEmpty();
}
public void clearErrors() {
this.errors.clear();
}
public String getFirstError() {
if (!hasErrors()) {
return "";
}
return errors.get(0);
}
public List<String> getAllErrors() {
return this.errors;
}
public T getModel() {
return model;
}
public void setModel(T model) {
this.model = model;
}
}
Your service would be something like:
public Notification<MyModel> addMyModel(MyModelDTO myModelDTO){
Notification<MyModel> notification = new Notification();
//if(JSR 303 bean validation errors) -> notification.pushError(...); return notification;
//if(business logic violations) -> notification.pushError(...); return notification;
return notification;
}
And then your controller would be something like:
Notification<MyModel> addAction = service.addMyModel(myModelDTO);
if (addAction.hasErrors()) {
model.addAttribute("myModel", addAction.getModel());
model.addAttribute("notifications", addAction.getAllErrors());
return "myModelView"; // no redirect if errors
}
redirectAttrs.addFlashAttribute("success", "My Model was added successfully");
return "redirect:/thepage";
Although the hasErrors() check is still there, this solution is more extensible as your service can continue evolving with new business rules notifications.
Another approach which I will keep very short, is to throw a custom RuntimeException from your services, this custom RuntimeException can contain the necessary messages/models, and use #ControllerAdvice to catch this generic exception, extract the models and messages from the exception and put them in the model. This way, your controller does nothing but forward the bound DTO to service.
Based on the answer by #isah, if redirect happens only after successful validation the code can be simplified to this:
#Controller
#RequestMapping("/")
public class MyModelController {
#ModelAttribute("myModel")
public MyModel myModel() {
return new MyModel();
}
#GetMapping
public String showPage() {
return "thepage";
}
#PostMapping
public String doAction(
#Valid #ModelAttribute("myModel") MyModel myModel,
BindingResult bindingResult,
RedirectAttributes redirectAttrs) throws Exception {
if (bindingResult.hasErrors()) {
return "thepage";
}
// service logic
redirectAttrs.addFlashAttribute("success", "My Model was added successfully");
return "redirect:/thepage";
}
}
One possible way is to use Archetype for Web forms, Instead of creating simple project, you can choose to create project from existing archetype of web forms. It will provide you with sufficient broiler plate code. You can also make your own archetype.
Have a look at this link to get deeper insight into archetypes.
Link To Archetypes in Java Spring
I'm using Spring Boot with Data JPA.
I have the following code.
A User Class with name and an informative message.
class UserResponse{
private String name;
private String message;
}
User JPA Repository which finds userBy id;
interface UserRepository{
Optional<User> findUserById(String id);
}
User Service which invokes repo and set message if user not found
class UserService(){
UserResponse user = new UserResponse();
public UserResponse getUserById(String userId){
Optional<User> useroptional = userRepository.findById(userId);
if(userOptional.isPresent()){
user.setName(userOptional.get().getName());
}else{
user.setMessage("User Not Found");
}
}
UserController has to set proper HTTP status code as per the message.
class UserController(){
public ResponseEntity<UserResponse> getUserById(String id){
UserResponse user = userService.getUserById(id);
HttpStatus status = OK;
if(!StringUtils.isEmpty(user.getMessage())){
status = NOT_FOUND;
}
return new ResponseEntity<>(user,status);
}
}
The problems I have faced is inorder to set proper status code in controller layer I have to inspect user message,which i didn't like.
Is there anyway we can create a control flow for Success and Failure cases.
Say One Return type and flow for Success scenario and vice-versa.
I know Scala has this feature with Either keyword.
Is there any alternate in Java ?
Or any other approach I can use to handle this better...
One approach would be returning RepsonseEntity in service layer itself with proper status code but setting status code is controller's Responsibility is what I felt.
In case of failure you can throw custom Exception with proper message. Then you can catch it in #ControllerAdvice. I'll add an example in a moment.
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(MyCustomException.class)
public ResponseEntity<String> exception(MyCustomException e) {
return new ResponseEntity(e.getMessage(), HttpStatus.NotFound);
}
}
In one #ControllerAdvice one could have more methods listening for different Exceptions. Custom Exception can hold whatever you want - it's a normal class - so you can return ResponseEntity of whatever you want.
For example:
#Transactional(readOnly = true)
#GetMapping("/{id}")
public ResponseEntity<?> getUserById(#PathVariable("id") String userId) {
return userRepository.findById(userId)
.map(user -> ResponseEntity.ok().body(user))
.orElse(new ResponseEntity<>(/* new ErrorMessage */, HttpStatus.NOT_FOUND))
}
For 'not found' response you have to create an error message object and return it to client.
Use Case:
let's design a RESTful create operation using POST HTTP verb - creating tickets where creator (assigner) specifies a ticket assignee
we're creating a new "ticket" on following location: /companyId/userId/ticket
we're providing ticket body containing assigneeId:
{
"assigneeId": 10
}
we need to validate that assigneeId belongs to company in URL - companyId path variable
So far:
#RequestMapping(value="/{companyId}/{userId}/ticket", method=POST)
public void createTicket(#Valid #RequestBody Ticket newTicket, #PathVariable Long companyId, #PathVariable Long userId) {
...
}
we can easily specify a custom Validator (TicketValidator) (even with dependencies) and validate Ticket instance
we can't easily pass companyId to this validator though! We need to verify that ticket.assigneeId belongs to company with companyId.
Desired output:
ability to access path variables in custom Validators
Any ideas how do I achieve the desired output here?
If we assume that our custom validator knows desired property name, then we can do something like this:
Approach one:
1) We can move this getting path variables logic to some kind of a base validator:
public abstract class BaseValidator implements Validator {
#Override
public boolean supports(Class<?> clazz)
{
// supports logic
}
#Override
public void validate(Object target, Errors errors)
{
// some base validation logic or empty if there isn't any
}
protected String getPathVariable(String name) {
// Getting current request (Can be autowired - depends on your implementation)
HttpServletRequest req = HttpServletRequest((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (req != null) {
// getting variables map from current request
Map<String, String> variables = req.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
return variables.get(name);
}
return null;
}
}
2) Extend it with your TicketValidator implementation:
public class TicketValidator extends BaseValidator {
#Override
public void validate(Object target, Errors errors)
{
// Getting our companyId var
String companyId = getPathVariable("companyId");
...
// proceed with your validation logic. Note, that all path variables
// is `String`, so you're going to have to cast them (you can do
// this in `BaseValidator` though, by passing `Class` to which you
// want to cast it as a method param). You can also get `null` from
// `getPathVariable` method - you might want to handle it too somehow
}
}
Approach two:
I think it worth to mention that you can use #PreAuthorize annotation with SpEL to do this kind of validation (You can pass path variables and request body to it). You'll be getting HTTP 403 code though if validation woudnt pass, so I guess it's not exaclty what you want.
You could always do this:
#Controller
public class MyController {
#Autowired
private TicketValidator ticketValidator;
#RequestMapping(value="/{companyId}/{userId}/ticket", method=POST)
public void createTicket(#RequestBody Ticket newTicket,
#PathVariable Long companyId, #PathVariable Long userId) {
ticketValidator.validate(newTicket, companyId, userId);
// do whatever
}
}
Edit in response to the comment:
It doesn't make sense to validate Ticket independently of companyId when the validity of Ticket depends on companyId.
If you cannot use the solution above, consider grouping Ticket with companyId in a DTO, and changing the mapping like this:
#Controller
public class MyController {
#RequestMapping(value="/{userId}/ticket", method=POST)
public void createTicket(#Valid #RequestBody TicketDTO ticketDto,
#PathVariable Long userId) {
// do whatever
}
}
public class TicketDTO {
private Ticket ticket;
private Long companyId;
// setters & getters
}
I am building a REST service using spring boot. My controller is annotated with #RestController. For debugging purposes I want to intercept the ResponseEntity generated by each of the controller methods (if possible). Then I wish to construct a new ResponseEntity that is somewhat based on the one generated by the controller. Finally the new generated ResponseEntity will replace the one generated by the controller and be returned as part of the response.
I only want to be able to do this when debugging the application. Otherwise I want the standard response generated by the controller returned to the client.
For example I have the controller
#RestController
class SimpleController
#RequestMapping(method=RequestMethod.GET, value="/getname")
public NameObject categories()
{
return new NameObject("John Smith");
}
}
class NameObject{
private String name;
public NameObject(name){
this.name = name;
}
public String getName(){ return name; }
}
This will generate the response:
{"name" : "John Smith"}
But I would like to change the response to include status info of the actual response e.g:
{"result": {"name" : "John Smith"}, "status" : 200 }
Any pointers appreciated.
The way I would try to achieve such a functionality is first by creating an Interceptor. And example can be found here
Second, I would employ Spring profiles to ensure that interceptor is loaded only in profile that I needed it in. Detail here. It's not exaclty debugging, but might do the trick.
You can do this with spring AOP, something like:
#Aspect
#Component
public class ResponseEntityTamperer {
#Around("execution(* my.package.controller..*.*(..))")
public Object tamperWithResponseEntity(ProceedingJoinPoint joinPoint)
throws Throwable {
Object retVal = joinPoint.proceed();
boolean isDebug = java.lang.management.ManagementFactory.getRuntimeMXBean()
.getInputArguments().toString()
.contains("jdwp");
if(isDebug && retVal instanceof ReponseEntity) {
// tamper with the entity or create a new one
}
return retVal;
}
}
The "find out if we're in debug mode" code is from this answer.