F# and RavenDB

Callum Linington
3 min readSep 2, 2022

--

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.

Unrelated home shot

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.

Interesting Time

So, lets dive into some more interesting aspects.

  1. I want auto database creation
  2. 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.

Then we 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.

The 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

--

--