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.
IReduxMessage is just a bare marker for message implementations, allowing them to be easily identified.
And the interface
IReduxReducer<T> just defines the simple signature required to combine a message and the existing state when creating the new state.
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.
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.
For the moment, the key piece of actual functionality is in the
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.
What we’ve done here is to create a guardrail that will stop us going off track if we make a mistake.
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.