There Is No State Only Events

I had got to the next feature, “Get an existing basket”, this was going to be a simple save the basket on creation, load it by id, return 404 if not found type of blog post, I even had the code written to store the basket state in a file using the id as the file name!

One of the joys of blogging and writing code for fun, is that you can change your mind, and learn new things along the way, the book club where I work has just finished the book

DDD BookWhich got me thinking about my little basket API and how it could be used to investigate the whole idea of state and events.  At work we have just started to use events (we call them messages) in Amazon Web Services to trigger off tasks like sending welcome emails. 

The book takes domain aggregates and the event store to the next level instead of saving object state and losing the meaning of the action, events are saved and the object (Aggregate) is rebuilt by playing these events back.  This has the advantage of being very saleable and because each event is saved the object can be rebuilt to any state in its history which can be a very useful tool in debugging applications.  It is also thought that because the event store is append only the speed of an application using this technology can be faster and support a greater number of transactions than a traditional application using state storage.

So lets have a quick look at the context the basket Aggregate, and what I am currently expecting it to interact with.

BasketBoundedContext

The basket bounded context contains our basket aggregate and a product cache that is maintained using events from the product aggregate.  The basket will raise events that are consumed by the order service to maintain its own cache.

So what events will the basket be raising, currently I can only think of three

  • Basket Created: contains the basket id
  • Product added to basket: contains the product id, product description and product price
  • Product removed from basket: contains the product id

All the events raised will be stored in an events store when we want to get the baskets state we can reload the events replay them and the resulting aggregate will be complete.

I am expecting an order service to be listening for basket messages so that when an order is placed it can continue its processing form its own basket cache, in a similar way to the basket context listens to all product messages to maintain its own product cache.  That’s the theory anyway.

Ok great so that’s event sourcing in a nutshell, but what does it mean for our basket code, well to keep it simple I am not going to do the publish event bit yet, so I will simply save events to an event store. and use these events to re-create the objects state. so first we need to add to the basket create method, so that it will store a basket created event.

Notice we don’t need any new tests for this, our tests covers the creation of the basket from the outside, how we do that is of no interest to the outside world, so we can simply add code here and we know its covered.

public static Basket CreateWithId()
{
  string id = GenerateBasketId();

  // create event
  var basketEvent = new CreateBasketEvent {Id = id};

  // store event in basket event store
  var filename = string.Format("./EventStore/Basket/{0}", id);
  var sterilizer = new XmlSerializer(typeof (List<CreateBasketEvent>));
  var writer = new StreamWriter(filename);
  sterilizer.Serialize(writer,new List<CreateBasketEvent>{basketEvent});
  writer.Close();

  // return new basket
  return new Basket { Id = id };
}

The even class itself is simple a dumb dto with a property id

namespace CheckoutBasket.API.Events
{
    public class CreateBasketEvent
    {
        public string Id { get; set; } 
    }
}

In the get method all we will have to do now is load the events and replay them through the basket to re-create its state.

But first we need a failing test for getting the basket.

public class WhenGettingAnExistingBasket : BddBase
{
   private Browser _browser;
   private Basket _basket;
   private string _basketId;

   protected override void Given()
   {
       var bootstrapper = new DefaultNancyBootstrapper();
       _browser = new Browser(bootstrapper);

       var response = _browser.Get("/basket");

       var basket = response.Body.DeserializeJson<Basket>();
       _basketId = basket.Id;
   }

   protected override void When()
   {
       var response = _browser.Get(string.Format("/basket/{0}", _basketId));

       _basket = response.Body.DeserializeJson<Basket>();
   }

   [Test]
   public void The_returned_basket_id_is_the_expected_basket_id()
   {
       _basket.Id.ShouldBe(_basketId);
   }
}

This test simply creates a basket then attempts to get that basket from the application, of cause at the moment it fails because our router does not know what to do with a basket request with an id attached, so lets fix that.

public class BasketHander : NancyModule
{
   public BasketHander()
   {
       Get["/basket/{id?}"] = _ =>
       {
           var basket = Basket.CreateWithId();

           return Response.AsJson(basket);
       };
   }
}

Here I have simply added the “{id?}” to the end of the root for get, this means the id is an optional part of the route, now our tests fails because we are getting a new basket each time with a different id to the one supplied, as our service only knows how to create baskets.

    public class BasketHander : NancyModule
    {
        public BasketHander()
        {
            Get["/basket/{id?}"] = _ =>
            {
                Basket basket;
                
                if (_.id == null)
                    basket = Basket.CreateWithId();
                else
                    basket = Basket.GetWithId(_.id);
                
                return Response.AsJson(basket);
            };
        }
    }

Here we are checking for the existence of the id, and either creating a new basket if the id is null or attempting to get the basket if the id is not null, we are still not doing any error checking but this is still the first pass and early stages, lets have a look at the GetWithId method.

public static Basket GetWithId(string basketId)
{
  var filename = string.Format("./EventStore/Basket/{0}", basketId);
  var sterilizer = new XmlSerializer(typeof (List<CreateBasketEvent>));
  var reader = new StreamReader(filename);

  var events = (List<CreateBasketEvent>) sterilizer.Deserialize(reader);
  reader.Close();

  return new Basket(){Id = events[0].Id};
}

Here again I have gone for the simplest thing that can possibly work, we are only working with one event at the moment so its a case of getting the file, desterilising the list of events and creating a new basket object and giving it the correct id.

We now have passing tests again.  The code is very simple but we need to be able to handle more than one type of event, we currently know of at lest three so lets refactor the code a bit to move us in that direction.

First up lets give the CreateBasketEvent class a base class which all future events can be derived from called ApplicationEvent our serializer can then be standardised to use this base class, it turns out that in order for the serialization to work we need to decorate the base class with an attribute so it knows what has inherited from it.

[XmlInclude(typeof(CreateBasketEvent))]
public class ApplicationEvent
{
}

public class CreateBasketEvent : ApplicationEvent
{
    public string Id { get; set; } 
}

Now we can generalize the serialization and deserialization so that it can in theory cope with any sort of event based on the ApplicationEvent.

public static Basket CreateWithId()
{
  string id = GenerateBasketId();

  var basketEvent = new CreateBasketEvent {Id = id};

  var filename = string.Format("./EventStore/Basket/{0}", id);
  var sterilizer = new XmlSerializer(typeof (List<ApplicationEvent>));
  var writer = new StreamWriter(filename);
  sterilizer.Serialize(writer,new List<ApplicationEvent>{basketEvent});
  writer.Close();

  return new Basket { Id = id };
}

public static Basket GetWithId(string basketId)
{
  var filename = string.Format("./EventStore/Basket/{0}", basketId);
  var sterilizer = new XmlSerializer(typeof (List<ApplicationEvent>));
  var reader = new StreamReader(filename);
  var events = (List<ApplicationEvent>) sterilizer.Deserialize(reader);
  reader.Close();

  return new Basket {Id = ((CreateBasketEvent)events[0]).Id};
}

Now we need to extract all the serialization into some sort of event store class this will have two methods. The first will be to save an event to the store, the second will be to get a list of events from the store, the code is refactored from the basket create and get methods.

public interface IEventStore
{
   void Store(ApplicationEvent applicationEvent, string path);

   IList<ApplicationEvent> Retrieve(string path);
}

public class EventStore : IEventStore
{
   public void Store(ApplicationEvent applicationEvent, string path)
   {
       var sterilizer = new XmlSerializer(typeof(List<ApplicationEvent>));
       var writer = new StreamWriter(path);
       sterilizer.Serialize(writer, new List<ApplicationEvent> { applicationEvent });
       writer.Close();

   }

   public IList<ApplicationEvent> Retrieve(string path)
   {
       var sterilizer = new XmlSerializer(typeof(List<ApplicationEvent>));
       var reader = new StreamReader(path);
       var events = (List<ApplicationEvent>)sterilizer.Deserialize(reader);
       reader.Close();

       return events;
   }
}

Notice we haven’t added any tests, we are simply at the moment moving code around, the use of tests written with the behaviour in mind and reducing the use of mocks allow us to refactor code, this is still a very naïve implementation, there is no concurrency we are not posting events and there is still a lot of work to do, but by not using mocks until the tests become too slow and only then mocking the boundaries of the application we will be able to implement refactoring’s changing the structure of the application as required.

But before we refactor more lets get the next test written, that is for a basket that does not exist.

public class WhenGettingANonExistingBasket : BddBase
{
   private string _basketId;
   private Browser _browser;
   private BrowserResponse _response;

   protected override void Given()
   {
       var bootstrapper = new DefaultNancyBootstrapper();
       _browser = new Browser(bootstrapper);

       _basketId = Guid.NewGuid().ToString().Replace("-", ""); 
   }

   protected override void When()
   {
       _response = _browser.Get(string.Format("/basket/{0}", _basketId));
   }

   [Test]
   public void then_the_response_should_be_404_not_found()
   {
       _response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
   }
}

This test of cause fails nicely with an internal server error, because the stream reader in the event store cannot find the file with the events, so we need to handle this first, easiest solution is to check for the file, and return null if not from the event store.

public IList<ApplicationEvent> Retrieve(string path)
{
  if (! File.Exists(path))
      return null;

  var sterilizer = new XmlSerializer(typeof(List<ApplicationEvent>));
  var reader = new StreamReader(path);
  var events = (List<ApplicationEvent>)sterilizer.Deserialize(reader);
  reader.Close();

  return events;
}

Now we still get an internal server error but its in the basket because we are trying to access the first event which at this point we know should be a create, but we have no events.  This we can handle by checking that we have events and returning null if we do not.

public static Basket GetWithId(string basketId)
{
  var filename = string.Format("./EventStore/Basket/{0}", basketId);
  var eventstore = new EventStore();
  var events = eventstore.Retrieve(filename);

  if (events == null)
      return null;

  return new Basket {Id = ((CreateBasketEvent)events[0]).Id};
}

So now we are getting an http.ok status back from the method, this is because our handler does not check what it gets back from the basket call, checking this for null and returning the correct response is the last step to make the test pass.

public class BasketHander : NancyModule
{
   public BasketHander()
   {
       Get["/basket/{id?}"] = _ =>
       {
           Basket basket;
           
           if (_.id == null)
               basket = Basket.CreateWithId();
           else
               basket = Basket.GetWithId(_.id);

           if (basket == null)
               return HttpStatusCode.NotFound;

           return Response.AsJson(basket);
       };
   }
}

And now the test passes.  Although the code at this point is not great, there is still a lot we could do to improve the basket and event store,  we do have the basics in place next time we can look at adding a product to the basket, which will force us to look at the event store, think about multiple event types and how we rebuild the aggregate from events.

Advertisements

About Duncan Butler

Trying to be a very agile software developer, working in C# with Specflow, Nunit and Machine Specifications, and in the evening having fun with Ruby and Rails
This entry was posted in Programming and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s