Creating an Angular application end-2-end – Part 3

Introduction

In the last two posts of this series which you can find here and here I started to implement the server side of a complete Angular JS application that uses CQRS and Event Sourcing as architectural patterns and stores the events created by the aggregates in the domain in GetEventStore. All this runs on my Ubuntu 14.10 Linux VM which is hosted in Hyper-V on my computer.

After we have successfully implemented the code that handles an incoming command, dispatches this command to the domain aggregate and stores the event(s) created by the aggregate in GetEventStore (GES), it is now time to start implementing the code necessary to build a read model from the events stored in GES. Our first read model will use MongoDB as the data store.

On a side note

Due to the fact that I changed my job and am now with Clear Measure my blog got a bit neglected. I had to first digest the many changes that awaited me there. Clear Measure is an awesome company and… we are hiring! If you are passionate about software development and are eager to work with great people then please apply.

The Read Model

We want to start with a very simple read model. Our read model should consist of a collection of all recipes. Each recipe is represented by the following view

using Recipes.Contracts;

namespace Recipes.ReadModel
{
    public class RecipeView
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public string UserName { get; set; }
        public RecipeStatus Status { get; set; }
    }
}

For the moment we just want to store the id, name, category and status of the recipe as well as the name of the user who authored the recipe. We now want to define an observer class that listens to incoming recipe events and uses them to create a read model. For each type of event that the observer handles we implement a When method which has the specific event as (single) parameter. If we have the two events RecipeCreated and RecipeSubmitted then the class (without implementation) looks like this

using Recipes.Contracts;

namespace Recipes.ReadModel
{
    public class RecipeObserver
    {
        public void When(RecipeCreated e)
        {
            //TODO
        }

        public void When(RecipeSubmitted e)
        {
            //TODO
        }
    }
}

To write the data to the underlying data store (MongoDB in this case) the observer uses a writer which implements this interface

using System;

namespace Recipes.ReadModel
{
    public interface IProjectionWriter<T> where T: class
    {
        void Add(int id, T item);
        void Update(int id, Action<T> update);
    }
}

The interface has only two members, one to add a new item to the underlying data collection and the other to update an existing item in the collection. The writer is injected to the observer through the constructor

using Recipes.Contracts;

namespace Recipes.ReadModel
{
    public class RecipeObserver
    {
        private readonly IProjectionWriter<RecipeView> writer;

        public RecipeObserver(IProjectionWriter<RecipeView> writer)
        {
            this.writer = writer;
        }

        public void When(RecipeCreated e)
        {
            //TODO
        }

        public void When(RecipeSubmitted e)
        {
            //TODO
        }
    }
}

We can now implement the code needed to add a new recipe when handling the RecipeCreated event and to update an existing recipe when handling the RecipeSubmitted event using the injected writer

using Recipes.Contracts;

namespace Recipes.ReadModel
{
    public class RecipeObserver
    {
        private readonly IProjectionWriter<RecipeView> writer;

        public RecipeObserver(IProjectionWriter<RecipeView> writer)
        {
            this.writer = writer;
        }

        public void When(RecipeCreated e)
        {
            writer.Add(e.Id, new RecipeView
            {
                Id = e.Id,
                Name = e.Name,
                Category = e.Category,
                UserName = e.UserName,
                Status = RecipeStatus.Draft
            });
        }

        public void When(RecipeSubmitted e)
        {
            writer.Update(e.Id, x => x.Status = RecipeStatus.Submitted);
        }
    }
}

If we are creating our read model this way the code is really simple to implement a new or extend an existing observer. In my company we use this approach a lot and it makes our lives quite easy. In this particular case if we add another recipe related event that the observer needs to handle we just need to add another When method to the class that handles the new event. Most probably this event will be used to update the existing recipe and thus we’ll use the Update method of the writer.

Now let’s implement the IProjectionWriter<T> interface for MongoDB, shall we? To access MongoDB from ASP.NET and C# we need a driver. Luckily we can just download and install it via nuget. We add this line to the dependencies section of the project.json file

“mongocsharpdriver”: “1.9.2″

Save the file and in a terminal run kpm restore to download and install the new package.

Here is the code for our MongoDB projection writer

using System;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;

namespace Recipes.ReadModel
{
    public class MongoDbProjectionWriter<T> : IProjectionWriter<T>
        where T: class
    {
        private const string DATABASE_NAME = "Recipes";
        private readonly string connectionString;
        private static MongoCollection<T> _collection;

        public MongoDbProjectionWriter(string connectionString)
        {
            this.connectionString = connectionString;
        }

        public void Add(int id, T item)
        {
            var collection = GetCollection();
            collection.Insert(item);
        }

        public void Update(int id, Action<T> update)
        {
            var query = Query.EQ("_id", new BsonInt32(id));
            var collection = GetCollection();
            var existingItem = collection.FindOneAs<T>(query);

            if (existingItem == null)
                throw new InvalidOperationException("Item does not exists");

            update(existingItem);
            collection.Save(existingItem);
        }

        private MongoCollection<T> GetCollection()
        {
            if (_collection == null)
            {
                var client = new MongoClient(connectionString);
                var server = client.GetServer();
                var database = server.GetDatabase(DATABASE_NAME);
                _collection = database.GetCollection<T>(typeof(T).Name);
            }
            return _collection;
        }
    }
}

To access MongoDB we need a connection string. If MongoDB is installed locally we can use “mongodb://localhost”. In this case we inject the connection string to use via constructor injection.

When the Add method on line 20 is called then we need to first determine to which MongoDB collection the new item shall be added (line 22). Once we have the collection we just add the item (line 23). To get hold of the collection we have the helper method GetCollection. In the GetCollection we first create a new MongoDB client using the connection string. Through the client we get access to the server (line 44) and via the server to the database (which we conveniently call Recipes, line 45). Once we have the database we can get hold of the desired collection (line 46). This collection by our convention shall be named like the type name of the item, e.g. RecipeView.

The Update method is only slightly more complicated. Since this is an update we assume that there must already exist an item in the collection that has the given Id as primary key (line 28 through 30). If we do not find such an item then we have a fatal error and we throw an exception. Otherwise we use the update action passed to the method to update the existing item (line 35) and save the updated item back to the collection in MongoDB (line 36).

That’s all we need for the moment. We can now try to test this code. For this we can e.g. implement a simple Test controller that when called creates a new instance of the MongoDB projection writer and uses it to add an item to the read model and also update this item thereafter. The code we use looks like this

using Microsoft.AspNet.Mvc;
using Recipes.Contracts;
using Recipes.ReadModel;

namespace Recipes.Controllers
{
    [Route("api/test")]
    public class TestController : Controller
    {
    	[Route("test")]
    	[HttpPost]
    	public void Test()
    	{
    		System.Console.WriteLine("HTTP POST to Test received...");
    		var sut = new MongoDbProjectionWriter<RecipeView>("mongodb://localhost");

    		sut.Add(1, new RecipeView{
    			Id = 1, 
    			Name = "Penne all arrabiata",
    			Category = "Italian",
    			UserName = "jdoe@foo.com",
    			Status = RecipeStatus.Draft
    		});

    		sut.Update(1, x => x.Status = RecipeStatus.Submitted);
    	}
    }
}

On line 15 we create a new instance of the MongoDB projection writer passing in the connection string for our locally installed MongoDB server. On line 17 we first create a new recipe and on line 25 we try to update the just created recipe.

We can now compile our code (kpm build) and if ok run the webserver (k kestrel). We also need to make sure MongoDB runs (see here for more details). We can then use the Postman plugin for Chrome to send a post request to the test controller just implemented above. Create a POST request to the URI http://localhost:5004/api/test/test. We should see the following in the terminal

image

And if we use e.g. Robomongo to access MongoDB and analyze the outcome we should see this

image

Clearly our code has created a new collection RecipeView in a database Recipes and we also see that in this collection there is the expected recipe with all its properties.

Summary

In this post we have implemented the infrastructure need to generate a read model from our events. The read model in this particular case is stored in MongoDB. Creating a read model for other undelying data stores is simple. Samples are Lucene.Net, the file system or a relational database. We only need to create a storage specific implementation of the interface IProjectionWriter<T> and we’re done.

About Gabriel Schenker

Gabriel N. Schenker started his career as a physicist. Following his passion and interest in stars and the universe he chose to write his Ph.D. thesis in astrophysics. Soon after this he dedicated all his time to his second passion, writing and architecting software. Gabriel has since been working for over 25 years as a consultant, software architect, trainer, and mentor mainly on the .NET platform. He is currently working as senior software architect at Alien Vault in Austin, Texas. Gabriel is passionate about software development and tries to make the life of developers easier by providing guidelines and frameworks to reduce friction in the software development process. Gabriel is married and father of four children and during his spare time likes hiking in the mountains, cooking and reading.
This entry was posted in Event sourcing, GetEventStore, How To, introduction, MongoDB, Read model, Ubuntu. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • jdan

    You are missing some code:
    “To write the data to the underlying data store (MongoDB in this case) the observer uses a writer which implements this interface
    Could not embed GitHub Gist 097604e46a0c03e9b27: Not Found”

    • gabrielschenker

      Thanks for notifying me. Fixed.