Hi, Habr! Not so long ago, the publishing house Manning published a difficult, but long-awaited and hard-won
book by the author about functional modeling of subject areas.

Since we are preparing books both on
Scala and on
subject-oriented design patterns , we will publish one of the Sahib Gosh articles on the ideas embodied in his book and ask how interesting this book would be.
I once studied Dean Wampler’s
presentation about subject-oriented design, anemic subject models, and functional programming that can smooth out some of the problems identified. I suppose OOP programmers might shudder from some theses of Wompler. They are contrary to generally accepted beliefs, according to which subject-oriented design should be carried out primarily by means of the PLO.
')
I will voice the idea with which I strongly agree - "
DDD stimulates you to understand the subject area, but does not help in the implementation of the models ." DDD is really great at helping developers master the subject area that they have to deal with and develop common terminology that will be used throughout the design and implementation of the application. Approximately such a role is played by design patterns - they provide a terminological apparatus, with the help of which one can essentially explain a task to developers without going into details of its implementation.
On the other hand, when you try to implement the concept of DDD using standard OOP techniques, where the state is associated with behavior, often you get a confusing changeable model. A model can be saturated in the sense that all aspects of a specific abstraction taken from the subject area can be incorporated into the class being modeled. But in this case, the class becomes fragile, since abstraction goes out too local, it lacks global capabilities in terms of reuse and composability. As a result, when we try to compose a set of abstractions at the level of the domain services, this level is overwhelmed with garbage-sticking code — such code is needed to cope with impedance mismatch between class boundaries.
Therefore, when Dean says, “
Models must be anemic ” - I think he calls to avoid such confusion of state and behavior in the domain object, which also gives a false sense of security and saturation of the model. He recommends writing domain objects as follows: they should have a state only if behaviors are modeled using autonomous functions.
Sometimes a beautiful implementation is just a function. Not a method. Not a class. Not frame. Just a function.
John carmack
There is another clumsy argument that comes across to me quite often: the state is confused with the behavior in the process of modeling the latter as the encapsulation of methods in the class grows. If you still adhere to this philosophy, refer you to the magnificent
article by Scott Meyer, written back in 2000. He refuses to assume that the class is the necessary level of modularization, and recommends writing more powerful systems of modules, since it is more convenient to store the domain behavior in modules.
Here is an anemic ordering subject model abstraction ...
case class Order(orderNo: String, orderDate: Date, customer: Customer, lineItems: Vector[LineItem], shipTo: ShipTo, netOrderValue: Option[BigDecimal] = None, status: OrderStatus = Placed)
Earlier, I
wrote how the DDD Specification and Aggregate patterns are implemented using the principles of functional programming. In addition, we discussed how to make functional updates of aggregates using data structures such as Lens. In this article, we will use them as building elements, apply more functional patterns and implement larger behaviors that simulate the domain language. In the end, one of the basic principles of DDD is to raise the vocabulary of the subject area to the implementation level, so that the functionality is obvious to the developer involved in supporting the model.
The basic idea is to check whether the creation of domain behavior in the form of autonomous functions really gives an effective domain model in accordance with the principles of DDD. The base classes of the model contain only those behaviors that can be changed by functional means. All domain behaviors are modeled using functions located in the module representing the aggregate.
Functions are assembled, and this is how we are going to interlink the behaviors of the domain and build large abstractions from smaller ones. Here is a small function that evaluates Order. Note: it returns
Kleisli
, which actually provides us with composition on top of monad functions. That is, instead of composing
a -> b
and
b -> c
, and that is exactly what we would have done with the usual arrangement of functions, we do the same with
a -> mb
and
b -> mc
, where
m
is a monad. Composition with effects, so to speak.
def valueOrder = Kleisli[ProcessingStatus, Order, Order] {order => val o = orderLineItems.set( order, setLineItemValues(order.lineItems) ) o.lineItems.map(_.value).sequenceU match { case Some(_) => right(o) case _ => left("Missing value for items") } }
But what does this give us? What exactly do we gain through functional patterns? We are able to distinguish families of similar abstractions, for example, applicatives and monads. Sounds somewhat abstract, perhaps, to justify this approach, a separate article is needed. Simply put, they encapsulate the effects and side effects of the calculations, so that you can focus on implementing the model as such. Take a look at the process function below - here’s the composition of monad functions in practice. But all the stuffing that provides the processing of effects and side effects is abstracted in
Kleisli
, so the implementation at the user level is simple and concise.
Kleisli
demonstrates the full potential of assembling monadic functions. Any behavior in the domain can fail, and the failure is modeled using the
Either
monad - here
ProcessingStatus
is just a type alias for
..type ProcessingStatus[S] = \/[String, S]
. Working with
Kleisli
, you do not have to write any code to handle failures. Below you can make sure that the composition is completely similar to the usual functions, alternative execution threads are taken into account at the pattern level.
When the
Order
evaluated, you need to apply discounts to the items included in it. This is another behavior implemented in accordance with the same pattern as
valueOrder
.
def applyDiscounts = Kleisli[ProcessingStatus, Order, Order] {order => val o = orderLineItems.set( order, setLineItemValues(order.lineItems) ) o.lineItems.map(_.discount).sequenceU match { case Some(_) => right(o) case _ => left("Missing discount for items") } }
Finally, we calculate the cost of order
Order
...
def checkOut = Kleisli[ProcessingStatus, Order, Order] {order => val netOrderValue = order.lineItems.foldLeft(BigDecimal(0).some) {(s, i) => s |+| (i.value |+| i.discount.map(d => Tags.Multiplication(BigDecimal(-1)) |+| Tags.Multiplication(d))) } right(orderNetValue.set(order, netOrderValue)) }
But the service method composing all the above described behaviors of the domain into one big abstraction. We do not have to instantiate a single object. We simply compose functions, and as a result, we manage to express the entire flow of events. The code turned out to be so readable and concise precisely because the abstraction itself is defined quite clearly.
def process(order: Order) = { (valueOrder andThen applyDiscounts andThen checkOut) =<< right(orderStatus.set(order, Validated)) }
If you are interested in the full source code for this example, I send you to my
repository on github.