DEV Community

Sergiy Yevtushenko
Sergiy Yevtushenko

Posted on

Creating DSL-like API's in Java (and fixing Builder pattern)

In many cases Java DSL is just an way to assemble some complex configuration and then pass built structure to internal method which will handle it. For example, SQL query builders, HTTP request builders are all fall into this category.

First of all, plain Builder pattern rarely convenient in such cases. It has no restrictions on order of calls and may result to incomplete configuration or just unreadable code.

The solution is to split Builder into set of interface chaining each other.

To illustrate approach let's imagine that we're trying to write yet another, very simple HTTP request builder. With described approach it might look like this:

public interface RequestBuilder {
    static Stage1 get(String uri) {
        return new Builder(Method.GET, uri, list());
    }

    interface Stage1 {
        default Stage2 withoutParameters(Object... parameters) {
            return with();
        }

        Stage2 with(Object... parameters);
    }

    interface Stage2 {
        Request build();
    }

    class Builder implements Stage1, Stage2 {
        private final Method method;
        private final String uri;
        private final List<?> parameters;

        private Builder(final Method method, final String uri, final List<?> parameters) {
            this.method = method;
            this.uri = uri;
            this.parameters = parameters;
        }

        @Override
        public Stage2 with(final Object... parameters) {
            return new Builder(method, uri, list(parameters));
        }

        @Override
        public Request build() {
            return new Request(method, uri, parameters);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The building code is concentrated in Builder class. This implementation uses immutable instance, but this is not strictly necessary.

The whole Builder class is never exposed directly to user code. Instead we return interface which limits user actions to with() and withoutParameters(). These methods in turn return next building stage interface (Stage2) which allows user to finish building object.

The usage of the example code might look like below.
Note that there is no way to not specify parameters (or lack of them) explicitly:

...
RequestBuilder.get("http://somewhere.com/api/users/{param1}")
              .with(userId1)
              .build();
...
RequestBuilder.get("http://somewhere.com/api/users")
              .withoutParameters()
              .build();
...

Enter fullscreen mode Exit fullscreen mode

The example is quite simple, but actually there might be any number of intermediate stages and they not necessarily should fix building process as unidirectional graph (for example, creating loops to assemble complex sub-object can be easily done as well).

What we get in turn:

  • There is no way to build incomplete object
  • User is guided through object building stages by IDE and limited number of choices at each step
  • All invocations through the code follow same pattern, making reading and reviewing such a code simpler
  • Whole API being properly designed and named can form quite convenient to use DSL.

Top comments (4)

Collapse
 
siy profile image
Sergiy Yevtushenko

Any DSL requires a lot of thinking. Described approach significantly simplifies building DSL's. And for particular case of replacement of plain Builder it's extremely simple, just some extra boilerplate code.

Collapse
 
rucko24 profile image
0x52

Thanks my big dog.

Thread Thread
 
rucko24 profile image
0x52

updated comment, thanks again...

Collapse
 
nickshoe profile image
nickshoe

I like the simplicity of this example. I find it a good starting point for learning how to build a DSL.