Quite a few years ago I used RavenDB, and it was a good experience — it has only come on leaps and bounds since. I used to use a lot of MongoDB, I got fed up of the “SQL Experience” and needed something better for OOP. But now I’m FP and DDD, SQL won’t cut it, and Mongo has been grating against me.
Getting Started with RavenDB
First things first, we need a local environment
docker run --rm -d -p 9010:8080 -p 38888:38888 --name ravendb -e RAVEN_Setup_Mode=None -e RAVEN_License_Eula_Accepted=true -e RAVEN_Security_UnsecuredAccessAllowed=PrivateNetwork ravendb/ravendb
There’s a few environment variables in there, and they are needed.
RAVEN_Setup_Mode=None This chooses the security during setup, essentially asking RavenDB to start up with no set up process — remove, and you’ll have to go through setup wizard every time you create the container, painful for automation. More info…
RAVEN_License_Eula_Accepted=true Again, another setup process.
RAVEN_Security_UnsecuredAccessAllowed=PrivateNetwork This is because TLS and certificate authentication is on by default, this makes the server accessible with no authentication. More info…
Connecting to RavenDB
There is a C# example, what I came up with is:
I use classes and interfaces because DI is still easy and my go to in F# — no need to mess around with monads and get into multiple monad hell.
.AddSingleton<IRavenClientFactory, RavenClientFactory>(fun sp ->
let configurationSection = config.GetSection("Raven")
let url = configurationSection.GetValue("Url")
let dbName = configurationSection.GetValue("Database")
new RavenClientFactory([ url ], dbName)
Reading from RavenDB
I created just a simple wrapper over the reading and writing. Nothing more than to just handle the extra orthogonal concerns that are coming later.
Don’t worry about the
ErrorResponse<'t> , it’s just a Discriminated Union, allows me to turn exceptions and other events into useful information for understanding at the API boundary. Such that if it’s a transient error, I can return a 500 and ask the client to retry or something…
There’s nothing overly amazing about this, just pretty standard stuff.
So, lets dive into some more interesting aspects.
- I want auto database creation
- Seamless integration with Domain Objects and Value Objects
Auto Database Creation
Why? During my local development, I switch between computers, sometimes I use my “big” computer — a.k.a. Tower PC — sometimes I like to sit and watch TV whilst coding — as I’m doing now whilst writing this blog. Each time, I want my database and other resources of my APIs/websites to be able to auto generate their seed data etc…
It turns out that in RavenDB, it’s a little cumbersome to create a new database. Unlike SQL or Mongo — where simply firing a query will either create a database or auto generate a collection — RavenDB requires you to interact with the
Maintenance APIs. In that link you’ll see the C# example, let’s make it funky-tional.
In this, you’ll see
Retries which is a little wrapper around Polly that I wrote.
This starts at
checkForDatabase which returns
Result<unit, unit> , essentially if it returns
Error () then we need to create a Database.
createIfNotExists -> tryCreateDatabase .
tryCreateDatabase fires of the
CreateDatabaseOperation . If this throws an exception of
ConcurrencyException then we can ignore, because it’s already being created somewhere else. If the exception is
RavenException then it’s probably because the server isn’t ready yet, and simply needs to be retried until it succeeds. It’s never failed in 20 retries, so that seems reasonable.
env is a
IWebHostEnvironment , this is so that I can check if we’re in development mode. Done like-a-so
ravenClientFactory.InitialiseDatabase env .
So, that is that, pretty much dunzo’d…
Seamless Integration with DDD
Well, you’ll have to wait till next time… :D