Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F# is a 2018 book by Scott Wlaschin.1 As it says on the tin, the book's goal is to show the reader how to implement domain modeling using functional programming. The phrase "domain-driven design" itself was coined by Eric Evans' 2003 book of the same name where all code examples are written in Java,2 and most other books and talks about DDD use object-oriented languages. While code examples for this book are provided in F#, no prior knowledge of the language is assumed. The first section of the book is a brief overview of domain-driven design terms, and section two walks through how the type system and chained function calls can be used for 'modeling in the small'. Part 3 wraps up the book by fully implementing the e-commerce bounded context introduced in Part 1.
The book's discussion of domain modeling topics agnostic to programming paradigm were good but could have gone into more detail. It covers the basics of entities, value objects, aggregates, and bounded contexts well if not as in-depth as Domain Driven Design. While the Evans book suffers from a low ratio between prose and code segments, Domain Modeling Made Functional never goes more than a few paragraphs without showing some code, making Wlaschin's arguments more concrete and the book more readable. One limitation of his approach is his reliance on a single, relatively straightforward business domain throughout the book while Evans offers more varied modeling challenges across multiple domains in a single chapter. Wlaschin's e-commerce business domain includes order validation, pricing, and an acknowledgement email,3 and this business logic isn't as complicated compared to the syndicated loan system in chapters 8 and 10 of Domain Driven Design where the solution must keep track of a lender's share of an incoming loan payment. The book would be improved by a chapter walking the reader through multiple thorny domain modeling cases.
What makes the book shine is its progressive disclosure of language features to an F# beginner in a way that really sells the language. Domain modeling doesn't require too many language features so Wlaschin spends as little time as possible explaining F# syntax. By introducing the type system and pattern matching early on the book can quickly explain the advantages that F# brings over imperative languages. An example of baking the domain rules into the type system is shown below, where the String50
type and module are set up to make null, empty, or string larger than 50 characters unrepresentable.
type String50 = private String50 of string
module String50 =
let value (String50 str) = str
let create fieldName str : Result<String50, string> =
if String.IsNullOrEmpty str then
Error(fieldName + " must be non-empty")
elif str.Length > 50 then
Error(fieldName + " must be less than 50 chars")
else
Ok(String50 str)
Concerning pattern matching, the following example was used in the book with the ShoppingCart
discriminated union.
type ShoppingCart =
| EmptyCart
| ActiveCart
| PaidCart
let addItem cart item =
match cart with
| EmptyCart -> ActiveCart { UnpaidItems = [ item ] }
| ActiveCart { UnpaidItems = existingItems }
-> ActiveCart { UnpaidItems = item :: existingItems }
| PaidCart _ -> cart
Wlaschin also gives an effective explanation of the Either monad to bring error handling into the primary control flow rather than the 'hidden' control flow of exception handling. Rather than explain that the Result
type is an implementation of the Either monad - or even explain what monads are - he explains the type before using it in the rest of the domain for error handling. Aiding his explanation are diagrams similar to those from his 'Railway Oriented Programming' talk shown below.4
For readers coming to functional programming for the first time, this is an appropriately gentle introduction to monads, and he doesn't even use the 'm-word' until he's fully explained Result
: "The m-word has a reputation for being scary, but in fact we’ve already created and used one in this very chapter!". I'm almost embarrassed to admit that this was how I learned that the Java Optional
class was itself a monad. Little did I know that nearly every day I was using an FP concept that had confused me for years! Something similar is done for Michał Płachta's Groking Functional Programming, where the Optional, Either, and I/O monads are demonstrated without using the word 'monad' anywhere in the book.5
This leaves a lot about F# that a novice to the language will have to pick up - there isn't a discussion of the built-in .NET types, a dedicated chapter to working with collections, and other such topics. For questions like 'how do I do a map over an array' the excellent F# Language Reference 6 and F# Library Reference provide quick answers.7 Domain Modeling Made Functional isn't sold as a way to learn F#, but while I didn't intend to at the outset I ended up reading the entire book cover-to-cover and it greatly motivated me to write F# in the process. Reading the book, using the language documentation, and doing a few old Advent of Code problems was a faster and more painless way to learn the basics of a new language than what I had done in the past by reading a language-learning book first. This has made me realise that I would much rather learn a new language by diving into a problem area that it is well equipped to work in: rather than just learn Rust, I'd rather do a deep dive in concurrency and learn Rust in the process. Rather than just learn C, I'd rather write a ray tracer, something I've tried unsuccessfully a few times since first seeing the spectacular ray tracer in 99 lines of C++.8 In the future, I'll be on the lookout for 'Learn concept X through language Y' books.
One criticism of the book is that it stresses practicality and making functional programming look normal to a fault. While some material in the persistence chapter helped tie database options to Result
types, much of the content on incorporating relational and document databases was unnecessary; this type of detail was left out of the Evans book for a reason. Wlaschin's chosen dependency injection method was by passing dependencies as function parameters, explaining that the Reader and Free monads would be omitted given the introductory nature of the book. The Reader monad was something that I immediately read up on after finishing the book, as I'm sceptical of passing every dependency as function parameters for large applications. Given how good his Either monad explanation was, I'm sure he would have hit the mark for Reader and Free as well.
The second to last sentence in Domain Modeling Made Functional is "In this book I aimed to convince you that functional programming and domain modeling are a great match", and the book does accomplish this. Wlaschin is one of the best technical authors I've read, making the 310 pages in the book fly by. While It is missing some advanced DDD and FP concepts it ended up being an excellent introduction to F# that I would recommend to anyone interested in learning the language.