Domain Modeling Made Functional by Scott Wlaschin is a book which guides you through the design and implementation of an e-commerce ordering system. It’s a real world application with non-trivial business requirements. The project is implemented in F#, but any other language with a powerful type system which allows you to write in a functional paradigm could be used. Scott is author of the popular website fsharpforfunandprofit.com and also a highly regarded speaker in the F# community.
The book assumes some knowledge of software design, that you’ve written any sort of software before, but assumes no knowledge of domain driven design or F#. This was ideal for me as I’m an F# hobbyist and know very little about domain driven design. One of my struggles in trying to learn functional programming has been finding resources on actually implementing a real world application. Tutorials describing monads or how to parse a simple calculator grammar don’t show you how to deal with the sorts of problems you face when writing business applications, specifically:
- A lot of IO, with different systems (file system, databases, remote APIs) which have different latencies and uptimes.
- Using OOP libraries (ASP.NET Core, database abstraction libraries, …) in a functional manner.
- Handling errors through multiple layers of abstraction.
- Ever changing business requirements, and how to structure the application to handle this.
There are great ‘success story’ conference talks about the latest bank that was written in Haskell, but unfortunately they never go into actionable detail. If you took the advice of a lot of blog posts and tutorials on the internet about writing functional applications you would know it’s a simple 3 step process
- Gather requirements
- Write the application functionally
These tutorials tell you everything about step #3, and nothing about #1 and #2. Domain Modeling Made Functional teaches you how to write your application using a functional architecture. It’s split into 3 secions which largely mimic the development process: gathering requirements, designing the architecture, and implementing the code. The book concludes with making some non-trivial modifications to the application, simulating a change in business requirements. I cover the 3 sections below.
I. Understanding the Domain
The first 55 pages of the book don’t contain any F# code so you can skip them. No! No! No! Gathering requirements, and then modelling the domain are a critical part in any software project. Scott describes a domain specific language specifically for describing requirements and organising them into domains. In other words, a set of language, diagrams, and questions that developers and the domain experts can use to get a shared understanding of what is being built, a ubiquitous language.
Good thing I have experience gathering software requirements and already know how to do it. Again, No! No! No! Scott emphasises the point that requirements should be gathered and organised in a domain driven manner. I learned an enormous amount in this book, but I think this section was most valuable to me. It challenged the principles and knowledge I already thought I had around requirements gathering and software design. It demonstrated that if you develop an understanding of user requirements and domains in the context of domain driven development then a functional architecture almost naturally appears out of nowhere. In contrast, if you were to develop an understanding of user requirements and domains in the traditional OOP model (database driven design or class driven design), then trying to retrofit a functional architecture or functional language on top of this will be complicated, clumsy, and difficult to understand. I suspect for most developers the unconcious default is to gather requirement in an OOP manner.
II. Modelling the Domain
The section opens with a brief primer of some of the best features of the F# language: the type system, algebraic data types, composing types, optional, and Result. This quickly brings anyone not familiar with F# up to speed. It then dives into modelling the order taking system using the F# type system, and clearly shows how the type system can be used to almost perfectly model the domain (check out Scott’s article on making illegal states unrepresentable).
Below is an example from the book of capturing business rules in the F# type system. The UnitQuantity of a product is the number of copies of the product that is being ordered. This could be modelled as an int, but we don’t really want to allow the user to order between -2,147,483,648 and 2,147,483,647 items of the product. In the example of the book we want this value to be between 1 and 1000. So how do we model this in the code?
We start by declaring our own type with a private constructor
type UnitQuantity = private UnitQuantity of int
The private constructor prevents us from creating a UnitQuantity value directly, so we can’t accidentally do
let x = UnitQuantity -500
We’ll define our own module with a function called create, we put our validation logic in there and it’s the only way to make new values of type UnitQuantity
module UnitQuantity = let create qty = if qty < 1 then Error "UnitQuantity can not be negative" else if qty > 1000 then Error "UnitQuantity can not be more than 1000" else Ok (UnitQuantity qty) let value (UnitQuantity qty) = qty
We can then see the function in action
match UnitQuantity.create -1000 with | Error msg -> printfn "Failure: %A" msg | Ok qty -> printfn "Success. Value is %A" (UnitQuantity.value qty) match UnitQuantity.create 50 with | Error msg -> printfn "Failure: %A" msg | Ok qty -> printfn "Success. Value is %A" (UnitQuantity.value qty)
This example of primitive wrapping is really trivial and something you’ve likely seen before. The book starts with this, but also describes how to achieve safety with much more complex types such as those which span multiple domains, or are aggregates. I found the real value of this book is how it achieves the latter, Scott’s explains the details and process really well and it’s a pattern which you can pull out for writing your own applications.
But it’s the real world. If this was used in a website or public API then in reality our UnitQuantity can only ever be a JSON integer. Using the bounded context approach we have our ‘unsafe outside world’ of JSON filled with primitives like int which contain untrusted values. Our application will have an explicit serialisation step which converts between the untrusted public DTOs (data transfer objects), and our safely typed internal representation. Any errors at this stage are appropriately returned to the user. The ‘safe inside world’, where we have types such as UnitQuantity, as well as F# algebraic data types and units of measure are used throughout our business logic. Details on how to write this are covered in Chapter 11.
III. Implementing the Model
The last section of the book describes how the application is implemented using a functional pipeline. But not just any pipeline. It’s a pipeline that describes application workflows which handle
- Dependency injection
- Async error handling
- Application events
- DTO conversion
- Data persistence (database)
In other words, everything that is needed in a real world application. All code from the book is available on Github. It’s well commented and can be read and understood without having read the book. I think that it’s an incredible testament to F# as a language and Scott as an author, and is a perfect architecture in which to base your own application on.
There are also chapters on computation expressions for more complex error handling, techniques on serialising complex F# algebraic data types to JSON, and how to best model persistence in a database.
This book describes how you can architect and build an application in a functional programming language using domain driven principles. Scott writes with clarity and describes the problems or gotchas that can occur before explaining how to solve them. The diagrams in the book illustrate the pipelines and transformations that need to occur. The F# code printed in the book was clear to understand and wasn’t overwhelming. We’ve all seen Java text books where application code is printed over several pages.
I learned a huge amount from Domain Modeling Made Functional and will keep referring back to it. Techniques for modelling requirements, architecture of functional applications, tricks with the F# type system to gain application safety, error handling in pipelines, and use of computation expressions to name just a few.
If you’re interested in what I’ve written about and would like to learn more than I recommend Scott’s talk Domain Modeling Made Functional, checking out his website fsharpforfunandprofit.com, and then buying the book.
The code with
UnitQuantity can be played around with here.