Anyone using DDD tactical patterns to model their domain? I’m reading some books on it and the examples are OO, so I’m wondering how to apply it to functional dynamically typed languages
@jon920 Do you mind sharing which books are you referring to here? Beside others, I read https://pragprog.com/titles/swdddf/domain-modeling-made-functional/ – found it excellent for FP oriented design.
“Patterns, Principles, and Practices of Domain-Driven Design” and “Microsoft .NET - Architecting Applications for the Enterprise (Developer Reference)”
@jon920 Can you elaborate what part(s) you're finding hard to apply in an FP context?
Entities, where you have the data and behavior together. In OOP they are advocating that you hide most attributes of the object, and only expose a subset of those attributes as public through getters or an interface of public business logic methods
As I know, you should not mix entities and service objects. Entities should hold just values (plain data structures) and services classes operate on them.
But in Elixir for example (I am only starting to look at Clojure) I think of a struct where you could poke at any of those attributes. Maybe you just need to wrap those structs into some kind of aggregate level module that selectively exposes an interface to hide implementation details?
I'd mostly use (immutable) hash maps for entities (and have the behavior in its own namespace). In OOP, you're forced to use encapsulation but I always ask "who are you protecting your data from?"
The Clojure Applied book leans heavily to records for entities, but Alex has said that in a new edition he would use records less and maps more.
I think there were performance concerns with maps, while records were faster to manipulate ..
Interesting, I ordered that book yesterday
“Who are you protecting the data from”: from the rest of your domain model so that they aren’t binding to information they should not be and duplicating logic or implementing it in a different way
To make it more concrete, perhaps they should not be setting some of the attributes, only reading them. For a FlightBooking in my book example the departure date setter is private, while the getter is public. So you can only construct a FlightBooking through a constructor that enforces invariants
But that doesn't really apply in a language where all attributes are immutable.
A lot of "patterns" in the OOP world are workarounds for deficiencies in OO itself.
(the "Core JEE Patterns" book is a classic example of a of lot of "patterns" that are very specifically workarounds for failings in JEE itself)
my take on difference between typical OOP code and idiomatic clojure: it's common in OOP to combine data, identity, and validation. These are packaged together in a class. the problem is that a class is inflexible and can't be taken apart. conversely, in clojure, the data, identity, and validation are all defined separately and then composed as needed to build an application
Most of the Gang of Four stuff becomes just "functions" when you move away from OO, for example.
The concepts of DDD apply but some of the low-level mechanics do not -- are just not needed.
In this book example (from Patterns Principles and Practices of DDD by Scot Millet/Nick Tune, C# book) the FlightBooking constructor will throw an error if the id, departure date, or customer id are null and their setters are made private. How can we enforce those invariants on a hash or struct/record in Elixir/Clojure? Because there is nothing preventing you from saying {id: “a8c9-7kjc-97ax-7jk2”, departure-date: null: customer-id: null} (hash example sorry if I messed up the Clojure syntax)
Clojure Spec is good for describing those sorts of constraints.
Interesting
whether or not {id: "a8c9-7kjc-97ax-7jk2", departure-date: null: customer-id: null}
is valid data depends on the domain, where the data is coming from, where it's going to, etc. in clojure, you can validate the data as needed rather than needing to satisfy arbitrary constraints just to specify some data.
consider loading some data from another system (either from a file or over the network). that data may or may not satisfy "customer id is not null", but may be valid in its context. it might be invalid in another context though.
validation and data are specified separately, as they should be (imo).
Yeah, I think OO inherently conflates validation with the data and with other behavior -- and therefore it loses the differentiating contexts and that has to be "constructed" somehow, which I think is where all this complexity of overlaying context on data and behavior comes from...
you can still use helper functions to make it easy to construct and manipulate data in a way that's valid for a specific use case
@smith.adriane Yeah the invariants I am talking about are only within a specific bounded context within the domain layer (roughly the yellow part here in the architecture i am trying to follow). You’re right outside of that the same validations/invariants would not necessarily apply
spec is neat because not only can you create specs to validate data, but given a spec, it can also automatically generate data that conforms to the spec. it's great for development and testing
I talk about some of the ways we use Spec at work in this blog post https://corfield.org/blog/2019/09/13/using-spec/
I’ll definitely check out spec. I know someone even made a lib for Elixir inspired by it https://github.com/keathley/norm was what I was thinking of, but I also just found this https://github.com/vic/spec
Cool thanks
Good writeup thanks again
Scott Wlaschin's Domain Modelling Made Functional has some ideas about how DDD translates to FP. It's F# based and invariants are taken care of by types, which obviously doesn't translate to Clojure, but the stuff about the basis of modelling in FP being through composite values transformed by compositions of functions I think comes across pretty well. Personally I find it a much more natural way of modelling than the OO equivalent for most domains.
The book’s code repo is here https://github.com/swlaschin/DomainModelingMadeFunctional (uses F#)
i really like the approach shown in the book and have used it a few times since then in node/typescript projects. it worked well for us