Creating a lazily-evaluated builder-like API in Java 8 using the extended Step Builder pattern

Tomasz Czermiński

Introduction

The Builder design pattern is known and used widely. To begin with, it was created to provide a way to create new objects. Using it in such a way is a good choice. However, there is at least one problem, facing which we should not use this pattern. This problem is building an API for performing a complex business action in a composable manner. You may ask why would somebody use it in such a way? The answer is – to build a test framework. It can be tempting to model a business action in form of a builder, but I would like to suggest a new approach.   

Case study

Requirements:

  • Let us say that we need to add a new order for a specific product in our shop.
  • Again, let us say that it is necessary to handle such an order in a transactional way.
  • Finally, assume that there are many validation rules, which should be matched against our order before the actual ‘commit’ (in a sense of IO operation as it can be costly).

Here is a simple example of the approach I see more and more often:

try {
    new ProductOrderingService()
        .newOrder()
        .addProduct()
        .withDestinationCity()
        .withDestinationCity()
        .order();
} catch (final Exception e) {
    LOGGER.error(e);
}
class ProductOrderingService {

    ProductOrderingService newOrder() {
        System.out.println("Initializing new order...");
        return this;
    }

    ProductOrderingService addProduct(final String product) {
        System.out.println("Adding product: " + product);
        return this;
    }

    ProductOrderingService withDestinationCity(final String destinationCity) {
        System.out.println("Adding destination city: " + destinationCity);
        return this;
    }

    ProductOrderingService withDestinationPostCode(final String destinationPostCode) {
        System.out.println("Adding destination post code: " + destinationPostCode);
        return this;
    }
    
    void order() {
        System.out.println("Submitting the order...");
    }
}

Is anything wrong here? First of all, we do not gain anything apart from the method chaining. The methods are executed independently anyway.  And by the way, how can we use transactions here? Every particular step can fail at any moment causing an exception to be thrown. We would end up wrapping our code with try-catch block which is kind of ugly. What is even worse is that if steps must be executed in a particular order (step execution depends on the results from the previously executed steps) we cannot tell in which order we should chain the methods. The only way to use such an API is to read DOCS which is error prone and… unproductive.

Anyway, look at the alternative (the syntax resembles the SQL transaction syntax) [1]:

final OrderProductTransaction.Result result = new ProductOrderingTransactionalService()
        .begin(() ->
                OrderProductTransaction
                        .builder()
                        .addProduct("Product no. 1")
                        .withDestinationCity("City no. 1")
                        .withDestinationPostCode("Post code no. 1")
                        .build()
        ).commit();

result.getThrownException().ifPresent(LOGGER::error);

Look how the builder() method returns an instance of the step? It is what makes the Step Builder [2] pattern differ from the traditional Builder. By the way, a transaction should not be instantiated outside of the package. From my perspective, a good encapsulation is a key to create a good API. Why? You do not want to break a contract with a client, so you would rather not let anyone use anything that is intended to be used internally.

public final class OrderProductTransaction {
    private final List<Runnable> steps;
    OrderProductTransaction(final List<Runnable> steps) {
        this.steps = steps;
    }

    public static ProductStep builder() {
        return new ProductStep(new LinkedList<>());
    }

    public OrderProductTransaction build() {
        return new OrderProductTransaction(steps);
    }

    public List<Runnable> getSteps() {
        return new LinkedList<>(steps);
    }

    public static final class Result {
        private final Exception thrownException;
        Result(final Exception thrownException) {
            this.thrownException = thrownException;
        }

        public Optional<Exception> getThrownException() {
            return Optional.ofNullable(thrownException);
        }
    }
}
public class ProductStep {
    private final List<Runnable> steps;
    ProductStep(final List<Runnable> steps) {
        this.steps = steps;
    }

    public DestinationCityStep addProduct(final String product) {
        steps.add(() -> System.out.println("Adding product: " + product));
        return new DestinationCityStep(steps);
    }
}
public class DestinationCityStep {
    private final List<Runnable> steps;
    DestinationCityStep(final List<Runnable> steps) {
        this.steps = steps;
    }

    public DestinationPostCodeStep withDestinationCity(final String destinationCity) {
        steps.add(() -> System.out.println("Adding destination city: " + destinationCity));

        return new DestinationPostCodeStep(steps);
    }
}
public class DestinationPostCodeStep {
    private final List<Runnable> steps;
    DestinationPostCodeStep(final List<Runnable> steps) {
        this.steps = steps;
    }

    public OrderProductTransaction withDestinationPostCode(final String destinationPostCode) {
        steps.add(() -> System.out.println("Adding destination post code: " + destinationPostCode));

        return new OrderProductTransaction(steps);
    }
}

What do we gain here? Firstly, our code is easier to use in every IDE. Basically, we guide a user. Our business action is divided into steps. The user can take a particular step only if other required steps have been taken before. This is the way, in which we can make sure that the user will take all the steps in the correct order. What about transactions then? If an exception is thrown, our program simply stops executing next steps and returns (but not re-throws!) the thrown exception. A validation could be performed in each step separately throwing an exception, which would be caught and then returned. Note that providing a meaningful exception is important and it is totally up to you.

Summary

We have seen the pros of the, let us call it the API builder [3] (I do not know how to call this pattern as I haven’t seen it anywhere on the web, but if it already exists please let me know!) – by which we can create readable, transactional, composable actions in a way similar to creating objects using the Builder pattern. What are the cons then? The one that I am aware of is that using a method chaining is somewhat arguable as we do not know what type is returned by each individual call [4]. And for now, I do not know of any other.

[1] https://github.com/tomaszczerminski/fluent-action-builder
[2] http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html
[3] In fact it’s a variation of the Step Builder pattern with lazy evaluation and transactional execution, but I’d like to have more consise name than that.
[4] If you use an IDE it’s no longer a problem

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami