Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
I’ve known about SQL Stream Store for a bit (I believe when it was called Cedar) but haven’t really looked into it much. The premise is to provide a stream store over SQL. Currently supporting MS SQL Server, PostgreSQL, and MySQL. Meaning it is simply a .NET Library you can use with an SQL implementation. Let’s take a look at how you can implement Event Sourcing with SQL Stream Store.
SQL Stream Store Demo Application
For this demo/sample, I’m going to create a .NET Core 3 console app. The idea will be to create the stereotypical event-sourcing example of a bank account.
I wanted to explore the primary functions needed of an event stream.
- Create a Stream
- Append a new Event to a Stream
- Read Events from a Stream
- Subscribe to a Stream to receive appended Events
NuGet Package
As always, the first thing is to get the SqlStreamStore NuGet Package by adding it as a PackageReference in your csproj.
I’ve also included Newtonsoft.Json because I’m going to be (de)serializing events.
Events
I’m going to create two events to represent the bank account transactions. Deposited and Withdrawn. Both of these will extend the abstract AccountEvent that will contain the TransactionId, Dollar Amount, and DateTime of the event.
Creating a Stream
There is no way of creating an empty stream. This is actually really nice. Instead when appending an event to a stream, if the stream does not already exist, it’s created. This is the same how EventStore works and should also feel familiar in comparison to the API.
Appending to a Stream
Appending a new event to a stream is pretty straight forward. From the IStreamStore there is an AppendToStream method that takes a few args.
The first is StreamId. This generally would represent your aggregate root ID. In this demo, I’m setting it Account:{GUID}.
The second arg is ExpectedVersion. This is also similar to the EventStore API. This is used for handling concurrency. You can specify an integer that represents the number of events that are persisted in the stream. You can also use the ExpectedVersion enum that can specify Any to not concern yourself with concurrency or NoStream to verify its the first event.
Finally, the 3rd param is an instance of NewStreamMessage. It contains the MessageId (GUID), an event name and the event json body.
An interesting takeaway here is the event name is intentionally made as a string so you are not serializing/deserializing to the CRL type name. This is a great idea since the CLR type name is likely to be changed more than just a plain string which you can keep constant.
Reading a Stream
You can read a stream forward or backward. Forward meaning from the very first event until the last, which is what I’ll use in this example.
You simply specify the StreamId, the starting version (0) and how many events to pull from the stream. The result contains an IsEnd to indicate there are no more events left in the stream to read.
Subscribing to a Stream
Subscribing to a stream is pretty straight forward. Specify the StreamId, the starting version/position of where you want to be notified of new events from, and then StreamMessageReceived for handling the newly appended event.
Wrapping it up
Now that I’ve covered all the primary aspects, it’s just a matter of adding some input/output to our console app by allowing the user to deposit and withdrawal from the account.
In the demo, I’m using the InMemoryStreamStore so there is no persisted data upon restarting the app. I’m also using a new GUID to represent the AccountId on each run.
Source Code
All the source code shown in this post is available on GitHub.
This was a quick look at just a few of the APIs in SQL Stream Store but should give you a feel for how it works.
If you’d like a more detailed contented related to various aspects of event sourcing, let me know in the comments or on twitter.
https://github.com/dcomartin/SqlStreamStore.Demo
Related Links: