Implement your own Redux in Java. Part 4: Middleware

javaredux

In this series of blog posts I'm implementing my own Redux in Java to use with JavaFX.

However, the purpose is not to create a production-ready library (because there is already ReduxFX available) but instead to learn by re-implementing it.

In the previous part I've introduced the basic concept of "middlewares", one of the extension mechanisms of Redux. However, in the first approach I was using a simplified API for middlewares to get a better understanding of the concept. The actual API of Middlewares is a little more complex. In this part I will show the limitations of the previous API and what kinds of middlewares you can't build with it. Then I will transform the API and the implementation to the "real" API. This will be a prerequisite to implement middlewares for asynchronous behavior, which will be the topic of the next part.

Topics in this blog series (maybe I will adjust this in the process of writing):

In the previous part I've created a ListMiddleware that supports the dispatch of Lists of actions. It checks if an incoming action is of type list and when this is the case it will dispatch all elements of the list. This was the code:

public class ListMiddleware<S> implements Middleware<S> {
	@Override 
	public DispatchFunction apply(DispatchFunction dispatch, Supplier<S> getState) {
		return action -> {
			if(action instanceof List) {
				List actionList = (List) action;
				
				actionList.forEach(dispatch);
			} else {
				dispatch.accept(action);
			}
		};
	}
}

While this middleware is not that useful in reality, it' a good example for the restrictions that we have with the current Middleware API. It's using a special type of object as action which is a common pattern when it comes to asynchronous middlewares.

Action-Creators

In the Redux community their is the patter of so called "action-creators". Instead of creating actions directly in your UI components, you move the creation-logic into a separate function. This way you can hide the creation of actions from the UI components and it's easier to reuse and to refactor action-creation.

In our application we have the AddItemAction and so we could create an action-creator like this:

public class TodoActionCreators {
	
	public static Object addItem(String text) {
		return new AddItemAction(text);
	}
	
}

The calling code in the UI component now looks like this:

public void addItem() {
	final String text = input.getText();

	store.dispatch(TodoActionCreators.addItem(text));

	input.setText("");
}

If at some later point in time we decide that we need to dispatch an additional action in the "add item" use-case, we can simply adjust the action-creator while leaving the UI component as it is. Our new action-creator could now look like this:

public static Object addItem(String text) {
	return Arrays.asList(
		new AddItemAction(text),
		new SomeOtherAction()		
	);
}

To support this we have to install the ListMiddleware. Otherwise the List would reach the reducer which is not what we want (in our current implementation it wouldn't reach the reducer but instead throw a ClassCastException). With Lists as actions it might even would be possible to handle this in the reducer but when we start to send asynchronous actions we get a real problem: The reducer has to be a pure function. It may not trigger any side-effects.

The other problem is that we would introduce a dependency to our middlewares in the reducer. This makes it harder to replace middlewares later on. So the general rule is: If your middleware allows you to dispatch special action objects it should also take care that these special objects don't reach the reducer. Your middleware has to filter out all "non-normal" actions.

Is this the case with the current implementation of the ListMiddleware? At first sight this seems to be the case. We check for every incoming action if it is of type List. But what happens when the list itself contains another list? You might ask, why would anybody pack a list of actions in a list of actions? Well we said that we like to decouple actions by using action-creators and this means that it's possible that one action-creator uses another action-creator. So our code could also look like this:

public static Object addItem(String text) {
	return Arrays.asList(
		new AddItemAction(text),
		someOtherActionCreator()		
	);
}

Here we invoke another action-creator in our own action-creator. And now it's possible that the other action-creator itself produces a List of actions. If we would restrict this nesting of special actions we would again introduce coupling between action-creators. We wouldn't be able to refactor one action-creator if it is used by another one. So in general we like to support such nested lists of actions. When we come to async middlewares later on we will see that we have a similar situation there.

So how would our ListMiddleware react in this situation? It passes the list down to the next dispatch function and ultimately to the dispatch function of the store. The store will try to cast the action to our marker interface Action and will throw a ClassCastException instead.

To understand this behavior we should go one step back and look closer at the chain of middlewares. The store has the "original" dispatch-function and passes it to the first middleware in the chain. The first middleware enhances/wraps the dispatch function and returns a new one. This new dispatch function will be passed to the second middleware in the chain and so on. The last middleware returns it's enhanced dispatch function, which will then be used by the store.

"Real" Middleware API

What we want is a way to pass an action to the start of the middleware chain. We like to have access to the original dispatch function. For this we need to change our middleware API. There are many ways to extend our API to support this but I will try to keep the code close to the original Redux library and there a nested function is used. In the original API a middleware is a function that takes the original dispatch function of the store and the getState function as argument and returns a function. This function is the "real" middleware: It takes a dispatch-function as argument and returns an enhanced dispatch-function.

Before I describe this in more details, let's adjust our Middleware interface:

public interface Middleware<S> extends BiFunction<DispatchFunction, Supplier<S>, Function<DispatchFunction, DispatchFunction>> {
	@Override
	Function<DispatchFunction, DispatchFunction> apply(DispatchFunction dispatch, Supplier<S> getState);
}

Ok, this can be confusing. Let's take a closer look at the arguments of the "outer" function: It takes a dispatch-function and a getState function as argument. These two are taken directly from the Store. The dispatch-function is the one that the middleware can use to pass an action to the start of the middleware chain. Maybe you can think of this outer function as some sort of "middleware factory method". It will be invoked by the Store to create the middleware's actual enhancer-function. This enhancer-function takes a dispatch-function as argument and enhances it and returns a new dispatch function. The difference here is that it don't get the original dispatch-function as argument but instead the dispatch function of the next middleware in the chain.

Maybe this becomes clearer when we look at how our two existing middlewares look like when we adjust the code to our new API. The LoggingMiddleware looks like this:

public class LoggingMiddleware<S> implements Middleware<S> {
	
	@Override
	public Function<DispatchFunction, DispatchFunction> apply(DispatchFunction dispatch, Supplier<S> getState) {
		return next -> action -> {
			
			S stateBefore = getState.get();

			next.accept(action);

			S stateAfter = getState.get();

			System.out.println("dispatched action: " + action);
			System.out.println("state before:");
			System.out.println(stateBefore);
			System.out.println("state after:");
			System.out.println(stateAfter);
		};
	}
}

The first thing you might notice is the next -> action -> ... line with the double arrows. This is the "nested" function I've mentioned before. In functional programming this is also known as Currying. It's the idea that every function that takes multiple arguments can also be expressed as sequence of functions, each of them taking only a single argument. So in principle it would also be possible to define the middleware API with this signature: DispatchFunction apply (DispatchFunction storeDispatch, Supplier<S> getState, DispatchFunction next). For developers that aren't that familiar with functional programming this version might be easier to handle because in my experience, many developers have some kind of "fear" when it comes to functions that return functions that return functions. On the other hand, if your are familiar with functional programming, this style of development becomes quite natural. And in many situations makes the code more composable which is also the case with redux middlewares. We will see this in just a few moments when we implement the handling of middlewares in our store but I'm also planning to write a dedicated blog post in this series on the topic of function composition.

What you should also notice in this code example the LoggingMiddleware ignores the original dispatch function from the store but instead just uses the "next" dispatch function from the middleware chain. This is important: If we would change this line to dispatch.accept(action) we would get an infinite loop (and a StackOverflowError) as a result.

Let's look at the ListMiddleware for another example:

public class ListMiddleware<S> implements Middleware<S> {
	@Override
	public Function<DispatchFunction, DispatchFunction> apply(DispatchFunction dispatch, Supplier<S> getState) {
		return next -> action -> {
			if(action instanceof List) {
				List actionList = (List) action;
				
				actionList.forEach(dispatch);
			} else {
				next.accept(action);
			}
		};
	}
}

If our action is an instance of List, we cast it to the list of actions and pass them to the original dispatch function from the store. This way we can make sure that they run through the whole middleware chain from the beginning. If our action is not a list, we ignore it and pass it to the next middleware in line. This finally solves our problem with the list of list of actions. With this API we now have everything at hand to implement more interesting middlewares like those for handling asynchronous behavior. This will be the topic of the next blog post in this series.

But before that we still have to adjust our Store implementation to our new API. The old version looked like this:

public class Store<S> {
	// ...
	private DispatchFunction dispatch = action -> {
		S oldState = this.currentState;
		
		S newState = reducer.reduce(oldState, (Action) action);

		if(oldState != newState && !oldState.equals(newState)) {
			this.currentState = newState;

			notifySubscribers();
		}
	};
	
	public Store(S initialState, Reducer<S> rootReducer, Middleware<S> ...middlewares) {
		this.currentState = initialState;
		this.reducer = rootReducer;

		for (Middleware<S> middleware : middlewares) {
			this.dispatch = middleware.apply(dispatch, this::getState);
		}
	}
	
	public void dispatch(Object action) {
		this.dispatch.accept(action);
	}
}

The only needed changes are within the constructor:

public Store(S initialState, Reducer<S> rootReducer, Middleware<S> ...middlewares) {
	this.currentState = initialState;
	this.reducer = rootReducer;
	
	for(Middleware<S> middleware : middlewares) {
		Function<DispatchFunction, DispatchFunction> middlewareFunction = middleware.apply(this::dispatch, this::getState);
		
		this.dispatch = middlewareFunction.apply(this.dispatch);
	}
}

As we have nested functions we have to apply them separately. First we have to invoke our "middleware factory function" (don't know a better name for it) to get our "middlewareFunction". Important here is that I'm passing this::dispatch instead of this.dispatch. It's a method reference to the store's dispatch method. Then I'm applying the actual middleware function and passing the "dispatch" object to it. For the first middleware in the chain this is the original dispatch function and for all following middlewares it's the enhanced dispatch function from the previous middleware.

And that's all. Our Store now supports Middlewares, the first of two extension mechanisms that the original Redux library provides. The second extension mechanism is called "Store enhancer". Store enhancers are much more powerful in the sense that they can adjust the behavior of the store in a much deeper way. Why am I telling you about store enhancers in an article about middlewares? While middlewares are an official extension mechanism of the original redux library, the store implementation itself has no idea of middlewares at all. Instead, the whole concept of middlewares is implemented by a store enhancer called "applyMiddlewares". In one of the future blog posts in this series I will describe what store enhancers are and how we can extract the middleware support into a store enhancer. But in the mean time we will keep this implementation and in the next few posts I will write more on middleware details and different types of middlewares.


You can find the code for redux example in this github repository. This commit is pointing directly to the state of this blog post.