How to model result?
Tags: java, cleancode, codequality, programming
Published: 2021-11-21 11:42:22

### Introduction
Method call often returns some result. For example let's assume we have method which creates new object: `Payment`. Now imagine `Payment` creation fails because passed `amount` is negative. In this case our method can just throw `IllegalArgumentException`. So far we have two possible method results:
* returned `Payment` instance
* thrown `IllegalArgumentException`.

It was a simple example, I will complicate it a little. Let's assume we already have `Payment` instance, and it is a specific payment type - `CardPayment`. New `CardPayment` instance represents only intention to collect money. `CardPayment` provides `execute` method which calls external service to realize cash flow. What are possible results? Same as before we can get some object - information about successful payment. Method could also throw an `Exception` if external service fails or it is not accessible (timeout for example). But `CardPayment` can fail also for many business reasons: client has no fund on the card, payment exceeded single transaction amount limit, payment exceeded daily transactions amounts sum limit or card has been blocked. These results are regular and common situations, so they shouldn't be model by throwing an `Exception`. Better choice is to design the `execute()` method interface which covers all described cases. How to model many possible business results?

Disclaimer: I consider only method synchronous result. Asynchronous solutions like event publish/subscribe is
out of the scope of this post.


### Problem
Let's summarize all possible results of described above `CardPayment#execute` method:
* success execution - payment made correctly, result contains external system success payment id,
* client has no fund on the card - result contains datetime field named `nextTryAfter`, all future card payments before this date will be rejected by external service
* payment exceeded single transaction maximum amount limit - result contains field `singleTransactionLimit` which is value of exceeded limit value,
* payment exceeded daily transactions amounts sum limit - result contains field `dailySumLimit` which is value of exceeded limit value,
* card has been blocked - result contains external system blocked card id.

Our goal is to model all possible business results of the `execute` method.


### Single class for all cases
In the simplest solution all above results can be designed with single class, for example:

```java
public class Example1 {

    record CardPaymentResult(String resultCode,
                             String successPaymentId,
                             LocalDateTime nextTryAfter,
                             BigInteger singleTransactionLimit,
                             BigInteger dailySumLimit,
                             String blockedCardId) {}

    public void example1() {
        // success execution - payment made correctly
        new CardPaymentResult("PAYMENT_OK", "vanoh5ailuChay6p", null, null, null, null);
        // client has no fund on the card,
        new CardPaymentResult("CLIENT_HAS_NO_FUND", null, LocalDateTime.of(2021, 11, 30, 10, 0, 0), null, null, null);
        // payment exceeded single transaction maximum amount limit,
        new CardPaymentResult("SINGLE_TRANSACTION_LIMIT_EXCEEDED", null, null, BigInteger.valueOf(20000), null, null);
        // payment exceeded daily transactions amounts sum limit,
        new CardPaymentResult("DAILY_TRANSACTIONS_LIMIT_EXCEEDED", null, null,null, BigInteger.valueOf(50000), null);
        // card has been blocked.
        new CardPaymentResult("CARD_BLOCKED", null, null,null, null, "eoZi5chu");
    }
}
```

In the above listing I use `record` Java keyword introduced in Java 14. Thanks to it, the Java compiler auto generates getter methods, `toString()`, `hashcode()` and `equals()` methods, so you don't have to write that boilerplate code yourself. Java `record` is immutable, no setter methods are generated.

You can easily find disadvantages of this solution:
* `CardPaymentResult` has many fields and every `resultCode` can increase theirs number.
* It is hard to predict which field are nulls. `CardPaymentResult` contains many nullable fields which presence depends on `resultCode`. It is easy to make mistake and get famous `NullPointerException`. Moreover, when you meet `CardPaymentResult` reference in code you can't simply predict what `resultCodes` are possible in given context and which fields can be nulls. For example, you must discover context like "scheduling next payment after failure" to know that you are dealing with `CLIENT_HAS_NO_FUND`, `SINGLE_TRANSACTION_LIMIT_EXCEEDED` or `DAILY_TRANSACTIONS_LIMIT_EXCEEDED` because for other resultCodes there is no sense to retry with new payment (`PAYMENT_OK` - we already succeed, we don't need to retry, `CARD_BLOCKED` - we are sure all future retries will fail).
It is also possible you pass by mistake instance of `CardPaymentResult` with `resultCode` which should never be present in given context.
* In every case when you need to handle one `resultCode` in unique way you need to add `if` condition to check if you
deal with this unique `resultCode`.

We definitely can design it better.


### Class per case
Second solution is to create dedicated class for every result case.

```java
public class Example2 {

    interface Result {}
    interface Results {
        record PaymentOk(String resultCode, String successPaymentId) implements Result {}
        record ClientHasNoFund(String resultCode, LocalDateTime nextTryAfter) implements Result {}
        record SingleTransactionLimitExceeded(String resultCode, BigInteger singleTransactionLimit) implements Result {}
        record DailyTransactionsLimitExceeded(String resultCode, BigInteger dailySumLimit) implements Result {}
        record CardBlocked(String resultCode, String blockedCardId) implements Result {}
    }

    public void example2() {
        // success execution - payment made correctly
        new Results.PaymentOk("PAYMENT_OK", "vanoh5ailuChay6p");
        // client has no fund on the card,
        new Results.ClientHasNoFund("CLIENT_HAS_NO_FUND", LocalDateTime.of(2021, 11, 30, 10, 0, 0));
        // payment exceeded single transaction maximum amount limit,
        new Results.SingleTransactionLimitExceeded("SINGLE_TRANSACTION_LIMIT_EXCEEDED", BigInteger.valueOf(20000));
        // payment exceeded daily transactions amounts sum limit,
        new Results.DailyTransactionsLimitExceeded("DAILY_TRANSACTIONS_LIMIT_EXCEEDED", BigInteger.valueOf(50000));
        // card has been blocked.
        new Results.CardBlocked("CARD_BLOCKED", "eoZi5chu");
    }
}
```

Pay attention that every possible result implements `Result` interface. Moreover, all `Result` descendant are grouped in `Results` interface. Thanks to it, if you will start typing `Example2.Results.` IDE will prompt with all possible Results.

Comparing to the previous solution:
* There are five classes with small number of fields instead of one big class with many fields. If new `resultCode` appears, instead of modifying one big class a new small class will be defined. It is compatible with principle
"open for extension, closed for modification".
* You are not dealing with null fields. In every case all fields are not-null. Moreover, you can use specific class
instead of general `Result` type to be sure that type with proper `resultCode` is used in given context.
* You still will need to use `if` condition to handle specific `resultCode` in unique way if you deal with `Result` type. But now it is possible that you will deal with specific type like `PaymentOk`, it won't be always general `Result` type. Moreover, Java 17 introduces pattern matching for `switch` condition, which improves code readability, for example:
```java
public static class PatternMatchingExample {

    public Message messageFor(Result result) {
        return switch (result) {
            case Results.PaymentOk v                      -> paymentOkMessage(v);
            case Results.ClientHasNoFund v                -> clientHasNoFund(v);
            case Results.SingleTransactionLimitExceeded v -> singleTransactionLimitExceeded(v);
            case Results.DailyTransactionsLimitExceeded v -> dailyTransactionsLimitExceeded(v);
            case Results.CardBlocked v                    -> cardBlocked(v);
            default                                       -> defaultMessage();
        };
    }
}
```

Before Java 17 you probably would use sequence of `instanceof` `if` conditions:
```java
public class InstanceOfExample {
    public Message messageFor(Result result) {
        if(result instanceof Results.PaymentOk) {
            return paymentOkMessage((Results.PaymentOk) result);
        }
        else if(result instanceof Results.ClientHasNoFund) {
            return clientHasNoFund((Results.ClientHasNoFund) result);
        }
        else if(result instanceof Results.SingleTransactionLimitExceeded) {
            return singleTransactionLimitExceeded((Results.SingleTransactionLimitExceeded) result);
        }
        else if(result instanceof Results.DailyTransactionsLimitExceeded) {
            return dailyTransactionsLimitExceeded((Results.DailyTransactionsLimitExceeded) result);
        }
        else if(result instanceof Results.CardBlocked) {
            return cardBlocked((Results.CardBlocked) result);
        }
        else {
            return defaultMessage();
        }
    }
}
```

Little note: some programmers say that using `instanceof` keyword always breaks polymorphism concept. I disagree with this opinion. According to [wikipedia](https://stepniewski.tech/L/3v6y8iDA):

> In programming languages and type theory, polymorphism is the provision of a single interface to entities of different types[1] or the use of a single symbol to represent multiple different types.[2]The concept is borrowed from a principle in biology where an organism or species can have many different forms or stages.[3]

In this case you deal with classes which represent results, not a behaviors. Their goal is to carry data, not perform actions. They are not have any id, they just are simple data structures. Our classes are not entities, but value objects.

### Other options...
Above solution is good for case when action has many results which contains different data (`successPaymentId` or `nextTryAfter` or `singleTransactionLimit` or `dailySumLimit` or `blockedCardId`). But this solution not always fit best. If your component returns many results but their data are not needed, you can use enum type instead of a collection
of classes, for example:

```java
public class Example3 {

    public enum Result {
        PAYMENT_OK, CLIENT_HAS_NO_FUND, SINGLE_TRANSACTION_LIMIT_EXCEEDED, DAILY_TRANSACTIONS_LIMIT_EXCEEDED, CARD_BLOCKED
    }

    public class PatternMatchingExample {

        public Message messageFor(Result result) {
            return switch (result) {
                case PAYMENT_OK                        -> paymentOkMessage();
                case CLIENT_HAS_NO_FUND                -> clientHasNoFund();
                case SINGLE_TRANSACTION_LIMIT_EXCEEDED -> singleTransactionLimitExceeded();
                case DAILY_TRANSACTIONS_LIMIT_EXCEEDED -> dailyTransactionsLimitExceeded();
                case CARD_BLOCKED                      -> cardBlocked();
                default                                -> defaultMessage();
            };
        }
    }
}
```

If your component returns only two different results you can use abstraction like `Either` class from `vavr.io` (`javaslang`) library:

```java
import io.vavr.control.Either;

public class Example4 {

    record PaymentOk(String resultCode, String successPaymentId) implements Example2.Result {}
    record PaymentFail(String resultCode, String failReason) implements Example2.Result {}

    public Either<PaymentFail, PaymentOk> example4() {
        //omitted code
        return Either.right(new PaymentOk("PAYMENT_OK", "vanoh5ailuChay6p"));
    }
}
```

Finally, if you have component with many results which have similar structure, the solution with single class for all cases (described at the beginning of this post) may fit best. You always can refactor it later when complexity will increase.

There just not exists the best solution for every case, everything depends on problem details. In this post I wanted to describe few approaches how you can model many business results. It is common problem and I hope you will feel a little more comfortable with it after this post.

All Java examples from this post you will find at my [github](https://stepniewski.tech/L/2LSNSpKr7).

---

Originally published at [https://stepniewski.tech](https://stepniewski.tech/blog/post/11-how-to-model-many-possible-business-results/).