Ashutosh Vaidya
Ashutosh Vaidya

Reputation: 91

Java Multistep Validation Method - Refactoring

I have a method which does multiple validations which are dependent on earlier one. This is purely a REST Service with no form/frontend. e.g.

public Json processPayment(User user, Amount amount, CardData cardData) {
    Json result = new Json();

    Json userResult = validateUser(user);
    if (userResult.isNotValid())
        result.put("errorCode", userResult.get("errorCode");
        result.put("message", userResult.get("message");
        return result;
    }

    Merchant merchant = getMerchant(user);
    Json merchantResult = validateMerchant(user);
    if (merchantResult.isNotValid())
        result.put("errorCode", merchantResult.get("errorCode");
        result.put("message", merchantResult.get("message");
        return result;
    }

    Json limitsResult = validateLimits(user, merchant, amount);
    if (limitsResult.isNotValid())
        result.put("errorCode", limitsResult.get("errorCode");
        result.put("message", limitsResult.get("message");
        return result;
    }

    // Like above there are few more steps.
    .
    .
    .

    // All validations are fine process transaction.
    Json transactionResult = processTransaction(user, merchant, amount, cardData);
    if (transactionResult.isNotValid())
        result.put("errorCode", transactionResult.get("errorCode");
        result.put("message", transactionResult.get("message");
    } else {
        result.put("message", "Transaction Successful");
        result.put("referenceNumber, transactionResult.get("rrn");
    }

    return result;
}

In each step, if the results are invalid then it should return immediately with the error message otherwise continue to next step.

Due to multiple steps, this method has become too big and almost impossible to do unit testing.

I want to break this method into smaller ones. I have already moved all the business logic of each step into separate methods but still the flow remains in this big method.

Sonarlint CC is 47 which is a big worry.

Please suggest what would be the right approach to handle this.

Thank you.

Upvotes: 2

Views: 1084

Answers (2)

Obenland
Obenland

Reputation: 866

Here is a little example which could be a solution for you.

The main idea is each validation step shares one common context. This context holds every information of your validation process.

Next you have a queue of validators. Each represents one validation step. A validator changes the context (like adding the merchant object), calls your validation methods and changes the result of the context if necessary.

The validation process itself just iterates over the queue looking for a failing validator.

Just run this code. Maybe it helps:

import java.util.*;

interface PaymentValidatorInterface {
    public boolean validate(PaymentValidationContext context);
}

class PaymentValidationContext {
    String result = "";
    String user;
    int cardData;
    String merchant;

    public PaymentValidationContext(String user, int cardData) {
        this.user = user;
        this.cardData = cardData;
    }
}

class PaymentValidator {
    public static boolean validateUser(PaymentValidationContext context) {
        if (context.user == null) {
            context.result += "User is wrong\n";
            return false;
        }
        return true;
    }

    public static boolean validateMerchant(PaymentValidationContext context) {
        context.merchant = context.user + "#" + context.cardData;
        if (context.merchant.length() <= 3) {
            context.result += "Marchant is wrong\n";
            return false;
        }
        return true;
    }

    public static boolean finishValidation(PaymentValidationContext context) {
        context.result += "Everything is fine.\n";
        return true;
    }
}

public class Processor {
    private final static Queue<PaymentValidatorInterface> validators = new LinkedList<>();
    static {
        validators.add(PaymentValidator::validateUser);
        validators.add(PaymentValidator::validateMerchant);
        validators.add(PaymentValidator::finishValidation);
    }

    public String processPayment(String user, int cardData) {
        PaymentValidationContext context = new PaymentValidationContext(user, cardData);
        validators.stream().anyMatch(validator -> !validator.validate(context));
        return context.result;
    }

    // For testing -------
    public static void main(String[] args) {
        Processor p = new Processor();
        System.out.print(p.processPayment("Foobar", 1337));    // ok
        System.out.print(p.processPayment(null, 1337));        // fails
        System.out.print(p.processPayment("", 1));             // fails
    }
}

Upvotes: 3

Ravi Chandra
Ravi Chandra

Reputation: 687

You can write doValidation() function like the following.

private doValidation(Json validationResult, Json result) {
    if (validationResult.isNotValid())
        result.put("errorCode", validationResult.get("errorCode");
        result.put("message", validationResult.get("message");
        return false;//validation failed
    }
    return true;//validation passed
}

and Call this method from processPayment() method.

public Json processPayment(User user, Amount amount, CardData cardData) {
    Json result = new Json();

    if( !doAllValidations(user,amount,cardData, result) )
       return result;

    // All validations are fine process transaction.
    Json transactionResult = processTransaction(user, merchant, amount, cardData);
    if (transactionResult.isNotValid())
        result.put("errorCode", transactionResult.get("errorCode");
        result.put("message", transactionResult.get("message");
    } else {
        result.put("message", "Transaction Successful");
        result.put("referenceNumber, transactionResult.get("rrn");
    }

    return result;
}

Finally you can move all validations to some other method if you want.

public bool doAllValidations(User user, Amount amount, CardData cardData, result) {
        Json userResult = validateUser(user);
        if (!doValidation(userResult, result))
          return result;


        Merchant merchant = getMerchant(user);
        Json merchantResult = validateMerchant(user);
        if (!doValidation(merchantResult, result))
          return result;

        Json limitsResult = validateLimits(user, merchant, amount);
        if (!doValidation(limitsResult, result))
          return result; 
        ....
}

Upvotes: 0

Related Questions