As I recommend in the book, as part of functional thinking we start with the functions or use cases. Related functions go in modules and composing modules lead to larger functionalities.
Now with this thinking, a use case like funds transfer may be conceived as a domain behavior. Depending on the complexity of the use case it can be a single function or a module. But, yes, the interface / algebra of such a function should be a ReaderT, as it expects stuff from the environment (db connection, config info etc.).
Once we have the info from the environment through a Reader monad, we need to implement transactional semantics to ensure that aggregates are consistently updated. For simplicity we can think of ACID here and use the transaction semantics of some library like Slick or doobie.
Here are some of the steps ..
1. Model anything that takes stuff from environment as a ReaderT. This decouples the environment from the domain model and allows you to build your computation separately from executing it
2. Within the function consider using monadic or applicative semantics depending on the use case. In case of funds transfer I think it can be monadic as we want a fast fail semantics
3. Ensure consistency of aggregates using transaction semantics of the underlying db access library. Both Slick and doobie offer compositional semantics for this.
HTH, let me know if u need any more details on this.
Thanks.
|