Lambdas for Fluent and Stable APIsNovember 19th, 2013
A few weeks ago I wrote an introduction on Java 8 lambdas. In this introduction I explained what a lambda is and how to use them in conjunction with the new Stream API, that is also introduced in Java 8.
The Stream API provides a more functional interface to collections. This interface heavily depends on lambdas. But there’s much more to lambdas than improved collection processing
Lambdas offer you a chance to build much more fluent APIs. To show this, as an example I like to use a
UserStore which facilitates fetching and saving users using a database. Its public API commonly looks like the following.
The list of
findBy methods often is longer than just the two I’ve included here. As the system grows it’s likely that there will be others. While that works, the thing is, all these methods basically do the same thing. They return all the users with a property that matches a specific value.
Some frameworks offer work-arounds for this mess. If you’ve used Hibernate you’re probably aware that they provide a work-around to this with
findByExample where you provide a
User as an example object that provides the properties and values to query by. Any value that is set in this example object is used to query by, while any field that is
null is excluded from the query. You can tweak this behavior a little, but there are a number of problems with this approach. Think of default values, required fields (i.e. fields that cannot be made
null), and immutability. iBatis, MyBatis, and also Spring Data, use code generation to save you time implementing all these methods, leaving the API bloated with a list of
These work-arounds might come a long way, but they do leave their own specific problems behind. A different approach would be to use lambdas.
Lambdas can help us to decouple the query part from the filter specification. Let’s change the
findBy functions to a single function that accepts a lambda.
That’s a much better API. Obviously it’s a bit naive as the predicate checks a
User object; you generally want to filter using your database query. Nonetheless it serves the purpose of this example pretty well, and you can experiment with your own lambdas to filter using database queries. [Note:
Predicate comes with Java 8 and is located in the
Before you might want to get mad because at least with the previous API the predicates were bundled in one place, we can still bundle (common) predicates. For example, by creating a utility class
UserPredicates that contains them.
Using the new
UserStore API has become pretty simple.
There one thing left in
UserStore that really bothers me, though. The
find(id) function returns a user. But what if there’s no such user?
To improve on this we can (and should) look at another new feature of Java 8, Optional. This is sort of the Java implementation of the maybe monad. It looks a lot like Scala’s
Optional we can better express that a function may return a value but not necessarily, and prevent using
null. In our
find(id) example returning
Optional clearly expresses that we might find a user with the requested ID, but maybe no such user exists.
Not only does the API now document the fact that you might get a user, it also never returns
null. I consider an API that never returns
null much safer. One day a new programmer might not realize that
find can return
null and a null-pointer exception is the result. Just hopefully the team catches it before production. Null-pointer exceptions are really easy to prevent, as long as you never use
We can use functions on
Optional to get a value from user if available, or to get a default value otherwise. For example, to safely get the user’s lastname we write the following.
This code is pretty expressive and doesn’t require a lot of explaination. If there’s a user, get it’s lastname. Otherwise, get an empty string.
What if we need to send the user a password-reset email, if it is found?
The password-reset is sent if a user is found, otherwise nothing happens.
Since Java does not support destructuring like many other languages that provide maybe (e.g. Haskell, Clojure, and Scala), we are limited to the functions of
Optional. This makes
Optional weaker than its equivalent in any of these languages.
Of course not only the APIs of repositories can benefit from lambdas.
Optional is also a good example of an API that benefits from lambdas. Personally I also find lambdas particulary useful to replace passing builders around. Rather than passing a specific builder to a function, often it improves decoupling by yielding a builder from the function. Let me show you an example for sending email to clarify this idea.
To use the
Mailer we need to pass a specific builder into it. These builders have a common interface but are different in the kind of message they build. And the
Mailer has separate methods because it must add different information based on the type being used. Because of this any client-code is tightly coupled to pass the correct builder.
As you probably suspect, this is where lambdas are helpful. Rather than requiring the client to create a builder and pass it in, the
Mailer functions can create the builder they need and yield it to a lambda.
To use the
Mailer all we need to do is provide a lambda to build the message.
The API now is much more stable. Client-code is decoupled from any changes in specific builders, and won’t break as long as functions on the builder remain compatible.
As the examples helped me show, lambdas can help you build more fluent and stable APIs that are more intention revealing. These APIs require not much documentation for other programmers to get going because it’s actually hard for them to get it wrong. As a general guideline I prefer clear and intention revealing code over documentation. Fix, don’t document.
Of course I’ve only shown a few examples in this article. Lambdas are much more widely applicable than only with the examples here. I hope this article provided you with some new insights in what lambdas can help you with, and that you can think of ways how they can improve your code.