The question is very technical, and it sits deeply between F# / C# differences. It is quite likely that I might’ve missed something. If you find a conceptual error, please, comment and I will update the question.
Let’s start from C# world. Suppose that I have a simple business object, call it Person
(but, please, keep in mind that there are 100+ objects far more complicated than that in the business domain that we work with):
public class Person : IPerson
{
public int PersonId { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
}
and I use DI / IOC and so that I never actually pass a Person
around. Rather, I would always use an interface (mentioned above), call it IPerson
:
public interface IPerson
{
int PersonId { get; set; }
string Name { get; set; }
string LastName { get; set; }
}
The business requirement is that the person can be serialized to / deserialized from the database. Let’s say that I choose to use Entity Framework for that, but the actual implementation seems irrelevant to the question. At this point I have an option to introduce “database” related class(es), e.g. EFPerson
:
public class EFPerson : IPerson
{
public int PersonId { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
}
along with the relevant database related attributes and code, which I will skip for brevity, and then use Reflection to copy properties of IPerson
interface between Person
and EFPerson
OR just use EFPerson
(passed as IPerson
) directly OR do something else. This is fairly irrelevant, as the consumers will always see IPerson
and so the implementation can be changed at any time without the consumers knowing anything about it.
If I need to add a property, then I would update the interface IPerson
first (let’s say I add a property DateTime DateOfBirth { get; set; }
) and then the compiler will tell me what to fix. However, if I remove the property from the interface (let’s say that I no longer need LastName
), then the compiler won’t help me. However, I can write a Reflection-based test, which would ensure that the properties of IPerson
, Person
, EFPerson
, etc. are identical. This is not really needed, but it can be done and then it will work like magic (and yes, we do have such tests and they do work like magic).
Now, let’s get to F# world. Here we have the type providers, which completely remove the need to create database objects in the code: they are created automatically by the type providers!
Cool! But is it?
First, somebody has to create / update the database objects and if there is more than one developer involved, then it is natural that the database may and will be upgraded / downgraded in different branches. So far, from my experience, this is an extreme pain on the neck when F# type providers are involved. Even if C# EF Code First is used to handle migrations, some “extensive shaman dancing” is required to make F# type providers “happy”.
Second, everything is immutable in F# world by default (unless we make it mutable), so we clearly don’t want to pass mutable database objects upstream. Which means that once we load a mutable row from the database, we want to convert it into a “native” F# immutable structure as soon as possible so that to work only with pure functions upstream. After all, using pure functions decreases the number of required tests in, I guess, 5 – 50 times, depending on the domain.
Let’s get back to our Person
. I will skip any possible re-mapping for now (e.g. database integer into F# DU case and similar stuff). So, our F# Person
would look like that:
type Person =
{
personId : int
name : string
lastName : string
}
So, if “tomorrow” I need to add dateOfBirth : DateTime
to this type, then the compiler will tell me about all places where this needs to be fixed. This is great because C# compiler will not tell me where I need to add that date of birth, … except the database. The F# compiler will not tell me that I need to go and add a database column to the table Person
. However, in C#, since I would have to update the interface first, the compiler will tell me which objects must be fixed, including the database one(s).
Apparently, I want the best from both worlds in F#. And while this can be achieved using interfaces, it just does not feel the F# way. After all, the analog of DI / IOC is done very differently in F# and it is usually achieved by passing functions rather than interfaces.
So, here are two questions.
How can I easily manage database up / down migrations in F# world? And, to start from, what is the proper way to actually do the database migrations in F# world when many developers are involved?
What is the F# way to achieve “the best of C# world” as described above: when I update F# type Person
and then fix all places where I need to add / remove properties to the record, what would be the most appropriate F# way to “fail” either at compile time or at least at test time when I have not updated the database to match the business object(s)?
How can I easily manage database up / down migrations in F# world? And, to start from, what is the proper way to actually do the database migrations in F# world when many developers are involved?
Most natural way to manage Db migrations is to use tools native to db i.e. plain SQL. At our team we use dbup package, for every solution we create a small console project to roll up db migrations in dev and during deployment. Consumer apps are both in F# (type providers) and C# (EF), sometimes with the same database. Works like a charm.
You mentioned EF Code First. F# SQL providers are all inherently "Db First" because they generate types based on external data source (database) and not the other way around. I don't think that mixing two approaches is a good idea. In fact I wouldn't recommend EF Code First to anyone to manage migrations: plain SQL is simpler, doesn't require "extensive shaman dancing", infinitely more flexible and understood by far more people. If you are uncomfortable with manual SQL scripting and consider EF Code First just for automatic generation of migration script then even MS SQL Server Management Studio designer can generate migration scripts for you
What is the F# way to achieve “the best of C# world” as described above: when I update F# type Person and then fix all places where I need to add / remove properties to the record, what would be the most appropriate F# way to “fail” either at compile time or at least at test time when I have not updated the database to match the business object(s)?
My recipe is as follows:
interfaces, it just does not feel the F# way
In Short
Even Shorter
Stick with Single Responsibility Principle and enjoy the benefits.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With