Implementing builders in Java


Note: Much improved versions of this post have been published to JAXenter.de and JAXenter.com:
Erzeugungsmuster mit Java 8 Lambdas
Creational patterns with Java 8 lambdas

I hope you enjoy reading them as much as I enjoyed writing them!

Sometimes I’m under the impression that one of my favorite DI frameworks, Spring, is the butt of a gazillion jokes regarding factories. For example it has a SimpleBeanFactoryAwareAspectInstanceFactory.

The name is actually well chosen: You’ll get a factory that produces instances of aspects by using another factory. It must be a factory, as you want to have polymorphism:

“In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created.”

“Define an interface for creating an object, but let subclasses decide which class to instantiate. The Factory method lets a class defer instantiation it uses to subclasses.”

Factory method pattern

Though I find factories useful, I’m under the impression that they are most useful in frameworks, providing groundworks for other stuff, for example for decorators through aspects.

Application wise I often need builders:

The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.

Builder pattern

Sometimes it’s actually correct, to end up with a “MyClassBuilderGenerationFactory” (though I’d probably call it MyClassFactoryBuilder, saying that it is a builder to create factories producing instances of MyClass):

So, what’s my favorite way of implementing builders using Java? Well, I pretty much have the idea from the great Venkat Subramaniam (if you have the chance to see one of his talks, go!). The example comes from a real world project (our temperature monitor) and builds a sensor:

import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.TimeZone;
import java.util.function.Consumer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
 
public class Sensor {
 
    // (1)
    private final DocumentBuilder documentBuilder;
 
    private final XPathExpression xPathExpression;
 
    private final DateTimeFormatter dateTimeFormatter;
 
    private final String url;
 
    // (2)
    Sensor(Builder builder) {
        try {
            this.documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            this.xPathExpression = XPathFactory.newInstance().newXPath().compile("/wx/temperature/current");
        } catch (ParserConfigurationException | XPathExpressionException ex) {
            throw new RuntimeException("Could not create DocumentBuilder or XPath", ex);
        }
 
        this.dateTimeFormatter = DateTimeFormatter
                .ofPattern(builder.dateTimePattern, builder.locale)
                .withZone(builder.timeZone.toZoneId());
 
        this.url = builder.url;
    }
 
    // (3)
    public static Sensor forUrl(final String url, final Consumer<Builder> cfg) {
        final Builder builder = new Builder(url);
        cfg.accept(builder);
        return new Sensor(builder);
    }
 
    public static class Builder {
 
        private String url;
 
        // (4)
        private String dateTimePattern = "dd.MM.yyyy (HH:mm)";
 
        private Locale locale = Locale.GERMAN;
 
        private TimeZone timeZone = TimeZone.getTimeZone("Europe/Berlin");
 
        // (5)
        private Builder(String url) {            
            this.url = url;
        }
 
        public Builder withUrl(String url) {
            // (6)
            if (url == null || url.isEmpty()) {
                throw new IllegalArgumentException("URL may not be empty!");
            }
            this.url = url;
            return this;
        }
 
        public Builder withDateTimePattern(String dateTimePattern) {
            this.dateTimePattern = dateTimePattern;
            return this;
        }
 
        public Builder withLocale(Locale locale) {
            this.locale = locale;
            return this;
        }
 
        public Builder withTimeZone(TimeZone timeZone) {
            this.timeZone = timeZone;
            return this;
        }
    }
 
    public static void main(String... a) {
        // (7)
        final Sensor sensor = Sensor.forUrl("http://wetter-aachen-brand.de/mwlive.xml", cfg -> cfg
                .withTimeZone(TimeZone.getTimeZone("Europe/Berlin"))
                .withLocale(Locale.GERMAN)
        );
    }
}

What do we have here?

  1. An immutable sensor object, all values are final, including the ones that take more than one information to configure (i.e. the dateTimeFormatter).
  2. A sensor cannot be build without the builder, so you won’t end up with invalid objects. Also, you won’t end up with a telescoping constructor (adding more and more arguments or overloading constructors). As you can see I don’t mind some intelligence in a constructor, but you could move the instantiation of the stuff that throws exceptions into the builder
  3. Several things going on here:
    A “speaking” method: I’ll always want to have a sensor for a given URL. Also note that I want to make clear which values are obligatory. A sensor without an URL is useless. If you add Project Lombok you can get rid of the manuell checks in (6)
    The creational method takes a Consumer as an argument. Together with the private Builder constructor in (5) this leads to the fact that the builder cannot be instantiated without the context of a Sensor. This clarifies the fact, the this builder here should be used once and only once. The creational method does the build, not the external caller.
  4. Decouple the builder properties from the actual needed properties. The builder knows a format pattern, the resulting object the formatter.
  5. Don’t allow the builder to be used standalone, only in the context of (3).
  6. Check for required arguments in the builder methods.
  7. Finally: A fluent API to configure your objects in a very concise way.

But can we do better than that? What if the creation of stuff depends on an order? It’s actually pretty easy to implement and you’ll get a very clean interface, without the need for purely functional languages by just using Java 8 idioms:

import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.TimeZone;
import java.util.function.Function;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
 
public class Sensor {
 
    private final DocumentBuilder documentBuilder;
 
    private final XPathExpression xPathExpression;
 
    private final DateTimeFormatter dateTimeFormatter;
 
    private final String url;
 
    // (1)
    Sensor(BuilderStep3 builder) {
        this.documentBuilder = builder.documentBuilder;
        this.xPathExpression = builder.xPathExpression;
        this.dateTimeFormatter = builder.dateTimeFormatter;
        this.url = builder.url;
    }
 
    public static class Builder {
 
        private String url;
 
        private Builder(String url) {
            this.url = url;
        }
 
        // (2)
        public BuilderStep2 withDateTimePattern(final String dateTimePattern) {
            return new BuilderStep2(url, dateTimePattern);
        }
    }
 
    public static class BuilderStep2 {
 
        private final String url;
 
        private String dateTimePattern = "dd.MM.yyyy (HH:mm)";
 
        private Locale locale = Locale.GERMAN;
 
        private TimeZone timeZone = TimeZone.getTimeZone("Europe/Berlin");
 
        public BuilderStep2(final String url, final String dateTimePattern) {
            this.url = url;
            this.dateTimePattern = dateTimePattern;
        }
 
        public BuilderStep2 withDateTimePattern(String dateTimePattern) {
            this.dateTimePattern = dateTimePattern;
            return this;
        }
 
        public BuilderStep2 withLocale(Locale locale) {
            this.locale = locale;
            return this;
        }
 
        public BuilderStep2 withTimeZone(TimeZone timeZone) {
            this.timeZone = timeZone;
            return this;
        }
 
        // (3)
        public BuilderStep3 withPath(final String path) {
            return new BuilderStep3(url, DateTimeFormatter
                    .ofPattern(this.dateTimePattern, this.locale)
                    .withZone(this.timeZone.toZoneId()), path);
        }
 
    }
 
    // (4)
    public static class BuilderStep3 {
 
        private final String url;
 
        private final DateTimeFormatter dateTimeFormatter;
 
        private final DocumentBuilder documentBuilder;
 
        private final XPathExpression xPathExpression;
 
        public BuilderStep3(String url, DateTimeFormatter dateTimeFormatter, String path) {
            this.url = url;
            this.dateTimeFormatter = dateTimeFormatter;
            try {
                this.documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
                this.xPathExpression = XPathFactory.newInstance().newXPath().compile(path);
            } catch (ParserConfigurationException | XPathExpressionException ex) {
                throw new RuntimeException("Could not create DocumentBuilder or XPath", ex);
            }
        }
    }
 
    // (5)
    public static Sensor forUrl(final String url, final Function<Builder, BuilderStep3> cfg) {
        return cfg.andThen(Sensor::new).apply(new Builder(url));
    }
 
    public static void main(String... a) {
        // (6)
        final Sensor sensor = Sensor.forUrl("http://wetter-aachen-brand.de/mwlive.xml", cfg -> cfg
                .withDateTimePattern("dd.MM.yyyy")
                .withLocale(Locale.GERMAN)
                .withPath("/wx/temperature/current")
        );
    }
}
  1. The sensor now takes a BuilderStep3 argument. Also take note that I have moved the construction of complex objects into the builders as well
  2. The first builder returns a BuilderStep2 as the context changes from an url to datetime stuff
  3. Context changes again from creating the actual DateTimeFormatter to XML and XPath stuff
  4. BuilderStep3 is the last step and returns no other builder (and also has no build method!)
  5. Here is the “fun”: The creational function now takes a Function from Builder to a BuilderStep3 as argument and no simple consumer. This way I am in control over creating the first builder and then I can guide the user of my builders through the right order of configuring stuff. The final usage in (6) doesn’t look much different but it’s now the only way to use that set of builders
    Also take note how easily you can chain function calls in Java 8
  6. Final usage: Configuration can only be done in the correct order

I think my last suggestion can be very nice integrated into the Step builder pattern as described by Marco.

| Comments (2) »

06-Jul-16


On sourcecode formatting

Without any comment on which style is right, you can use GNU util expand / unexpand to replace tabs with spaces and vice versa.

For example, you can use the following command to replace all tabs with spaces and the recommended 8 spaces width in all Java files through

find . -name '*.java' ! -type d -exec bash -c 'expand -t 8 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;

Expand takes care of spaces between tabs and should be the preferable solution to sed in this case.

While you’re at it, you can use sed however to trim trailing spaces:

find . -iname '*.java' -type f -exec sed -i '' 's/[[:space:]]\{1,\}$//' {} \+

For further reading, follow this discussion:

😉

| Comments (0) »

04-Jul-16


Adding Spring-Integration to a Boot application

This weeks post has some anecdotally value for me. 9 years ago I wrote a horrible tutorial about threads, which I really liked back then. In the meantime I often thought I should delete the post and only kept it around to prove some improvements on my side 😉

Anyway, the code in that tutorial has been the foundation for a module that is running for near as long as the blog post is old. My company uses it on several occasions for data transfer of all kind, being it files, checking emails or ftp servers and it works pretty well. The downside: It’s pretty much not maintainable, the code is bad and half the stuff or more I wrote can be replaced with messaging or a real service bus.

In the first iteration of rewrite I did exactly that: I used a Java Messaging Service implementation for moving stuff around and did the rest myself. Luckily this time I noticed…

Entering Spring Integration:

Extends the Spring programming model to support the well-known Enterprise Integration Patterns. Spring Integration enables lightweight messaging within Spring-based applications and supports integration with external systems via declarative adapters. Those adapters provide a higher-level of abstraction over Spring’s support for remoting, messaging, and scheduling. Spring Integration’s primary goal is to provide a simple model for building enterprise integration solutions while maintaining the separation of concerns that is essential for producing maintainable, testable code.

I really don’t care at the moment which backend or transport I use… As long as I know it’s swappable, I’m fine.

Getting up and running in a Spring Boot application is as easy as adding the dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-integration</artifactId>	    	
</dependency>
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-file</artifactId>	  	
</dependency>
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-java-dsl</artifactId>
    <version>1.1.2.RELEASE</version>
</dependency>
@SpringBootApplication
@EnableIntegration
public class Application {
}

Nothing fancy here.

You can either create your flows throw XML Definitions which has the benefit of being supported by Spring Tool Suite™ or as @Beans. For the later, you’ll find a nice sample here.

I opted for the bean version, mainly for one reason: I have to support multiple, differently configured instances of one or more flows.

I have a list of workflow builders (custom interface) in an @Configuration class that gets auto populated through @EnableConfigurationProperties and in a loop i use the method described in this GitHub issue:

@PostConstruct
void initializeWorkflows() {	
    this.workflowBuilders.stream().flatMap(workflowBuilder -> workflowBuilder.build().entrySet().stream()).forEach(workflow -> {
        beanFactory.registerSingleton(workflow.getKey(), workflow.getValue());
        beanFactory.initializeBean(workflow.getValue(), workflow.getKey());
    });	
}

The “Cafe Demo” helped a lot to get started with the Java DSL, especially the lambda version.

To demonstrate what is possible, I can show you my current task, which can be roughly explained as follow “Watch one ore more directories for a given tenant, detect CSV and excel files, split the sheets of excel sheets into separate content and load all contents into a database and call a stored procedure afterwards”.

The first part is really simple:

// 1
final FileListFilter<File> fileListFilter = new CompositeFileListFilter<>(Arrays.asList(
    new FileSystemPersistentAcceptOnceFileListFilter(this.metadataStore, "exceldatenaustausch:"),
    new FilesOnlyFileListFilter()		    
));
final FileReadingMessageSource reader = new FileReadingMessageSource();
reader.setDirectory(new File(s.directory));
reader.setFilter(fileListFilter);
 
// 2	            
IntegrationFlows
    .from(reader, endpoint -> endpoint.poller(Pollers.fixedRate(s.intervall, TimeUnit.SECONDS)))
    .enrich(enricher -> enricher
    	.header("tenant", s.tenant)
    	.<File>headerFunction("contentType", message -> contentTypeMap.getOrDefault(tika.detect(message.getPayload()), "unknown"))
    )
    .route(payload -> "exceldatenaustausch-incoming-channel")
    .get();
  1. Setup a file reading message source, that watches a directory, accepts only files and those only once
  2. Poll that reader every n seconds, add a tenant number and the content type to each file message and route them to a specific channel

And all the rest is easy as well:

IntegrationFlows
    .from("exceldatenaustausch-incoming-channel")
    // 1
    .filter("!headers.contentType.equals('unknown')")
    // 2
    .route("headers.contentType", mapping -> mapping
        .channelMapping("csv",   "read-csv-channel")
        .channelMapping("excel", "split-excel-channel")
    )
    .channel("read-csv-channel")
        // 3
        .transform(File.class, Strings::fromFile)
        .route(payload -> "store-content-channel")
    .channel("split-excel-channel")
        // 4
        .split(new ExcelSplitter(locale, linebreak, separator, dateFormat), "extractSheets")
        .enrich(enricher -> enricher
        	.<ExcelSheet>headerFunction("sheetName", message -> message.getPayload().getSheetName())
        	.<ExcelSheet>headerFunction("sheetNumber", message -> message.getPayload().getSheetNumber())
        )
        // 3
        .transform(ExcelSheet.class, ExcelSheet::getContent)
        .route(payload -> "store-content-channel")
    .channel("store-content-channel")
    // 5
    .<String>handle((content, headers) -> {
        return excelDatenaustauschRepository.add((Integer)headers.get("madantennr"), (String) headers.get("fileName") content);			
    })
    // 6
    .handle(this.excelDatenaustauschService, "callfExcelDatenaustausch")
    .<Long>handle((status, headers) -> {
        LOGGER.info("Stored {} and called F_EXCEL_DATENAUSTAUSCH with result {}.", headers.get("fileName"), status);
        return null;
    })
    .get();

We see here:

  1. Filtering based on SpEL expressions
  2. Routing based on SpEL expressions and following channel mappings
  3. Transformation of messages from files to strings
  4. Splitting of messages from one file into a list of other stuff
  5. Handling of messages with an explicit handler
  6. Handling of messages by an implicit bean method

I like the result of one week work very much: The stuff is actually readable. Even if I don’t have a nice visualization from an XML based configuration, one can understand the flow just from the code and that’s much more than I had with the nearly 10 year old homegrown solution. I also have the infrastructure in place, to distribute that flow (and others) along several services and switch transports if necessary without the need for any boilerplate code.

| Comments (0) »

29-Jun-16


IMPROVE

In der vergangenen Woche war ich in der glücklichen Situation, einen weiteren Kurs mit Gernot Starke, Peter Hruschka und Carola Lilienthal zu besuchen.

Der IMPROVE Kurs war nicht nur ein hervorragender Baustein im iSAQB CPSA Advanced Level, sondern ein hervorragender Austausch zwischen Entwicklern und engagierten Trainern.

Natürlich fand’ ich auch die konkreten Themen zum Refactoring auch spannend, aber nicht so sehr wie den Schwerpunkt auf Analyse (und auch wenn es schwer fällt, auf Evaluate).

Bzgl. Refactoring und dem Plädoyer von Carola, Architekturen bzw. Konzepte und Muster nicht nur “auf dem Papier” zu haben, sondern auch zu leben, reicht eigentlich schon diese Einschätzung alltäglicher Aufgaben:


aufgabenteilung

Die (bekannten?) Top 5 Gründe für Probleme / Issues in Softwaresystem und Software:

  1. Unrealistische Zeitpläne
  2. Feature Creep
  3. Mitarbeiterfluktuation
  4. Interessenskonflikte von Stakeholder
  5. Produktivitätsunterschiede (“10x Programmer”)

Eigentlich war ich ganz froh, dass die Unterlagen (unverschuldet) nicht pünktlich da waren, denn ich habe mir endlich noch mal was mitgeschrieben. Da ich leider nicht so toll zeichnen kann wie einige, habe ich die mir wichtigen Impulse mal in eine unsortierte Mindmap gebracht (das Bild ist mit einer PDF Datei verlinkt):

Was mir – wie auch im Foundation Kurs mit Peter und Gernot – gefallen und Spaß gemacht hat, waren die praktischen Übungen. Übungen, die einen aus der Komfortzone bringen und insbesondere von Peter mit gefühlt großen Vergnügen forciert werden.

Für mich nehme ich – bei allem Spaß am Programmieren und refactoren – mit, dass meine weiteren Schwerpunkte ganz bewusst auf Softskills liegen werden. Schon alleine in den Übungen merkte ich wieder, wie wichtig es ist, sich auf andere Menschen einzulassen. Jede Architektur ist in meinen Augen auch immer ein Abbild des oder der Menschen dahinter und insbesondere ihrer Art, miteinander zu kommunizieren.

Und im übrigen, der beste Spruch bzgl. misslungener Kommunikation hinsichtlich Projektstatus, den ich aufgeschnappt habe:

| Comments (0) »

23-Jun-16


NetBeans IDE plugin supporting programming with Spring Boot

There are several plugins out there for supporting Spring development in NetBeans, but the

NB-SpringBoot

by Alessandro Falappa seems to be the most feature complete. At the moment installation must be done via manual download from here but that should be fixed soon.

You’ll get a really nice Spring property support:


propertysupport

Great integration with start.spring.io:


startspringio.png

And also some useful templates for different Spring artifacts (just one hint, Alex, make the suggested name uppercase):


templates.png

I’m gonna pass this on to my colleagues and friends, I think it’s a very great addition to NetBeans.

| Comments (1) »

19-Jun-16