Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Architectural Patterns
in Building Modular
Domain Models
Debasish Ghosh
@debasishg
Architectural Patterns
in Building Modular
Domain Models
Debasish Ghosh
@debasishg
Architectural Patterns
in Building Modular
Domain Models
Debasish Ghosh
@debasishg
Architectural Patterns
in Building Modular
Domain Models
Debasish Ghosh
@debasishg
— John Hughes in Why Functional Programming Matters
https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf
“[..] mo...
Intent
• Domain Model
• Domain Model Algebra
• Algebraic Combinators
• Compositionality
• Algebra as the basis of modulari...
Patterns of modularization of domain models
Algebraic patterns of modularization of domain models
Algebraic patterns of modularization of domain models
using pure values
Algebraic patterns of modularization of domain models
using pure values as effects
Algebraic patterns of modularization of domain models
using pure values as effects
even in the presence of side-effects
Domain Modeling
Domain Modeling
(Functional)
What is a domain model ?
A domain model in problem solving and software engineering is a
conceptual model of all the topic...
The Functional Lens ..
“domain API evolution through algebraic
composition”
The Functional Lens ..
“domain API evolution through algebraic
composition”
Building larger domain behaviours
out of small...
The Functional Lens ..
“domain API evolution through algebraic
composition”
Use composition
of pure functions and types
Your domain model is
a function
Your domain model is
a function
Your domain model is
a collection of
functions
Your domain model is
a collection of
functions
some simpler models are ..
https://msdn.microsoft.com/en-us/library/jj591560.aspx
A Bounded Context
• has a consistent vocabulary
• a set of domain behaviors modeled as
functions on domain objects
impleme...
Domain Model = ∪(i) Bounded Context(i)
Bounded Context = { m[T1,T2,..] | T(i) ∈ Types }
Module = { f(x,y,..) | p(x,y) ∈ Do...
• Functions / Morphisms
• Types / Sets
• Composition
• Rules / Laws
algebra
Domain Model Algebra
explicit verifiable
• types
• type constraints
• functions between types
• type constraints
• more con...
Domain Model Algebra
• Algebra as the glue for binding domain model
artifacts
• Algebras evolve by composition
Reusable co...
Algebra of a Monoid
trait Semigroup[A] {
def combine(x: A, y: A): A
}
trait Monoid[A] extends Semigroup[A] {
def empty: A
...
Algebra of a Foldable
trait Foldable[F[_]] {
def foldleft[A,B](as: F[A], z: B, f: (B, A) => B): B
def foldMap[A,B](as: F[A...
Algebraic Combinators
def mapReduce[F[_], A, B](as: F[A])(f: A => B)
(implicit fd: Foldable[F], m: Monoid[B]): B =
fd.fold...
Domain Behaviors
object Payments extends .. {
def valuation(payments: List[Payment]): Money = {
implicit val m: Monoid[Mon...
Algebras => Functional Patterns
• Bits of domain elements evolving from reusable
generic algebras
• The algebras we reuse ...
Domain Model = ∪(i) Bounded Context(i)
Bounded Context = { m[T1,T2,..] | T(i) ∈ Types }
Module = { f(x,y,..) | p(x,y) ∈ Do...
• domain function
• on an object of type x, y, ..
• composes with other functions
• closed under composition
Domain Model ...
Given all the properties of algebra, can we
consider algebraic composition to be the
basis of designing, implementing and ...
Problem Domain
Bank
Account
Trade
Customer
...
...
...
Problem Domain
...
entities
Bank
Account
Trade
Customer
...
...
...
do trade
process
execution
place
order
Problem Domain
...
entities
behaviors
Bank
Account
Trade
Customer
...
...
...
do trade
process
execution
place
order
Problem Domain
...
market
regulations
tax l...
do trade
process
execution
place
order
Solution Domain
...
behaviors
Functions
([Type] => Type)
Bank
Account
Trade
Customer
...
...
...
do trade
process
execution
place
order
Solution Domain
...
entities
behaviors
func...
Bank
Account
Trade
Customer
...
...
...
do trade
process
execution
place
order
Solution Domain
...
market
regulations
tax ...
Bank
Account
Trade
Customer
...
...
...
do trade
process
execution
place
order
Solution Domain
...
market
regulations
tax ...
Bank
Account
Trade
Customer
...
...
...
do trade
process
execution
place
order
Solution Domain
...
market
regulations
tax ...
Client places order
- flexible format
1
Client places order
- flexible format
Transform to internal domain
model entity and place for execution
1 2
Client places order
- flexible format
Transform to internal domain
model entity and place for execution
Trade & Allocate to...
def fromClientOrder: ClientOrder => Order
def execute(market: Market, brokerAccount: Account)
: Order => List[Execution]
d...
Takeaways ..
• Publish as much domain behavior as you can through the
algebra of your APIs. Since algebra is compositional...
.. so we have a decent algebra of our module, the
names reflect the appropriate artifacts from the
domain (ubiquitous langu...
1. Compositionality - How do we compose
the 3 behaviors that we published to
generate trade in the market and allocate
to ...
• Error handling ?
• throw / catch exceptions is not RT
• Partiality ?
• partial functions can report runtime exceptions i...
Side-effects
• Database writes
• Writing to a message queue
• Reading from stdin / files
• Interacting with any external re...
modularity
side-effects don’t compose
.. the semantics of compositionality ..
in the presence of side-effects
The solution is to abstract side-effects into data
types that are pure values for which
referential transparency holds and...
The solution is to abstract side-effects into
data types that are pure values for which
referential transparency holds and...
The solution is to abstract side-effects into
data type-constructors that are pure values
for which referential transparen...
Option[A]
Either[A,B]
(partiality)
(disjunction)
List[A]
(non-determinism)
Reader[E,A]
(read from environment aka dependen...
F[A]
The answer that the
effect computesThe additional stuff
modeling the computation
• The F[_] that we saw is an opaque type - it
has no denotation till we give it one
• The denotation that we give to F[_] ...
def fromClientOrder: ClientOrder => F[Order]
def execute(market: Market, brokerAccount: Account)
: Order => F[List[Executi...
• .. we have intentionally kept the algebra open
for interpretation ..
• .. there are use cases where you would like to
ha...
class TradingInterpreter[F[_]]
(implicit me: MonadError[F, Throwable])
extends Trading {
def fromClientOrder: ClientOrder ...
• .. one lesson in modularity - commit to a
concrete implementation as late as
possible in the design ..
• .. we have just...
The Program
def tradeGeneration[M[_]: Monad](T: Trading[M]) = for {
order <- T.fromClientOrder(cor)
executions <- T.execut...
The Program
def tradeGeneration[M[_]: Monad](T: Trading[M]) = for {
order <- T.fromClientOrder(cor)
executions <- T.execut...
The Program
def tradeGeneration[M[_]: Monad](T: Trading[M]) = for {
order <- T.fromClientOrder(cor)
executions <- T.execut...
The Program
import cats.effect.IO
def tradeGeneration[M[_]: Monad](T: Trading[M]) = for {
order <- T.fromClientOrder(cor)
...
The Program
import monix.eval.Task
def tradeGeneration[M[_]: Monad](T: Trading[M]) = for {
order <- T.fromClientOrder(cor)...
The Program
def tradeGenerationLoggable[M[_]: Monad]
(T: Trading[M], L: Logging[M]) = for {
_ <- L.info("starting order pr...
Raise the level of
abstraction
trait Trading[F[_]] {
def fromClientOrder
: Kleisli[F, ClientOrder, Order]
def execute(mark...
The Program
def tradeGeneration[M[_]: Monad](T: Trading[M])
: Kleisli[M, ClientOrder, List[Trade]] = {
T.fromClientOrder a...
Effects
Side-effects
- Rob Norris at scale.bythebay.io talk - 2017 (https://www.youtube.com/
watch?v=po3wmq4S15A)
“Effects and side-effects are...
Testing
• Testable

• We did not commit to a
concrete type upfront - some
virtues of being lazy with
evaluation

• For mon...
Takeaways
• Modularity in the presence of side-effects is a
challenge
• Algebraic modeling is the key to address this
• Ef...
Takeaways
• Compose effects parametrically
• Honor the law of using the least powerful
abstraction that works
• Be polymor...
Questions?
Architectural Patterns in Building Modular Domain Models
Architectural Patterns in Building Modular Domain Models
Architectural Patterns in Building Modular Domain Models
Architectural Patterns in Building Modular Domain Models
Architectural Patterns in Building Modular Domain Models
Architectural Patterns in Building Modular Domain Models
Architectural Patterns in Building Modular Domain Models
You’ve finished this document.
Download and read it offline.
Upcoming SlideShare
What to Upload to SlideShare
Next
Upcoming SlideShare
What to Upload to SlideShare
Next
Download to read offline and view in fullscreen.

Share

Architectural Patterns in Building Modular Domain Models

Download to read offline

The main theme of the talk is how to use algebraic and functional techniques to build modular domain models that are pure and compositional even in the presence of side-effects. I discuss the use of pure algebraic effects to abstract side-effects thereby keeping the model compositional.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

Architectural Patterns in Building Modular Domain Models

  1. 1. Architectural Patterns in Building Modular Domain Models Debasish Ghosh @debasishg
  2. 2. Architectural Patterns in Building Modular Domain Models Debasish Ghosh @debasishg
  3. 3. Architectural Patterns in Building Modular Domain Models Debasish Ghosh @debasishg
  4. 4. Architectural Patterns in Building Modular Domain Models Debasish Ghosh @debasishg
  5. 5. — John Hughes in Why Functional Programming Matters https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf “[..] modularity is the key to successful programming. Languages that aim to improve productivity must support modular programming well. But new scope rules and mechanisms for separate compilation are not enough — modularity means more than modules. Our ability to decompose a problem into parts depends directly on our ability to glue solutions together.To support modular programming, a language must provide good glue. [..] Using these glues one can modularize programs in new and useful ways[…]. Smaller and more general modules can be reused more widely, easing subsequent programming.This explains why functional programs are so much smaller and easier to write than conventional ones.”
  6. 6. Intent • Domain Model • Domain Model Algebra • Algebraic Combinators • Compositionality • Algebra as the basis of modularization of domain models • Algebraic effects to keep your domain model pure, modular and compositional
  7. 7. Patterns of modularization of domain models
  8. 8. Algebraic patterns of modularization of domain models
  9. 9. Algebraic patterns of modularization of domain models using pure values
  10. 10. Algebraic patterns of modularization of domain models using pure values as effects
  11. 11. Algebraic patterns of modularization of domain models using pure values as effects even in the presence of side-effects
  12. 12. Domain Modeling
  13. 13. Domain Modeling (Functional)
  14. 14. What is a domain model ? A domain model in problem solving and software engineering is a conceptual model of all the topics related to a specific problem. It describes the various entities, their attributes, roles, and relationships, plus the constraints that govern the problem domain. It does not describe the solutions to the problem. Wikipedia (http://en.wikipedia.org/wiki/Domain_model)
  15. 15. The Functional Lens .. “domain API evolution through algebraic composition”
  16. 16. The Functional Lens .. “domain API evolution through algebraic composition” Building larger domain behaviours out of smaller ones
  17. 17. The Functional Lens .. “domain API evolution through algebraic composition” Use composition of pure functions and types
  18. 18. Your domain model is a function
  19. 19. Your domain model is a function
  20. 20. Your domain model is a collection of functions
  21. 21. Your domain model is a collection of functions some simpler models are ..
  22. 22. https://msdn.microsoft.com/en-us/library/jj591560.aspx
  23. 23. A Bounded Context • has a consistent vocabulary • a set of domain behaviors modeled as functions on domain objects implemented as types • each of the behaviors honor a set of business rules • related behaviors grouped as modules
  24. 24. Domain Model = ∪(i) Bounded Context(i) Bounded Context = { m[T1,T2,..] | T(i) ∈ Types } Module = { f(x,y,..) | p(x,y) ∈ Domain Rules } • domain function • on an object of types x, y, .. • composes with other functions • closed under composition • business rules
  25. 25. • Functions / Morphisms • Types / Sets • Composition • Rules / Laws algebra
  26. 26. Domain Model Algebra explicit verifiable • types • type constraints • functions between types • type constraints • more constraints if you have DT • algebraic property based testing (algebra of types, functions & laws of the solution domain model)
  27. 27. Domain Model Algebra • Algebra as the glue for binding domain model artifacts • Algebras evolve by composition Reusable combinators Build larger abstractions out of smaller ones using properties of compositionality
  28. 28. Algebra of a Monoid trait Semigroup[A] { def combine(x: A, y: A): A } trait Monoid[A] extends Semigroup[A] { def empty: A } parametricity
  29. 29. Algebra of a Foldable trait Foldable[F[_]] { def foldleft[A,B](as: F[A], z: B, f: (B, A) => B): B def foldMap[A,B](as: F[A], f: A => B) (implicit m: Monoid[B]): B = foldleft(as, m.zero, (b: B, a: A) => m.combine(b, f(a))) }
  30. 30. Algebraic Combinators def mapReduce[F[_], A, B](as: F[A])(f: A => B) (implicit fd: Foldable[F], m: Monoid[B]): B = fd.foldMap(as)(f) Built out of PURE algebra ONLY Uses the algebras of Monoid and Foldable
  31. 31. Domain Behaviors object Payments extends .. { def valuation(payments: List[Payment]): Money = { implicit val m: Monoid[Money] = MoneyAddMonoid mapReduce(payments)(creditAmount) } def maxPayment(payments: List[Payment]): Money = { implicit val m: Monoid[Money] = MoneyOrderMonoid mapReduce(payments)(creditAmount) } }
  32. 32. Algebras => Functional Patterns • Bits of domain elements evolving from reusable generic algebras • The algebras we reuse already exist - as a designer we provide the implementation of those algebras in the context of the domain model • The algebras are the patterns, the implementations are instances of patterns in the context of our domain model
  33. 33. Domain Model = ∪(i) Bounded Context(i) Bounded Context = { m[T1,T2,..] | T(i) ∈ Types } Module = { f(x,y,..) | p(x,y) ∈ Domain Rules } • domain function • on an object of types x, y • composes with other functions • closed under composition • business rules
  34. 34. • domain function • on an object of type x, y, .. • composes with other functions • closed under composition Domain Model = ∪(i) Bounded Context(i) Bounded Context = { m[T1,T2,..] | T(i) ∈ Types } Module = { f(x, y, .. ) | p(x) ∈ Domain Rules } • business rules (algebra) (algebra)
  35. 35. Given all the properties of algebra, can we consider algebraic composition to be the basis of designing, implementing and modularizing domain models ?
  36. 36. Problem Domain
  37. 37. Bank Account Trade Customer ... ... ... Problem Domain ... entities
  38. 38. Bank Account Trade Customer ... ... ... do trade process execution place order Problem Domain ... entities behaviors
  39. 39. Bank Account Trade Customer ... ... ... do trade process execution place order Problem Domain ... market regulations tax laws brokerage commission rates ... entities behaviors laws
  40. 40. do trade process execution place order Solution Domain ... behaviors Functions ([Type] => Type)
  41. 41. Bank Account Trade Customer ... ... ... do trade process execution place order Solution Domain ... entities behaviors functions ([Type] => Type) algebraic data type
  42. 42. Bank Account Trade Customer ... ... ... do trade process execution place order Solution Domain ... market regulations tax laws brokerage commission rates ... entities behaviors laws functions ([Type] => Type) algebraic data type business rules / invariants
  43. 43. Bank Account Trade Customer ... ... ... do trade process execution place order Solution Domain ... market regulations tax laws brokerage commission rates ... entities behaviors laws functions ([Type] => Type) algebraic data type business rules / invariants Monoid Monad ... reusable algebra
  44. 44. Bank Account Trade Customer ... ... ... do trade process execution place order Solution Domain ... market regulations tax laws brokerage commission rates ... entities behaviors laws functions ([Type] => Type) algebraic data type business rules / invariants Monoid Monad ... Domain Algebra
  45. 45. Client places order - flexible format 1
  46. 46. Client places order - flexible format Transform to internal domain model entity and place for execution 1 2
  47. 47. Client places order - flexible format Transform to internal domain model entity and place for execution Trade & Allocate to client accounts 1 2 3
  48. 48. def fromClientOrder: ClientOrder => Order def execute(market: Market, brokerAccount: Account) : Order => List[Execution] def allocate(accounts: List[Account]) : List[Execution] => List[Trade] trait Trading { } trait TradeComponent extends Trading with Logging with Auditing algebra of domain behaviors / functions functions aggregate upwards into modules modules aggregate into larger modules
  49. 49. Takeaways .. • Publish as much domain behavior as you can through the algebra of your APIs. Since algebra is compositional, it’s easy to extend later by stacking abstractions on top of existing ones. • Types are important but in domain modeling, names must come from the vocabulary of the domain - Ubiquitous Language • Group related functions into modules. Modules are also compositional in Scala. • Functions aggregate into modules, modules aggregate into components.
  50. 50. .. so we have a decent algebra of our module, the names reflect the appropriate artifacts from the domain (ubiquitous language), the types are well published and we are quite explicit in what the behaviors do ..
  51. 51. 1. Compositionality - How do we compose the 3 behaviors that we published to generate trade in the market and allocate to client accounts ? 2. Side-effects - We need to compose them alongside all side-effects that form a core part of all non trivial domain model implementations
  52. 52. • Error handling ? • throw / catch exceptions is not RT • Partiality ? • partial functions can report runtime exceptions if invoked with unhandled arguments (violates RT) • Reading configuration information from environment ? • may result in code repetition if not properly handled • Logging ? • side-effects Side-effects
  53. 53. Side-effects • Database writes • Writing to a message queue • Reading from stdin / files • Interacting with any external resource • Changing state in place
  54. 54. modularity side-effects don’t compose
  55. 55. .. the semantics of compositionality .. in the presence of side-effects
  56. 56. The solution is to abstract side-effects into data types that are pure values for which referential transparency holds and which can be composed with other pure functional abstractions
  57. 57. The solution is to abstract side-effects into data types that are pure values for which referential transparency holds and which can be composed with other pure functional abstractions
  58. 58. The solution is to abstract side-effects into data type-constructors that are pure values for which referential transparency holds and which can be composed with other pure functional abstractions Effects
  59. 59. Option[A] Either[A,B] (partiality) (disjunction) List[A] (non-determinism) Reader[E,A] (read from environment aka dependency Injection) Writer[W,A] (logging) State[S,A] (state management) IO[A] (external side-effects) .. and there are many many more ..
  60. 60. F[A] The answer that the effect computesThe additional stuff modeling the computation
  61. 61. • The F[_] that we saw is an opaque type - it has no denotation till we give it one • The denotation that we give to F[_] depends on the semantics of compositionality that we would like to have for our domain model behaviors
  62. 62. def fromClientOrder: ClientOrder => F[Order] def execute(market: Market, brokerAccount: Account) : Order => F[List[Execution]] def allocate(accounts: List[Account]) : List[Execution] => F[List[Trade]] trait Trading { } • We haven’t yet given any denotation to the effect type • We haven’t yet committed to any concrete effect type
  63. 63. • .. we have intentionally kept the algebra open for interpretation .. • .. there are use cases where you would like to have multiple interpreters for the same algebra ..
  64. 64. class TradingInterpreter[F[_]] (implicit me: MonadError[F, Throwable]) extends Trading { def fromClientOrder: ClientOrder => F[Order] = makeOrder(_) match { case Left(dv) => me.raiseError(new Exception(dv.message)) case Right(o) => o.pure[F] } def execute(market: Market, brokerAccount: Account) : Order => F[List[Execution]] = ... def allocate(accounts: List[Account]) : List[Execution] => F[List[Trade]] = ... } One Sample Interpreter
  65. 65. • .. one lesson in modularity - commit to a concrete implementation as late as possible in the design .. • .. we have just indicated that we want a monadic effect - we haven’t committed to any concrete monad type even in the interpreter ..
  66. 66. The Program def tradeGeneration[M[_]: Monad](T: Trading[M]) = for { order <- T.fromClientOrder(cor) executions <- T.execute(m1, ba, order) trades <- T.allocate(List(ca1, ca2, ca3), executions) } yield trades
  67. 67. The Program def tradeGeneration[M[_]: Monad](T: Trading[M]) = for { order <- T.fromClientOrder(cor) executions <- T.execute(m1, ba, order) trades <- T.allocate(List(ca1, ca2, ca3), executions) } yield trades depends on the algebra only
  68. 68. The Program def tradeGeneration[M[_]: Monad](T: Trading[M]) = for { order <- T.fromClientOrder(cor) executions <- T.execute(m1, ba, order) trades <- T.allocate(List(ca1, ca2, ca3), executions) } yield trades depends on the algebra only the story of what needs to be done, NOT HOW
  69. 69. The Program import cats.effect.IO def tradeGeneration[M[_]: Monad](T: Trading[M]) = for { order <- T.fromClientOrder(cor) executions <- T.execute(m1, ba, order) trades <- T.allocate(List(ca1, ca2, ca3), executions) } yield trades object TradingComponent extends TradingInterpreter[IO] tradeGeneration(TradingComponent).unsafeRunSync
  70. 70. The Program import monix.eval.Task def tradeGeneration[M[_]: Monad](T: Trading[M]) = for { order <- T.fromClientOrder(cor) executions <- T.execute(m1, ba, order) trades <- T.allocate(List(ca1, ca2, ca3), executions) } yield trades object TradingComponent extends TradingInterpreter[Task] tradeGeneration(TradingComponent)
  71. 71. The Program def tradeGenerationLoggable[M[_]: Monad] (T: Trading[M], L: Logging[M]) = for { _ <- L.info("starting order processing") order <- T.fromClientOrder(cor) executions <- T.execute(m1, ba, order) trades <- T.allocate(List(ca1, ca2, ca3), executions) _ <- L.info("allocation done") } yield trades object TradingComponent extends TradingInterpreter[IO] object LoggingComponent extends LoggingInterpreter[IO] tradeGenerationLoggable(TradingComponent, LoggingComponent).unsafeRunSync
  72. 72. Raise the level of abstraction trait Trading[F[_]] { def fromClientOrder : Kleisli[F, ClientOrder, Order] def execute(market: Market, brokerAccount: Account) : Kleisli[F, Order, List[Execution]] def allocate(accounts: List[Account]) : Kleisli[F, List[Execution], List[Trade]] }
  73. 73. The Program def tradeGeneration[M[_]: Monad](T: Trading[M]) : Kleisli[M, ClientOrder, List[Trade]] = { T.fromClientOrder andThen T.execute(m1, ba) andThen T.allocate(List(ca1, ca2, ca3)) } object TradingComponent extends TradingInterpreter[IO] val tk = tradeGeneration(TradingComponent) tk(cor).unsafeRunSync
  74. 74. Effects Side-effects
  75. 75. - Rob Norris at scale.bythebay.io talk - 2017 (https://www.youtube.com/ watch?v=po3wmq4S15A) “Effects and side-effects are not the same thing. Effects are good, side-effects are bugs.Their lexical similarity is really unfortunate because people often conflate the two ideas”
  76. 76. Testing • Testable • We did not commit to a concrete type upfront - some virtues of being lazy with evaluation • For monadic effects, easier testing with the Id monad - just use a different implementation for the same algebra def tradeGenerationLoggable[M[_]: Monad] (T: Trading[M], L: Logging[M]) = for { _ <- L.info("starting order processing") order <- T.fromClientOrder(cor) executions <- T.execute(m1, ba, order) trades <- T.allocate(List(ca1, ca2, ca3), executions) _ <- L.info("allocation done") } yield trades
  77. 77. Takeaways • Modularity in the presence of side-effects is a challenge • Algebraic modeling is the key to address this • Effects as algebras are pure values that can compose based on laws • Determine the type of effect based on the semantics of compositionality of your domain behaviors
  78. 78. Takeaways • Compose effects parametrically • Honor the law of using the least powerful abstraction that works • Be polymorphic (parametric) as early as you can, commit to concrete types as late as you can
  79. 79. Questions?
  • jcversan1

    Jan. 2, 2018
  • yevgenpolyak

    Dec. 15, 2017
  • AravindYarram

    Dec. 15, 2017

The main theme of the talk is how to use algebraic and functional techniques to build modular domain models that are pure and compositional even in the presence of side-effects. I discuss the use of pure algebraic effects to abstract side-effects thereby keeping the model compositional.

Views

Total views

1,136

On Slideshare

0

From embeds

0

Number of embeds

65

Actions

Downloads

29

Shares

0

Comments

0

Likes

3

×