DDD and FP can be friends
This article was inspired by a talk that challenges my thinking and experience with DDD and FP over the past years: DDD and FP Can’t Be Friends - Yet - Mike Sperber and Henning Schwentner - DDD Europe 2023. To be honest, I'm not sure how to interpret the talk. It relies on stereotypes: the level-headed DDDer versus the overly eager 'let's start coding asap' Haskeller, and I expected some reconciliation at the end. Although there is some of that and the talk leaves room for both perspectives, I was disappointed because it seems to suggest that modelling in FP is more difficult than in OO, and that OO is better suited for DDD modelling, which I believe is not the case. At first, I thought these stereotypes were used for demonstration purposes, but as the talk progressed, I became less certain. In the end, the title appears to reflect the talk's conclusion, which I disagree with. FP and DDD work well together, and in my opinion, the challenges presented apply to OO as well. On the other hand, the talk did prompt me to consider a few things I hadn't given much thought to before, which I will discuss further.
First of, if your takeaway from this talk is only its title, I encourage you to spend another 45 minutes listening to the Tech Lead Journal's podcast episode #79 with Mr. Scott Wlaschin to understand how FP and DDD can 'be friends' quite effectively. He does a much better job than I can. (It's worth listening to regardless, including a fun analogy explaining why effectiveness is more important than efficiency, using the old TV series 'Murder She Wrote'.) Alternatively, for a deeper understanding, you can read the excellent book Domain Modelling Made Functional.
And now to my notes:
DDD is not tied to any specific programming paradigm
DDD and FP are just as compatible as DDD and OO. OO's design patterns and object hierarchies can present challenges similar to those of the Monad and Applicative 'patterns' mentioned in the talk when it comes to modelling close to the domain. These abstractions are useful, and I would argue necessary, as they often result in better designs and more maintainable, bug-free code. In practice, we will likely use a ubiquitous language at the functional level and employ design patterns and abstractions for better software design. Interestingly, both approaches share the goal of building better software. Over-abstraction can be an issue for sure but my point is that this is not an issue solely linked to FP. Many factors can influence the paradigm choice and the extent to which abstraction levels and preciseness are modelled.
A strategic approach applies to architecture and code
While watching the talk, it seemed to me that it was more about tactical versus strategic approaches to modeling and programming, rather than FP versus DDD. I'm using the following definition of tactical vs. strategic because, to me, it clearly illustrates the short-term vs. long-term impact of the chosen approach. Words in brackets are my own.
"In tactical programming [modelling], the primary goal is to get something working quickly, even if that results in additional complexity; in strategic programming [modelling], the most important goal is to produce a great system design." (John Ousterhout, A Philosophy of Software Design, 2nd Edition)
In the talk both the DDD-practitioner and FP-practitioner advocate that their approach is strategically the most important, urging the other to be more tactical. For instance, the DDD-practitioner recommends writing code that works 'just enough' using TDD, while the FP-practitioner essentially wants to start coding immediately without considering the overall picture beforehand. However, in my opinion, they both operate at different levels of granularity, and at each level, it is worthwhile to pursue a DDD-inspired strategic approach (if part of your core domain but that's a whole different topic). If strategic design is important, it is important at the higher level and at the code level. It's not an 'either/or' choice.
First, define your architecture using bounded contexts and iterate until the value begins to diminish and risks are addressed. (E.g. by using a user journey, the talk shows a nice way of doing this.) Then, start coding. Until you actively work with the domain and 'encode' the knowledge of the domain in something visual or code-based, you are missing important details. While modelling the architecture and drawing diagrams is needed and can feel very satisfying getting something running connects to reality more than any other activity. The same applies to abstracting and refactoring code — do it until the value begins to diminish and risks are sufficiently addressed. In my experience, remodeling, refactoring, and abstracting for deeper understanding is often worth it. Knowing when to stop or continue is the challenge at each level.
When it comes to collaboration between business and implementers the key is to determine the appropriate level of abstraction where both can meet and discuss. BDD offers a method that encourages collaboration between domain experts and implementers based on a shared, non-technical specification. Modelling data and actions close to the domain as in DDD is another approach, be it OO or FP. The latter doesn't require domain experts to understand all the details of the code.
Finally some quick notes on smaller things from the talk I will not go into:
TTD and BDD are techniques used in FP as well.
Haskell is not the only kind of FP.
Immutability does not prevent changes to the domain. (It can even have benefits to modelling.)
The perceived incompatibility between DDD and FP, as suggested by the talk, is not accurate. Both paradigms can be effective in their own right. The main takeaway is that DDD is not tied to a specific programming paradigm and that a strategic approach applies to both architecture and code, regardless of whether it's OO or FP. Techniques such as TDD and BDD, along with the right choice of programming language, can enable this successful collaboration. Ultimately, the choice between DDD and FP, or a combination of both, should be guided by the specific needs and constraints of the project at hand.