Spring data rest and business rules validation
Asked Answered
D

3

8

I want to seek a best practice for applying business rules when working with spring data rest.

Lets consider following scenario:

  • I have a Customer and Order in @OneToMany relationship.
  • I have a business rule saying that Customer needs to have verified flag set to be able to make orders

So I need to make sure that whenever someone POSTs to /orders the Customer making the call is verified.

I'm considering using beforeSave Validators autowiring other service/repositories into the Validator and check whatever needs to be checked.

Is there better way of achieving the same?

Delamare answered 15/2, 2018 at 9:46 Comment(3)
Can you precise in which way you want something better? What is the problem with the current approach?Jazzman
There is no real problem, but I thought that Validators were meant to validate single entity without external dependencies. I might be wrong. I think this is pretty common use case and was wondering how spring-data-rest guys intended to do the cross entity validation.Delamare
I would prefer an approach where you can fail as fast as possible, if the user is not verified. This can be a security filter in the filter chain. You can leverage Spring security for that, or write your own custom solution.Costanzia
T
5

There are several ways to solve this. As far as my knowledge goes:

  1. Usage of spring security annotations like @PreAuthorize. The intended use of these annotations is however for security purposes and you are mentioning business rules. I would use these for user authorization rules Spring data rest security chapter

  2. The use of validators as you mentioned yourself. Spring data rest Validators

  3. Use spring data rest events Spring data rest events. You can create global event handlers, however here you need to determine the entity type. I would go with Annotated event handlers to perform business logic Spring data rest annotated event handler

Thormora answered 2/3, 2018 at 12:49 Comment(1)
Ok, the #2 still looks best to me :) Lets see if anyone else comes with other idea. Otherwise I accept the answer. ThanksDelamare
D
1

So just for the sake of world piece I'm adding my solution. Went with #2.

The documentation is pretty clear on how to proceed so just sharing few tips which may save you time.

  1. You need to assign validators manually, auto-discovery doesn't work
  2. Manually spelling event type is error prone, some helper Enum could be handy.

Like:

/** 
 * "beforeSave" gets called on PATCH/PUT methods
 * "beforeCreate" on POST
 * "beforeDelete" on DELETE
 */
enum Event {
    ON_CREATE("beforeCreate"), ON_UPDATE("beforeSave"), 
 ON_DELETE("beforeDelete");

    private String name;

    Event(String name) {
        this.name = name;
    }
} 

...

private static void addValidatorForEvents(ValidatingRepositoryEventListener eventListener, Validator validator, Event... events) {
    Arrays.asList(events).forEach(event -> eventListener.addValidator(event.name, validator));
}
Delamare answered 7/3, 2018 at 9:58 Comment(0)
L
0

One out of the box solution you can use to solve your Business rules related problems, is using Spring AOP. What you can do, is define an Annotation (say @X) and place that annotation on top of your POST call.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface X{}

Next what you need to do is, create an aspect, and run your custom validation logic in this aspect as follows,

@Aspect
@Component
public class CustomAspect {

   //You can autowire beans here

    @Around("@annotation(qualified name of X)")
    public Object customMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        flag = customLogic();
        if (flag){
           return joinPoint.proceed(); //return if logic passes, otherwise
        }else{
           throw new BusinessRuleException("Business rule violated");
        }
    }

    private boolean customLogic(){
     //your custom logic goes here
    }
}

And finally apply this annotation on top of any method in controller layer like:

@X
@RequestMapping(method = RequestMethod.POST, value = "do-something")
public void callSomething(HttpServletRequest request) throws Exception {
   // your business logic goes here
}

Only thing to note above is that you need to pass HttpServletRequest request explicitly to your controller method in order to AOP aspect get the same context for manipulation of user session related attributes like session_id, etc.

Above solution will help you add business rules on top of your Business Logic and help you with all kinds of pre validations you want to build in your web application. It is a pretty handy application of Spring AOP. Do reach out in case of any

Locarno answered 3/3, 2018 at 10:58 Comment(4)
This defeats the purpose of using spring-data-rest because it generates the endpoint for you on the fly, you don't have access to them.Delamare
How is it adding an end point, instead it is just like spring security's preauthorize annotations. Only upside here is that you get to customize it as you choose.Locarno
I don't fully follow, but with spring data rest you typically don't create Controllers with @RequestMapping methods at all. I suggest yo check the project.Delamare
Oh I got it. My bad. Thanks for clarifying :)Locarno

© 2022 - 2024 — McMap. All rights reserved.