Thursday, 30 April 2015

Using the Model-Store pattern in Inversion (part 2)

This post is the second in a series about using a Model-Store pattern in the Inversion behavioural micro-framework for .Net (see: https://github.com/guy-murphy/inversion-dev).

The first part can be found here.

Introduction


In the previous post in this series, I talked about the Model part of the Model-Store pattern; basically POCO (plain old C# objects) with a little extra infrastructure added to give them immutability (i.e. if a value changes then they become a different object with a different reference) and also decoration with Inversion's IData interface, which lets the object be more useful within the Inversion ecosystem.

In this post, I'm going to talk about the IStore interface and how it can be implemented to be a lightweight, efficient data store that uses immutable models.

The code for this post is not contained in the standard Inversion framework repository. Instead, I have created a new repository that contains the Inversion.Data namespace here: https://github.com/fractos/inversion-data

Stores


A Store provides data access for a specific type of storage medium. This could be a database, a filesystem, or anything that you can perform basic CRUD operations upon.

It is generally tied to a particular type of Model, or a limited set of Models that could be described as being within a Bounded Context. This is in order to avoid duplication of access to the Model objects in storage. If a set of Models exist within a Bounded Context then they are not accessed by the rest of a system and so therefore access duplication should not occur. In other situations it is probably best to define a Store that handles as few Model types as possible so that the granularity of configuration could be on a per-Model basis.

In the example from part one of this series, a Store that is implemented for User objects would also cover UserMetadata objects since the UserMetadata class could be a private class within the User class's domain.

Tying a Store to a Model type has connotations. It is advised that the most suitable way of modelling with this pattern is to use simple objects that do not maintain a web of active references to other types of object and which do not have the expectation of these references to be kept up to date automatically by the actions of a DAL. This won't suit everyone's architectural styles.

The Model should only represent the data. The Store should only deal with the translation of that data in and out of a storage medium.

Now I think it's worth talking briefly about what a Store is not.

A Store is NOT a Repository


To reiterate, a Store uses immutable model objects. A Repository keeps track of changes, but a Store does not because immutable objects become a different object if their properties are changed. Changes in the underlying storage medium must be made explicitly by putting a data object back into the Store. Model objects do not have to track changes automatically so they can be primitive classes with no dependency upon libraries such as Entity Framework.

The IStore Interface


This is the basic interface that all Store classes must implement. It covers the following two areas of concern, those being that a Store is a resource and that a Store has state.




Stores are a resource


A Store is something that an application should be able to dip into; a lightweight component that is used only for data access. Once the data access is over, you should be able to throw away the store.

For this reason a Store implements the IDisposable interface. A Store may not have anything to dispose at the end of its use, but this enforces that there is the chance to perform actions at that point.

Stores have state


A Store should guard against misuse. Certain operations may only be valid when the Store is in a particular state. A primary use-case for this is being able to adjust the properties of a connection in a database store before the connection has been fully opened. The general solution for this is to implement a state property that defines whether the Store has been started or stopped explicitly.

For this reason, a Store implements methods that adjust its state and any data access methods should check the current state when they are called, raising an exception if the Store is in an unsuitable state.

The Store state is represented by a simple enumeration - StoreState:




A Store which has never been started will have a state equal to StoreState.Unstarted. When started explicitly, a Store will have a state equal to StoreState.Started. When stopped explicitly, a Store will have a state equal to StoreState.Stopped.

The IStore interface defines two methods for explicitly changing the state of the Store - Start and Stop:

    void Start();
    void Stop();

The IStore interface defines a public get property HasStarted, which should report whether the Store has had Start called upon it and has not yet had Stop called upon it:

    bool HasStarted { get; }

The IStore interface also defines another public get property HasStopped, which should report whether the Store has had Stop called upon it:

    bool HasStopped { get; }

The StoreBase abstract base class


This abstract class provides a base upon which to build other implementations of a Store, as we will see below.



The private property _state keeps track of whether the Store has ever been started, or is currently started or stopped. The initial value is StoreState.Unstarted. Only methods in the base class can adjust the _state property since it is private. Overriding methods for Start (and Stop) must call their base implementations as part of their instructions.

The public property HasStarted can report whether the Store is in a state where it can perform operations, i.e. whether the _state property is equal to StoreState.Started.

The public property HasStopped can report whether the Store is in a stopped state, i.e. whether the _state property is equal to StoreState.Stopped.

The public method Start is used to explicitly start any resources that the Store implementation needs. If the Store is already started, then an exception is raised.

The public method Stop is used to explicitly stop any resources that the Store implementation needs. This should be called by a descendant implementation of Dispose, and since both Stop or Dispose could be called explicitly (by a call from user code) or implicitly (when leaving a using statement block), then the Stop method does not try to raise an exception if the Store is in an incompatible state. Instead, it changes the state to Stopped if the Store is currently started. Generally, it is useful to keep a flag that records whether Dispose has been called already, and if the flag is set then no action is taken. This protects against the situation of a call to Stop being followed by an implicit call to Dispose. However, this may not be necessary if a Store has nothing to do during its disposal phase.

It is the descendant implementation's responsibility to have the correct behaviour within its overridden instructions for Stop and Dispose, and this is especially important for database connection types which must have their connection objects disposed (e.g. SqlClient). For this reason, Dispose is marked as abstract so that the descendant is forced to provide some form of implementation.

I have also added a simple method for asserting whether the Store has been started called AssertIsStarted. This will be put to a lot of use in our specialised Store.

StoreStartedException is a simple wrapper for ApplicationException. It is used to emit an exception from the Store when trying to start it if it is already in a started state:



Similarly there is a StoreStoppedException which should be emitted from a data access method when the Store is found to be in a stopped state.

Also, StoreProcessException - an instance or descendant of which should be emitted from a Store method if something goes wrong during processing a call, although that's not a hard and fast rule, just a useful convention.

I've omitted the source code for these as they are exactly the same as StoreStartedException apart from the name, but you can find it in the GitHub repository for Inversion.Data. The differentiation between them is just an exercise in taxonomy.

Now we are ready to see how a Store can be implemented for a particular storage technology, and then show how it can be used.

Implementing a Store for MongoDB


You probably already know this, but MongoDB by 10Gen is a cross-platform, "NoSQL" style document database which is used by many projects and applications across the globe. I use it extensively for most of my side-projects as a primary storage mechanism. My experience with it has led me to appreciate a horses-for-courses approach to databases - MongoDB can be a very quick leg-up to a working system and in production it is very stable and well-behaved, clustering notwithstanding. If you're looking to get into document databases then it's a really good starting place.

It's pretty straightforward to set up on Windows or Linux. Check out its home site https://www.mongodb.org/ and also the very good admin UI Windows application MongoVue at http://www.mongovue.com/

A word about namespaces


The projects and assemblies that I am writing as part of this tutorial are arranged in a particular way. Essentially - Store, IStore, StoreState and the Exception classes are sitting in a core assembly called Inversion.Data. But my specialised classes, i.e. the Store and support classes that I am about to make live in a project called Inversion.Data.MongoDB. This has a default namespace of Inversion.Data and a Store folder within it, which means that by referencing the assembly Inversion.Data.MongoDB the Inversion.Data namespace is augmented with the contents of the root of the project, in this case that means the MongoDBStore class. The Store has the technology name used as a prefix to its class name to differentiate it within the namespace.

This convention is in place because we (Guy Murphy and I) have found that it aids a composition-by-augmentation approach; if you include an assembly with a particular technology stack, then it will appear within a well known namespace. No special knowledge of namespaces that are tied to a technology is required and, frankly, it just feels neater.

The base MongoDB implementation - MongoDBStore.cs


To use MongoDB, I start by adding the NuGet package "MongoDB.Driver". That package loads a couple of dependencies - MongoDB.Bson and MongoDB.Core.

The code for the MongoDBStore class is as follows:



This gives us some very basic functionality that can be used to build our specialised Store for a particular Model type.

The constructor for MongoDBStore requires the passing of both a connection string argument and a database name. I've made the base store centric around the named database in MongoDB because this allows basic functionality with collections that would be common across Stores that might want to access more than one collection within a database. Frankly, it's as good a place as any to put this level of organisation. If you feel that you want to change that around then go for it. The connection string and database name are stored in read-only properties which are private as they are unlikely to be of use to anything outside of the base implementation. Connection strings and database names are things that shouldn't leak outside of the Store's abstraction as they are a configuration concern.

Start has been overridden, taking the opportunity to create a MongoClient instance from the connection string and then use that client to gain an IMongoDatabase reference to the named database. The base Start method is called in order to correctly set the state of the Store. The result is that we now have an active MongoClient instance and a stored reference to the required database that is ready to go.

Stop has not been overridden as there is no action to take when stopping.

Similarly, Dispose has no actual work to do so it simply calls Stop on the base Store.

This basic Store has its index property overridden which allows access by name to a MongoDB collection within a database. The get-accessor performs a quick state check of the Store to determine if it has been started (and thus, whether it is safe to use the Database property), and then returns an IMongoCollection generic of type BsonDocument (objects that represent a piece of BSON data, which is a binary-serialised version of JSON peculiar to MongoDB).

Some OO-purists might want to mark this base Store as abstract, such that it cannot itself be instantiated and must be inherited from before it can perform any work. However, this is a fully working Store providing a basic mechanism for retrieving documents from a named collection, therefore I have not marked it as abstract as it could be put to immediate use as-is.

Next time ...


In the next part of this article, I will go through the specialised Store for our User object - MongoDBUserStore.cs.

Update:
Had to change name of Store to StoreBase to avoid a namespace conflict. Gists updated to reflect this.

No comments:

Post a Comment