Reputation: 7685
Given multiple REST resources to gather order information.
/order/{id}
/order/{id}/positions
/order/{id}/invoice
/order/{id}/shipment
Within the a Srping Boot 2 application it's implemented across multiple controllers, e.g. OrderController
, InvoiceController
, etc.
Currently every controller uses the OrderRepository to ensure the the order with the given id
exists. Otherwise it throws an exception. It's always the same replicated code.
@RestController
public class OrderController {
// ...
@GetMapping("order/{id}")
public Order getCustomer(@PathVariable final Integer id) {
return this.orderRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("order not found"));
}
}
Does the framework provide a callback to write the order id
check just once?
I found the AntPathMatcher
but it seems not the right way, since it provides just an boolean interface.
Upvotes: 1
Views: 210
Reputation: 44685
This is usually a good case for bean validation. While there is already builtin support for many cases of validation (@Size
, @NotNull
, ...), you can also write your own custom constraints.
First of all, you need to define an annotation to validate your ID, for example:
@Documented
@Constraint(validatedBy = OrderIdValidator.class)
@Target({PARAMETER})
@Retention(RUNTIME)
public @interface ValidOrderId {
String message() default "Invalid order ID";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Since your order ID is always a parameter for your controller mappings, you could use ElementType.PARAMETER
to only allow the annotation for method parameters.
The next step is to add the @Constraint
annotation to it and point to a custom validator class (eg. OrderIdValidator
):
@Component
public class OrderIdValidator implements ConstraintValidator<ValidOrderId, Integer> {
private OrderRepository repository;
public OrderIdValidator(OrderRepository repository) {
this.repository = repository;
}
@Override
public boolean isValid(Integer id, ConstraintValidatorContext constraintValidatorContext) {
return repository.existsById(id);
}
}
By implementing the isValid
method, you can check whether or not the order exists. If it doesn't exist, an exception will be thrown and the message()
property of the @ValidOrderId
annotation will be used as a message.
The last step is to add the @Validated
annotation to all of your controllers, and to add the @ValidOrderId
annotation to all order ID parameters, for example:
@Validated // Add this
@RestController
public class OrderController {
@GetMapping("order/{id}")
public Order getCustomer(@PathVariable @ValidOrderId final Integer id) { // Add @ValidOrderId
// Do stuff
}
}
If you prefer to use a different response status for your validations, you could always add a class annotated with the @ControllerAdvice
annotation and use the following method:
@ExceptionHandler(ConstraintViolationException.class)
public void handleConstraints(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
Upvotes: 3