At the core of the Redux design pattern is the store, a central place for storing the current state of the application. There’s only ever one store for the entire application. Changes in state are made by dispatching messages to the store that are processed by a reducer.

For a first cut at defining the store, we first need to define some interfaces that represent other parts of the system.

The interface IReduxMessage is just a bare marker for message implementations, allowing them to be easily identified.

public interface IReduxMessage
{
}

And the interface IReduxReducer<T> just defines the simple signature required to combine a message and the existing state when creating the new state.

public interface IReduxReducer<T>
{
    T Reduce(IReduxMessage message, T currentState);
}

Now we can look at the implementation of the store itself. At this point of our project, the store will be quite simple, but as we progress we will be enhancing it considerably.

Our definition begins with a reference to the reducer used by this store and a property allowing read/only access to our current state.

public class ReduxStore<T>
{
    private readonly IReduxReducer<T> _reducer;

    public T State { get; private set; }

Why only reference a single reducer?

The glib answer is that we don’t want to violate the single responsibility principle by giving the store too many responsibilities. Having the store manage the policy for how reducers combine would introduce significant complexity.

More importantly, there’s a useful principle from the land of functional programming that I’ve applied here - that a system gains expressive power when you can treat many of a thing in the same way as one of them. We’ll see how to combine different kinds of reducers later on.

Both of our members are initialised by our constructor in the usual fashion.

    public ReduxStore(IReduxReducer<T> reducer, T initialState)
    {
        _reducer = reducer 
            ?? throw new ArgumentNullException(nameof(reducer));
        State = initialState;
    }

For the moment, the key piece of actual functionality is in the Dispatch() method:

    public void Dispatch(IReduxMessage message)
    {
        State = _reducer.Reduce(
            message ?? throw new ArgumentNullException(nameof(message)),
            State);
    }
}

Reducers are supposed to be very quick, and they’re not supposed to interact outside of our process (say, by making a call to a webservice or running a database query), so the implementation of Dispatch() hasn’t been declared as async.

But we do have a problem … if one of our reducers calls Dispatch(), things could get messy. While it is possible to do this correctly, it’s not recommended practice, and there are better ways to achieve the same result. Let’s modify the implementation to throw an exception if we try to dispatch a second message while we are still processing the first.

private bool _dispatching;

public void Dispatch(IReduxMessage message)
{
    if (_dispatching)
    {
        // TOCONSIDER: If this exception becomes a problem, 
        // introduce a queue to serialize message processing instead.
        throw new InvalidOperationException(
            "Calling Dispatch() while processing Dispatch() is not permitted.");
    }

    _dispatching = true;
    try
    {
        State = _reducer.Reduce(
            message ?? throw new ArgumentNullException(nameof(message)),
            State);
    }
    finally
    {
        _dispatching = false;
    }
}

What we’ve done here is to create a guardrail that will stop us going off track if we make a mistake.

The TOCONSIDER comment reminds us that if we later decide we want to make this permitted, we can introduce a queue to capture messages and process them serially and predictably. Since we don’t know we need that, that YAGNI principle applies and this guardrail keeps things safe.

Prior post in this series:
Commandline Builds
Next post in this series:
Static Analysis

Comments

blog comments powered by Disqus