Exploring F# for Domain Driven Design
Getting our feet wet
Once upon a time… actually not that long ago, I got into a conversation with a coworker about the virtues of different type systems. Somehow the conversation led to F# which peaked my interest.
Shortly after that conversation we had a lab-day at work where I proposed exploring ‘type driven development’ using F#. We took a subset of a domain we know very well: absence requests. The idea was to see how well we could capture this domain using the F# type system and get a feel for the advantages it offered over Ruby-like dynamic type systems or C#-like ‘mostly static’ type systems.
We used Visual Studio Code with the Ionide F# plugin. We created a single script file for our exploration, so we could execute it interactively and use the REPL for further exploration & learning. Overall this felt as a very productive way to get up to speed.
Visual Studio Code with Ionide rocks!
Ionide will visualize the inferred types on-the-fly. I got addicted to this feature, and cannot imagine living without it.
In our domain an absence request goes through some states:
- A request is submitted by a worker
- A team leader approves or denies the request
- When approved: the system must do some additional policy-driven checks
- In the end the request ends up in an approved or a denied state
Here is a gist with the type definition:
It reads a bit like a spec. The combination of discriminated union types makes it impossible to represent illegal states.
A lot less code is needed because the type system prevents representing illegal states.
Take a look at ‘Period’ starting on line 15. A request can be submitted for a date range, for a single day or for a time span. F# makes it so easy to define these different types.
A date range has a starting and ending date. For a single day one can specify a day part, e.g. morning (which we call FirstHalf in our domain lingo because the first half is not always a morning). Finally a time span has a starting and ending time.
With some effort it is possible to define classes and methods in C# that contain the same underlying validations. But the code quickly becomes ugly, and tends to be spread across multiple source files.
In F# this definition is so simple to write. The entire definition is in the same source file, and you can get surprisingly far without needing to write actual code.
Commands and events
We tend to write a use case as a function that gets a command and returns business events or some error.
The current state of our domain object is updated by applying those events.
- f(current_state, command) -> events | error
- f(current_state, events) -> new_state
Here is an example of an Event-type:
Note that the type system makes it really simple to embed business decisions in this definition. E.g. an approved event has an optional comment whereas a denied event has a (required) comment.
The actual code for this experiment is close to trivial. You can see a snippet above in the screenshot where I show type inference at work.
One of the things that struck me was how ‘dynamic’ the code looks while it is actually extremely strongly typed and static. But thanks to the type inference it is often unnecessary to declare the types of function arguments or return values.
Thanks to the type inference it is seldom necessary to explicitly declare the types of arguments.
This experiment aroused my interest. After a day of muddling through these first steps I decided I wanted to invest more time into getting to know F#. So I did!
More to come…