Dependency injection using the Reader Monad in Java 8

John McClean
4 min readJul 28, 2015

--

The blog entry “The reader monad for dependency injection” in Scala, details a functional alternative to frameworks such as Spring or Dagger. It is possible to do exactly the same thing in Java 8 using Functional Java and Cyclops . cyclops-react now also includes an implementation of the Reader monad via it’s FluentFunctions API.

See also (https://medium.com/@johnmcclean/can-we-make-working-with-functions-easier-in-java-8-81ed9d1050f2) & Tony Morris’ and Runar Bjarnson’s (more advanced) presentation on Dependency Injection without the Gymnastics : <article continues after the presentation>

(Hat tip to Mark Perry for the links!)

Brief introduction to the Reader Monad

The the class fj.data.Reader is, like JDK8 Stream, Optional & CompletableFuture, a monad. This means it has methods very useful for transforming data, such as map and bind (flatMap). The ‘data’ being transformed, in this case, just always turns out to be a single argument Function.

Single Argument Functions

Single argument functions accept a single parameter and return some output.

 (Input in) -> output

When we create a Reader instance, the input to our function, is the type we want to inject. The function itself defines the work we would like to do with that type. For example if we would like to load data from a database, we could define a function as follows

(UserRepository userRepository) -> userRepository.get(id)

In this example we are loading a User object via the supplied UserRepository. If we don’t have a UserRepository to hand, we can simply define the function and then lazily execute it at a point in the code where the UserRepository is available.

Pass a function to a Reader

We can pass this function to the unit method of the Reader monad (equivalent to Stream.of or Optional.of)

int id = 1000;
Reader r = Reader.unit( userRepository -> userRepository.get(id))

Executing a function inside a Reader

We can execute this function via the Reader#f method. E.g.

User user1000 = r.f(userDao);

Working with the returned User

We can also ‘work’ with the User that will be returned when the function is executed, inside the Reader, before we call ‘f’. In this case we will be lazily defining the set of functions we want the Reader to apply. ‘f’ will behave much like a terminal operation in the Stream api, and map / bind will lazily tee up the operations to be executed.

int id = 1000;
Reader r = Reader.unit(userRepository -> userRepository.get(id))
.map(User::getBoss);

In this example we have chained the operations to load the user of specified id, but then extract their boss.

User user1000sBoss = r.f(userDao);

A User DAO

Let’s define a UserRepository interface with get and find methods

public interface UserRepository { public User get(int id);
public User find(String username);
}

A User entity

Let’s also define a User entity

@Value
public class User {
int id;
String name;
String email;
User supervisor;

}

Loading Users

With our DAO in place we can define a class, or interface with default methods, that makes use of the DAO. In our case, because we want to inject the DAO, we won’t use it directly but via a function that accepts the DAO and the Reader Monad.

You can view the code here — Users.java

Working with our Readers

We could call map/ bind directly on our Reader monads, or, we can make use of another Cyclops feature — Comprehensions. Comprehensions allow us to abstract away transformation code, and focus on what data we want to use, and what we want to change it to. For example, to find a user with a given username and return both their names we can do something like this

Reader r = Do.add(findUser(name))
.withAnyM(user ->getUser(user.getSupervisor().getId()))
.yield(user -> boss -> "user:"+name+" boss is "+boss.getName())
.unwrap();

When we call ‘f’ on the Reader returned our result will be a String with the users name and their boss’ name.

Note [ Although the Cyclops team will eventually create a custom comprehension builder that understands Functional Java types — for now, we use FJ.anyM method to enable Cyclops for comprehender correctly interpret the Generic Type info on the FunctionalJava Reader. This makes the code a little bit noisier, than the Scala equivalent.]

A UserInfo Class

Let’s construct a UserInfo class to handle all this, in this case we will return a map of User Info rather than just a simple String.

You can view the code here — UserInfo.java:

Injecting the Dependency

We can call our userInfo class and pass in an implementation of UserRepository to get back a map of user information for a given username.

e.g.

UserRepository repo = new UserRepositoryImpl();
String username = "bob";
Map bobsData = new UserInfo().userInfo(username).f(repo);

Create an Application class

Now that we have defined our Readers, and how to process with them, the last step is to do the actual dependency injection to get real results back. We can define an Application class that has access to an implementation of the UserRepository, and defines a userInfo method that does the dependency injection.

You can view the code, along with a mock repo here — Application.java

--

--

John McClean

Architecture @ Verizon Media. Maintainer of Cyclops. Twitter @johnmcclean_ie