October 14, 2016

Redux4j, Implementing Redux in Java

Hi people of the internet!

After multiple months having fun with React and Redux, here’s what I thought: « why can’t we have some fun implementing it in Java 8? »

That’s what we’re going to do right away, just for fun :)

To be honest my first idea was to write it in Clojure and make on article of it, but there was nothing new nor hard about it, so get back to classic.

What is redux?

Redux is a predictable state container for JavaScript apps.

In short, it is a simple and predictable state model, borrowing concepts from functional programming an immutable data.

If you want a nice cartoon introduction to redux, you can browse the following link: https://code-cartoons.com/a-cartoon-intro-to-redux-3afb775501a6

Java implementation

Redux is easy to implement, even in Java. It does take less than a hundred lines to implement the core of it including middleware management and reducers combination.

Too easy to implement using traditional Java but what fun would that be, instead we’ll be doing it using the functional stuff of Java 8, and using also some parts of Javaslang.

Let’s see how we do that and what do we need in the rest of the article :)

Store

Well, we just need a Store really, the reducers and actions are just plain Java functions or objects, so we don’t need to deal with them or do something special about it.

We will hava a ReduxStore interface like this:

ReduxStore.java

import java.util.UUID;
import java.util.function.Consumer;

public interface ReduxStore<State, Action> {
    State getState();

    void dispatch(Action action);

    UUID subscribe(Consumer<State> subscriber);

    void unsubscribe(Consumer<State> subscriber);
    void unsubscribe(UUID subscriberId);
}

I think we’re covering pretty much everything by now. A store:

  • has a state that we can retrieve using getState
  • can dispatch actions
  • let user subscribing by giving a Consumer<Action>
  • can be used by subscribers to stop being notified when they’re done

Reducers

Reducers are Function2<Action, State, State> which means a function taking an Action and the current State, do some business and returns a State.

We’ll define a Reducer interface in order to simplify the code.

Reducer.java

import javaslang.Function2;

@FunctionalInterface
public interface Reducer<Action, State> extends Function2<Action, State, State> {
}

A store will take a reducer function that will be called on each action dispatching, to compute the next state.

Middlewares

In short, a middleware is a custom extension point between the dispatching of an action and the call to the reducer. Besides, middlewares are composable.

Middlewares can be used for a lot of things, like logging for instance.

We’ll define a Middleware as a Consumer taking three parameters:

  • the store
  • the action currently dispatched
  • the next middleware in the chain of composition

Java 8 does not define a TriConsumer, so we’ll define one ourselves:

TriConsumer.java

import java.util.Objects;

@FunctionalInterface
public interface TriConsumer<T, U, V> {
    void accept(T t, U u, V v);

    default TriConsumer<T, U, V> andThen(TriConsumer<? super T, ? super U, ? super V> after) {
        Objects.requireNonNull(after);

        return (l, r, v) -> {
            accept(l, r, v);
            after.accept(l, r, v);
        };
    }
}

And then we’ll create our Middleware interface:

Middleware.java

@FunctionalInterface
public interface Middleware<State, Action>
    extends TriConsumer<ReduxStore<State, Action>, Action, Middleware<State,Action>> {
}

Store implementation

Our Store implementation will implement the previously explained interface and will have a current state, a reducer, a list of subscribers and a stack of middlewares.

State currentState;
final Reducer<Action, State> reducer;
final Map<UUID, Consumer<State>> consumers = new HashMap<>();
final Consumer<Action> middlewareStack;

The constructor will take an initial state, a reducer and a variable number of middlewares. These middleware will be folded into one middleware dispatching calls to the inner middleware until there’s no more middleware to call and we’ll then call the internalDispatch consumer.

private Store(State initialState, Reducer<Action, State> reducer, Middleware<State, Action>... middlewares) {
    this.currentState = initialState;
    this.reducer = reducer;

    if (middlewares == null || middlewares.length == 0) {
        middlewareStack = null;
    } else {
        middlewareStack = (action) -> List.of(middlewares)
            .fold((s1, a1, m) -> internalDispatch(action),
                (m1, m2) -> (c, d, e) -> m2.accept(this, action, m1)
            )
            .accept(this, action, null);
    }
}

The getState simply returns the current state:

public State getState() {
    return currentState;
}

Dispatching an action will depending on if there’s any registered middlewares call them, otherwise it will call the internalDispatch methods which does the reducing and notifying stuff.

public void dispatch(Action action) {
    if (middlewareStack == null) {
        this.internalDispatch(action);
    } else {
        middlewareStack.accept(action);
    }
}

public void internalDispatch(Action action) {
    reduce(action);
    notifySubscribers();
}

The reducing stuff will just apply the function with both the current action and state, like defined in the Reducer interface.

private void reduce(Action action) {
    this.currentState = reducer.apply(action, this.currentState);
}

Notifying and subscribing is also easy to implement, there’s nothing complicated, we let the user unsubscribe directly by giving either the UUID which was provided on subscribing, or the Consumer directly.

private void notifySubscribers() {
    consumers.values().parallelStream()
        .forEach(subscriber -> subscriber.accept(currentState));
}

public UUID subscribe(Consumer<State> subscriber) {
    final UUID uuid = UUID.randomUUID();
    this.consumers.put(uuid, subscriber);
    return uuid;
}

public void unsubscribe(Consumer<State> subscriber) {
    this.consumers.entrySet().stream()
        .filter(e -> e.getValue().equals(subscriber)).findFirst()
        .ifPresent(e -> this.consumers.remove(e.getKey()));
}

public void unsubscribe(UUID subscriberId) {
    this.consumers.remove(subscriberId);
}

As you’ve surely seen, the constructor of Store is private, we’ll create a simple Redux class with static methods to bootstrap the whole thing.

It will contains a createStore method and also a combineReducers one. This combineReducers as the name indicates is a reducer that combine multiple reducers, each reducer having it’s own part of the store state.

We will support both Map<String, Object> and Java beans (any custom object with getters/setters).

import javaslang.Tuple2;
import org.apache.commons.beanutils.PropertyUtilsBean;
import java.util.Map;

public class Redux {
    private static final PropertyUtilsBean props = new PropertyUtilsBean();

    public static <State, Action> Store<State, Action> createStore(State initialState, Reducer<Action, State> reducer,
                Middleware... middlewares) {
        return Store.create(initialState, reducer, middlewares);
    }

    @SafeVarargs
    public static <State> Reducer<Object, State> combineReducers(Tuple2<String, Reducer>... reducers) {
        javaslang.collection.Map<String, Reducer> allReducers = javaslang.collection.HashMap.ofEntries(reducers);

        return (action, state) -> {
            if (state instanceof Map) {
                Map<String, Object> s = (Map<String, Object>) state;
                allReducers.forEach((a, r) -> s.put(a, r.apply(action, s.get(a))));
            } else {
                allReducers.forEach((a, r) -> {
                    try {
                        props.setProperty(state, a, r.apply(action, props.getNestedProperty(state, a)));
                    } catch (Throwable e) {}
                });
            }
            return state;
        };
    }
}

That’s it.

The Counter sample

Traditionally the first sample of redux you’ll see in every tutorial is the Counter example where a state holds an integer, and the only understood actions are INC and DEC to increment and decrement the counter.

public class Foo {
    static final String INC = "INC";
    static final String DEC = "DEC";

    // this is our reducer which increments if INC, decrement if DEC
    // and does nothing otherwise
    final Reducer<String, Integer> reducer = (action, state) -> {
        return state + Match(action).of(
            Case($(INC), 1),
            Case($(DEC), -1),
            Case($(), 0)
        );
    };

    public void foo() {
        // This is our store with its initial state of zero and the reducer seen above
        Store<Integer, String> store = Redux.createStore(0, reducer);

        // dispatch an INC action
        store.dispatch(INC);
        store.getState(); // 1

        // dispatch an DEC action
        store.dispatch(DEC);
        store.getState(); // 0
    }
}

Let’s make it a Junit test so that we can test that everything is working correctly.

public class TestCounter {
    static final String INC = "INC";
    static final String DEC = "DEC";

    final Reducer<String, Integer> reducer = (action, state) -> {
        return state + Match(action).of(
            Case($(INC), 1),
            Case($(DEC), -1),
            Case($(), 0)
        );
    };

    // a consumer which increments an atomic integer
    final AtomicInteger consumerCount = new AtomicInteger(0);
    final Consumer<Integer> consumer = (state) -> consumerCount.incrementAndGet();

    // a middleware that sleeps 50ms before and after
    final AtomicLong m1Start = new AtomicLong(0);
    final AtomicLong m1End = new AtomicLong(0);
    final Middleware<Integer, String> m1 = (store, action, next) -> {
        m1Start.set(System.currentTimeMillis());
        sleep(50);
        next.accept(store, action, null);
        sleep(50);
        m1End.set(System.currentTimeMillis());
    };

    // a middleware that just store the start and end time
    final AtomicLong m2Start = new AtomicLong(0);
    final AtomicLong m2End = new AtomicLong(0);
    final Middleware<Integer, String> m2 = (store, action, next) -> {
        m2Start.set(System.currentTimeMillis());
        next.accept(store, action, null);
        m2End.set(System.currentTimeMillis());
    };

    @Test
    public void testCounter() {
        Store<Integer, String> store = Redux.createStore(0, reducer);

        // initial state is still zero until we dispatch some actions
        assertThat(store.getState(), equalTo(0));

        // dispatch an INC action
        store.dispatch(INC);
        // the state is now 0 + 1 = 1
        assertThat(store.getState(), equalTo(1));
        assertMiddlewareOrder();

        // three times increment
        Stream.of(1, 2, 3).forEach(e -> store.dispatch(INC));
        // the state is now 1 (current state) + 1 + 1 + 1 = 4
        assertThat(store.getState(), equalTo(4));
        assertMiddlewareOrder();

        // five times decrement
        Stream.of(1, 2, 3, 4, 5).forEach(e -> store.dispatch(DEC));
        // the state is now 4 (current state) - 1 - 1 - 1 - 1 -1 = -1
        assertThat(store.getState(), equalTo(-1));
        assertMiddlewareOrder();

        // Subscribe the consumer whose goal is to increment an AtomicInteger
        // default AtomicInteger value is 0
        store.subscribe(consumer);
        // seven times increment the store
        Stream.of(1, 2, 3, 4, 5, 6, 7).forEach(e -> store.dispatch(INC));
        // the state is now -1 (current state) + 7*1  6
        assertThat(store.getState(), equalTo(6));
        // the consumer has been called 7 times also
        assertThat(consumerCount.intValue(), equalTo(7));
        assertMiddlewareOrder();

        // unsubscribe the consumer
        store.unsubscribe(consumer);
        // three times decrement
        Stream.of(1, 2, 3).forEach(e -> store.dispatch(DEC));
        // the state is now 6 (current state) - 1 - 1 - 1 = 3
        assertThat(store.getState(), equalTo(3));
        // but since the consumer has un-subscribed, the AtomicInteger stays unchanged
        assertThat(consumerCount.intValue(), equalTo(7));
        assertMiddlewareOrder();
    }

    private void assertMiddlewareOrder() {
        assertTrue(m1Start.longValue() < m2Start.longValue());
        assertTrue(m1End.longValue() > m2End.longValue());
    }

    private void sleep(long m) {
        try { Thread.sleep(m); } catch (Throwable e) {}
    }
}

In this example we’ve seen action dispatching, subscribers and middlewares. That’s all there is to it.

You can find the whole code on my GitHub agrison/redux4j, with additional tests (TodoApp) and support for subscribers in form of traditional Java Observer, and some read/write locking on the getState and reduce bits.

As usual feel free to give your opinion and/or improve the beast.

It was a fun experiment.

Until next time!

Alexandre Grison - //grison.me - @algrison