Updating my sample Event Sourcing application 4 years later

Some time ago I gave an Introduction to Event Sourcing and CQRS talk. It's still available on YouTube. Along side it I also wrote a simple patient management application to demonstrate some of the points. Well it's been 4 years now and I figured it was probably time to update it.

I'm not going to be explaining any of the Event Sourcing concepts in these posts so if you want know what I'm talking about, go watch the talk

I want move it to the latest frameworks/libraries and take the chance to change some of the parts that I'm not happy with. I also want to use it as an excuse to play around with EventStore Cloud (full disclosure: I work for EventStore, but I'll try to be unbiased in my opinions).

So lets see what we've got...

I pulled the project from Github and the first thing I've noticed is, damn, those project files are ugly!

Do you remember these things? They used to be an absolute nightmare.

After recovering from this, the solution is made up four projects:

  • Explorer: A simple web application to explore the projections.
  • PatientManagement: Contains the core domain.
  • SeedGenerator: For generating some fake data.
  • TestConsole: For running some sample commands.

And was all built using:

  • .NET 4.5.2
  • NancyFX 1.4
  • RavenDB 3.5.2
  • EventStoreDB 3.9

What's the plan?

Well I'm going to start by bumping all the dependencies. That means moving to .NET 5.0, which should bring with it some nice features. I'm going to swap out RavenDB for MongoDB and upgrade EventStoreDB to v20.10 (It hasn't jumped 17 versions, we've had a change in our versioning strategy). Then finally and unfortunately, going to have to remove the depedency on NancyFX as the product is no longer in development :(. I'm not sure what to use now but I'm considering Carter.

Because it's going to be a bit more of an undertaking, I'm just going to ignore the explorer project for now.

When I first gave this talk I was using a pre release of Jetbrains Rider. It's now on 20.3 and has long since replaced my use of Visual Studio. Infact I can't remember the last time I opened it.

Moving to .NET 5

This proved suprisingly simple. I just deleted the contents of each of the .csproj files and replace them with the following (or similar):

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="RavenDB.Client" Version="3.5.2" />
        <PackageReference Include="EventStore.Client" Version="3.9.0" />
    </ItemGroup>

    <ItemGroup>
        <ProjectReference Include="..\PatientManagement\PatientManagement.csproj" />
    </ItemGroup>

</Project>

This will now allow me take advantage of some of the new langauge features such as method expressions and more importantly records. But I'll be looking at this later. First lets move everything over to MongoDB.

Switching from RavenDB to MongoDB

In the original solution I used RavenDB to persist my projections:

 When<PatientAdmitted>(e =>
 {
     using (var session = connectionFactory.Connect())
     {
         session.Store(new Patient
         {
             Id = e.PatientId,
             WardNumber = e.WardNumber,
             PatientName = e.PatientName,
             AgeInYears = e.AgeInYears
         });

         session.SaveChanges();
     }

     Console.WriteLine($"Recording Patient Admission: e.PatientName}");
});

And moving it to MongoDB means it now looks like this:

var collection = database.GetCollection<Patient>("WardView");

When<PatientAdmitted>(e =>
{
    collection.InsertOne(new Patient
    {
        Id = e.PatientId,
        WardNumber = e.WardNumber,
        PatientName = e.PatientName,
        AgeInYears = e.AgeInYears
    });

    Console.WriteLine($"Recording Patient Admission: {e.PatientName}");
});

There was a couple of other changes such as setting up the connection but it was pretty simple. The syntax got a little more tricky around updates where Mongo makes use of these Builders:

When<PatientTransfered>(e =>
{
    var filter = Builders<Patient>
        .Filter.Eq(x => x.Id, e.PatientId);
    var update = Builders<Patient>
        .Update.Set(a => a.WardNumber, e.WardNumber);

    collection.UpdateOneAsync(filter, update);

    Console.WriteLine($"Recording Patient Transfer: {e.PatientId}");
});

Upgrading to EventStoreDB 20.10

This is the latest version of EventStore and was only released a short time ago. My original project used a TCP client and although there is now a gRPC client, the syntax is different, so I'm going to save that move for another blog post.

The good news on this is that the syntax hasn't changed much and I only had to change three things.

The first was to change to sliceStart type from an int to a long when retrieving events

async Task<List<object>> GetEvents(string streamName)
{
    long sliceStart = StreamPosition.Start;
    var deserializedEvents = new List<object>();
    StreamEventsSlice slice;

    do
    {
        slice = await _connection
            .ReadStreamEventsForwardAsync(
                streamName, 
                sliceStart, 
                200, 
                false);
                
        deserializedEvents
            .AddRange(slice.Events.Select(e => e.Deserialize()));
        
        sliceStart = slice.NextEventNumber;

    } while (!slice.IsEndOfStream);

    return deserializedEvents;
}
change to long on line 3

The second change was also simple. Because EventStore is moving towards gRPC as a protocol, TCP is now disabled by default so you need to turn it on using --enable-external-tcp. Or with docker:

$> docker run --name eventstore-patientmanagement -it -p 2113:2113 -p 1113:1113 EVENTSTORE_ENABLE_EXTERNAL_TCP=true eventstore/eventstore

Finally the last change was around security and this is because EventStore went from being insecure by default to secure by default. This means if you don't want to go to the effort of generating certs for development work (which I don't) you need to run EventStoreDB with the --insecure flag. Or again, with docker:

$> docker run --name eventstore-patientmanagement -it -p 2113:2113 -p 1113:1113 -e EVENTSTORE_INSECURE=true eventstore/eventstore

Then you just need to set the option to disable TLS when connecting from a client:

var connectionSettings = ConnectionSettings
    .Create()
    .DisableTls();

var eventStoreConnection = EventStoreConnection.Create(
    connectionSettings,
    new IPEndPoint(IPAddress.Loopback, 1113));

eventStoreConnection.ConnectAsync().Wait();

And that's it

So not much too it. I then ran the SeedGenerator application and it populated EventStore with some seed data:

This is a horrible way to name your streams btw. It should ideally be {type}-{identifier}. I'm going to fix that later.

Then I ran the ProjectionManager And it produced some projections for me:

Conclusion and what's next

That's it for now. It was pretty easy to upgrade only took an hour or so. You can see the differences in their entirety here. I've also included a docker-compose file too to make things a bit easier.

There's going to be a number of posts in this series. I mentioned at the start of this post there's a few things I want to do with this project:

  • Make use of some of the new .NET language features.
  • Move to the EventStore gRPC client.
  • Switch over to using EventStore Cloud to try out some of its features.
  • Correct some of the mistakes I made and change some of the things I purposefully simplified.
  • Move the explorer project over to a new web framework.

So with that in mind the next post will be about making use of the new .NET language features.