Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DDD functional way: Why is it better to decouple state from the behavior when applying DDD with functional language?

I've read several articles (also the book Functional domain modeling) where they propose to decouple state of the domain object from the behavior, but I cannot understand advantage of such approach over reach domain model.

Here is an example of reach domain model:

case class Account(id: AccountId, balance: Money) {
  def activate: Account = {
   // check if it is already active, eg, enforce invariant 
   ...
  }
  def freeze: Account = ???
} 

I can chain operations with this account in following way:

account.activate.freeze

Here is example of "anemic" approach which they suggest:

case class Account(id: AccountId, balance: Money)

object AccountService {
  def activate =  (account: Account) => {
   // check if it is already active, eg, enforce invariant 
    ...
  }

  def freeze =  (account: Account) =>   {
    ...     
  }
}

And here I can chain operations like this

activate andThen freeze apply account

What is the advantage of the second approach except of "elegant" syntax?

Also, in case of reach domain model, I will enforce invariants in single class, but in case of "anemic" model, logic/invariants can spread across services

like image 481
Teimuraz Avatar asked Jul 01 '19 13:07

Teimuraz


People also ask

What is DDD concept in robotics?

Domain-driven design (DDD) is a software development philosophy centered around the domain, or sphere of knowledge, of those that use it. The approach enables the development of software that is focused on the complex requirements of those that need it and doesn't waste effort on anything unneeded.

What is the main idea of domain-driven design?

Domain-Driven Design(DDD) is a collection of principles and patterns that help developers craft elegant object systems. Properly applied it can lead to software abstractions called domain models. These models encapsulate complex business logic, closing the gap between business reality and code.

What is DDD pattern?

DDD patterns help you understand the complexity in the domain. For the domain model for each Bounded Context, you identify and define the entities, value objects, and aggregates that model your domain. You build and refine a domain model that is contained within a boundary that defines your context.

What is DDD example?

An aggregate is a domain-driven design pattern. It's a cluster of domain objects (e.g. entity, value object), treated as one single unit. A car is a good example. It consists of wheels, lights and an engine.


1 Answers

I offer two thought processes, that can help explain this puzzle:


The concept of state in your example and the book differ. (I do hope we both are referring to Functional and Reactive Domain Modeling).

Your example states of activate, and freeze are probably domain concepts, while the book talks about states that only serve as markers. They do not necessarily have a role in the domain logic and exist only to disambiguate states of the workflow. Ex. applied, approved and enriched.


Functional programming is all about implementing behaviors, that are independent of the data passed into them.

There are two aspects of note while implementing such behaviors.

A behavior can be reusable across contexts. It can be an abstract trait, a monoid if you will, that takes any type T, and performs the same operation on it. In your example, freeze could be such a behavior, applicable to Account, Loan, Balance, etc.

The behavior has no side effect whatsoever. One should be able to call the behavior again and again with the same data set and receive the same expected response without the system getting affected or throwing an error. Referencing your example, calling freeze repeatedly on an account should not throw an error.

Combining the two points, one could say it makes sense to implement a behavior as a reusable piece of code across different contexts (as a Service) while ensuring that the input is validated (i.e., validate the state of the object provided as input before processing).

By representing the acceptable state of the object as a separate type and parameterizing the model/object with this explicit type, we could enforce a static check of input, during compile time. Referring to the example provided in the book, you can only approve andThen enrich. Any other incorrect sequence will raise a compile-time error, which is far more preferable to using defensive guards to check input during runtime.

Thus, the second approach is not just elegant syntax at the end of the day. It is a mechanism to build compile-time checks, based on the state of an object.


So, while the output has the appearance of an anemic model, the second approach is taking advantage of some beautiful patterns bought forth by functional programming.

like image 66
Subhash Avatar answered Sep 30 '22 13:09

Subhash