3

My service calls a downstream service, which returns a ResponseEntity<MyBodyClass> response. My service needs to react differently based on different properties of response

For example if response.getStatusCode() == HttpStatus.BAD_REQUEST and response.getBody().code == 1, the service should do some action "A", but if for the same status code the response.getBody().id == 1, then it should do "B". Another example is if response.getStatusCode() == HttpStatus.OK and response.getBody() != null, service should do "C".

I know this can be done with a bunch of if statements, but is there a better design pattern?

onepiece
  • 3,279
  • 8
  • 44
  • 63
  • You can use the strategy pattern, creating a different action class for each result. https://www.geeksforgeeks.org/strategy-pattern-set-1/ – Cλstor Mar 10 '20 at 07:42

2 Answers2

5

Implement the rules as a chain of responsibility :

ruleFoo -> ruleBar -> ruleFooBar -> ....

The chain has to be ordered according to the rule precedence and each element of the chain (the rule here) is processed to detect a match. As soon as a rule matches the rule performs the suitable action and the chain process is stopped.

Here a very simple implementation that takes advantage of java 8 stream (that is a little different of the original GOF pattern but I Like it because that is lighter to implement).

public interface ResponseRule {        
    boolean match(Response response);                  
    void performAction();                          
}

public class RuleFoo implements ReponseRule {

    public boolean match(Response response){
       return response.getStatusCode() == HttpStatus.BAD_REQUEST && response.getBody().code == 1;
    }

    public void performAction(){
       // do the action
    }
}

public class RuleBar implements AbstractReponseRule {

    public boolean match(Response response){
       return response.getStatusCode() == HttpStatus.BAD_REQUEST && response.getBody() != null;
    }

    public void performAction(){
       // do the action
    }
}

And how use them :

Response response = ...;
List<ResponseRule> rules = new ArrayList<>();
rules.add(new RuleFoo(), new RuleBar());

rules.stream()
     .filter(rule -> rule.match(response))
     .findFirst()
     .ifPresent(ResponseRule::performAction);

Note that if rules execution could be accumulated, that would require a small change :

rules.stream()
     .filter(rule -> rule.match(response))
     .forEachOrdered(ResponseRule::performAction);
davidxxx
  • 125,838
  • 23
  • 214
  • 215
1

The usual technique is to use a "handler" class. You first need an interface:

interface ResponseHandler {
    // The order in which handlers are called
    int getPriority();
    // return true of the response is handled by this handler
    boolean handle(ResponseEntity<MyBodyClass> response);
}

Then create some implementations:

class BadRequestHandler implements ResponseHandler {
    public int getPriority() {
        return 1;
    }
    public boolean handle(ResponseEntity<MyBodyClass> response) {
        if (response.getStatusCode() == HttpStatus.BAD_REQUEST 
            && response.getBody().code == 1) {
            // do stuff to handle the request
            return true;
        }
        return false;
    }
}

Then you need a List of them and you loop over the list trying each handler in turn until one returns true.

List<ResponseHandler> responseHandlers;
// add response handler implementations to the list and sort it.
for (int i = 0; i < responseHandlers.size() 
        && !responseHandlers.get(i).handle(response); i++);

Before using the list you should sort it on getPriority. The easiest way to do that is to implement Comparable in the object as well. See Sort ArrayList of custom Objects by property.

This technique will not be as efficient as just writing a lot of if statements (since you can probably factor the conditions to remove common sub-conditions) but it does offer the flexibility to plug-in new handlers more easily and the code may be more readable.

rghome
  • 8,529
  • 8
  • 43
  • 62