2. 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)
Wednesday, 23 September 15
3. Rich domain
models
State Behavior
Class
• Class models the domain abstraction
• Contains both the state and the
behavior together
• State hidden within private access
specifier for fear of being mutated
inadvertently
• Decision to take - what should go inside
a class ?
• Decision to take - where do we put
behaviors that involve multiple classes ?
Often led to bloated service classes
State Behavior
Wednesday, 23 September 15
4. •Algebraic Data Type (ADT) models the
domain abstraction
• Contains only the defining state as
immutable values
• No need to make things “private” since
we are talking about immutable values
• Nothing but the bare essential
definitions go inside an ADT
•All behaviors are outside the ADT in
modules as functions that define the
domain behaviors
Lean domain
models
Immutable
State
Behavior
Immutable
State
Behavior
Algebraic Data Types Functions in modules
Wednesday, 23 September 15
5. Rich domain
models
State Behavior
Class
•We start with the class design
• Make it sufficiently “rich” by putting all
related behaviors within the class, used to
call them fine grained abstractions
•We put larger behaviors in the form of
services (aka managers) and used to call
them coarse grained abstractions
State Behavior
Wednesday, 23 September 15
6. Lean domain
models
Immutable
State
Behavior
•We start with the functions, the
behaviors of the domain
•We define function algebras using types
that don’t have any implementation yet
(we will see examples shortly)
• Primary focus is on compositionality that
enables building larger functions out of
smaller ones
• Functions reside in modules which also
compose
• Entities are built with algebraic data
types that implement the types we used in
defining the functions
Immutable
State
Behavior
Algebraic Data Types Functions in modules
Wednesday, 23 September 15
7. The Functional Lens ..
“domain API evolution through algebraic
composition”
Wednesday, 23 September 15
8. • Set of behaviors
• Can be composed
• Usually closed under composition
• Set of business rules
Domain Model = { f(x) | P(x) Є { domain rules }}
x has a type
Wednesday, 23 September 15
11. Domain Model Algebra
(algebra of types, functions & laws)
explicit
• types
• type constraints
• expression in terms of other generic algebra
Wednesday, 23 September 15
12. Domain Model Algebra
(algebra of types, functions & laws)
explicit
verifiable
• types
• type constraints
• expression in terms of other generic algebra
• type constraints
• more constraints if you have DT
• algebraic property based testing
Wednesday, 23 September 15
21. A Monoid
An algebraic structure
having
• an identity element
• a binary associative
operation
trait Monoid[A] {
def zero: A
def op(l: A, r: => A): A
}
object MonoidLaws {
def associative[A: Equal: Monoid](a1:
A, a2: A, a3: A): Boolean = //..
def rightIdentity[A: Equal: Monoid](a:
A) = //..
def leftIdentity[A: Equal: Monoid](a:
A) = //..
}
Wednesday, 23 September 15
22. Monoid Laws
An algebraic structure
havingsa
• an identity element
• a binary associative
operation
satisfies
op(x, zero) == x and op(zero, x) == x
satisfies
op(op(x, y), z) == op(x, op(y, z))
trait Monoid[A] {
def zero: A
def op(l: A, r: => A): A
}
object MonoidLaws {
def associative[A: Equal: Monoid](a1:
A, a2: A, a3: A): Boolean = //..
def rightIdentity[A: Equal: Monoid](a:
A) = //..
def leftIdentity[A: Equal: Monoid](a:
A) = //..
}
Wednesday, 23 September 15
23. A Monoid
• generic
• domain independent
• context unaware
trait Monoid[A] {
def zero: A
def op(l: A, r: => A): A
}
Wednesday, 23 September 15
24. A Monoid
trait Monoid[A] {
def zero: A
def op(l: A, r: => A): A
}
• generic
• domain independent
• context unaware
implicit def MoneyPlusMonoid = new Monoid[Money] {
def zero = //..
def op(m1: Money, m2: Money) = //..
}
• context of the domain
Wednesday, 23 September 15
25. A Monoid
trait Monoid[A] {
def zero: A
def op(l: A, r: => A): A
}
• generic
• domain independent
• context unaware
implicit def MoneyPlusMonoid = new Monoid[Money] {
def zero = //..
def op(m1: Money, m2: Money) = //..
}
• context of the domain
(algebra)
(interpretation)
Wednesday, 23 September 15
26. A Monoid
trait Monoid[A] {
def zero: A
def op(l: A, r: => A): A
}
• generic
• domain independent
• context unaware
implicit def MoneyPlusMonoid = new Monoid[Money] {
def zero = //..
def op(m1: Money, m2: Money) = //..
}
• context of the domain
(algebra)
(interpretation)
(reusable across contexts)
(varies with context)
Wednesday, 23 September 15
32. .. so we talk about domain algebra, where the
domain entities are implemented with sets of
types and domain behaviors are functions that
map a type to one or more types.And
domain rules are the laws which define the
constraints of the business ..
Wednesday, 23 September 15
33. Functional Modeling encourages Algebraic API Design
which leads to organic evolution of domain models
Wednesday, 23 September 15
36. Client places order
- flexible format
Transform to internal domain
model entity and place for execution
1 2
Wednesday, 23 September 15
37. Client places order
- flexible format
Transform to internal domain
model entity and place for execution
Trade & Allocate to
client accounts
1 2
3
Wednesday, 23 September 15
40. def clientOrders: ClientOrderSheet => List[Order]
def execute: Market => Account => Order => List[Execution]
def allocate: List[Account] => Execution => List[Trade]
Types out of thin air No implementation till now
Type names resonate domain language
Wednesday, 23 September 15
44. Algebraic Design
• The algebra is the binding contract of the
API
• Implementation is NOT part of the algebra
• An algebra can have multiple interpreters
(aka implementations)
• One of the core principles of functional
programming is to decouple the algebra from
the interpreter
Wednesday, 23 September 15
45. def clientOrders: ClientOrderSheet => List[Order]
def execute: Market => Account => Order => List[Execution]
def allocate: List[Account] => Execution => List[Trade]
let’s do some algebra ..
Wednesday, 23 September 15
46. def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s do some algebra ..
Wednesday, 23 September 15
47. def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s do some algebra ..
Wednesday, 23 September 15
48. def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s do some algebra ..
Wednesday, 23 September 15
49. def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s do some algebra ..
Wednesday, 23 September 15
50. def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s do some algebra ..
Wednesday, 23 September 15
51. def f: A => List[B]
def g: B => List[C]
def h: C => List[D]
.. a problem of composition ..
Wednesday, 23 September 15
52. .. a problem of
composition with effects ..
def f: A => List[B]
def g: B => List[C]
def h: C => List[D]
Wednesday, 23 September 15
53. def f[M: Monad]: A => M[B]
def g[M: Monad]: B => M[C]
def h[M: Monad]: C => M[D]
.. a problem of composition with
effects that can be generalized ..
Wednesday, 23 September 15
54. def f[M: Monad]: A => M[B]
def g[M: Monad]: B => M[C]
.. a problem of composition with
effects that can be generalized ..
Wednesday, 23 September 15
55. def f[M: Monad]: A => M[B]
def g[M: Monad]: B => M[C]
.. a problem of composition with
effects that can be generalized ..
Define a mapping
M[B] => B
Wednesday, 23 September 15
56. def f[M: Monad]: A => M[B]
def g[M: Monad]: B => M[C]
.. a problem of composition with
effects that can be generalized ..
Define a mapping
M[B] => B
Wednesday, 23 September 15
57. def f[M: Monad]: A => M[B]
def g[M: Monad]: B => M[C]
.. a problem of composition with
effects that can be generalized ..
m.map(f(a))(g)
Wednesday, 23 September 15
58. def f[M: Monad]: A => M[B]
def g[M: Monad]: B => M[C]
.. a problem of composition with
effects that can be generalized ..
m.map(f(a))(g) M[M[C]]
Wednesday, 23 September 15
59. def f[M: Monad]: A => M[B]
def g[M: Monad]: B => M[C]
.. a problem of composition with
effects that can be generalized ..
m.join(m.map(f(a))(g)) M[C]
Wednesday, 23 September 15
60. def f[M: Monad]: A => M[B]
def g[M: Monad]: B => M[C]
.. a problem of composition with
effects that can be generalized ..
andThen
Wednesday, 23 September 15
61. .. the glue (combinator) ..
def andThen[M[_], A, B, C](f: A => M[B], g: B => M[C])
(implicit m: Monad[M]): A => M[C] = {(a: A) =>
m.join(m.map(f(a))(g))
}
Wednesday, 23 September 15
62. case class Kleisli[M[_], A, B](run: A => M[B]) {
def andThen[C](f: B => M[C])
(implicit M: Monad[M]): Kleisli[M, A, C] =
Kleisli((a: A) => M.flatMap(run(a))(f))
}
.. function composition with
Effects ..
It’s a Kleisli !
Wednesday, 23 September 15
67. def tradeGeneration(
market: Market,
broker: Account,
clientAccounts: List[Account]) = {
clientOrders andThen
execute(market, broker) andThen
allocate(clientAccounts)
}
Implementation follows the specification
and we get the Ubiquitous Language for
free :-)
.. the complete trade generation
logic ..
Wednesday, 23 September 15
68. algebraic & functional
• Just Pure Functions. Lower cognitive load -
don’t have to think of the classes & data
members where behaviors will reside
• Compositional. Algebras compose - we
defined the algebras of our domain APIs in
terms of existing, time tested algebras of
Kleislis and Monads
Wednesday, 23 September 15
69. def clientOrders: Kleisli[List, ClientOrderSheet, Order]
def execute(m: Market, b: Account): Kleisli[List, Order, Execution]
def allocate(acts: List[Account]): Kleisli[List, Execution, Trade]
.. our algebra still doesn’t handle errors
that may occur within our domain
behaviors ..
.. function composition with
Effects ..
Wednesday, 23 September 15
72. def clientOrders: Kleisli[List, ClientOrderSheet, Order]
return type constructor
What happens in case the operation fails ?
Wednesday, 23 September 15
73. Error handling as an
Effect
• pure and functional
• with an explicit and published algebra
• stackable with existing effects
def clientOrders: Kleisli[List, ClientOrderSheet, Order]
Wednesday, 23 September 15
75. def clientOrders: Kleisli[List, ClientOrderSheet, Order]
.. stacking of effects ..
M[List[_]]: M is a Monad
Wednesday, 23 September 15
76. type Response[A] = String / Option[A]
val count: Response[Int] = some(10).right
for {
maybeCount <- count
} yield {
for {
c <- maybeCount
// use c
} yield c
}
Monad Transformers
Wednesday, 23 September 15
77. type Response[A] = String / Option[A]
val count: Response[Int] = some(10).right
for {
maybeCount <- count
} yield {
for {
c <- maybeCount
// use c
} yield c
}
type Error[A] = String / A
type Response[A] = OptionT[Error, A]
val count: Response[Int] = 10.point[Response]
for{
c <- count
// use c : c is an Int here
} yield (())
Monad Transformers
Wednesday, 23 September 15
78. type Response[A] = String / Option[A]
val count: Response[Int] = some(10).right
for {
maybeCount <- count
} yield {
for {
c <- maybeCount
// use c
} yield c
}
type Error[A] = String / A
type Response[A] = OptionT[Error, A]
val count: Response[Int] = 10.point[Response]
for{
c <- count
// use c : c is an Int here
} yield (())
Monad Transformers
richer algebra
Wednesday, 23 September 15
79. Monad Transformers
• collapses the stack and gives us a single
monad to deal with
• order of stacking is important though
Wednesday, 23 September 15
80. def clientOrders: Kleisli[List, ClientOrderSheet, Order]
.. stacking of effects ..
case class ListT[M[_], A] (run: M[List[A]]) { //..
Wednesday, 23 September 15
82. type StringOr[A] = String / A
type Valid[A] = ListT[StringOr, A]
Wednesday, 23 September 15
83. type StringOr[A] = String / A
type Valid[A] = ListT[StringOr, A]
def clientOrders: Kleisli[Valid, ClientOrderSheet, Order]
def execute(m: Market, b: Account): Kleisli[Valid, Order, Execution]
def allocate(acts: List[Account]): Kleisli[Valid, Execution, Trade]
Wednesday, 23 September 15
84. type StringOr[A] = String / A
type Valid[A] = ListT[StringOr, A]
def clientOrders: Kleisli[Valid, ClientOrderSheet, Order]
def execute(m: Market, b: Account): Kleisli[Valid, Order, Execution]
def allocate(acts: List[Account]): Kleisli[Valid, Execution, Trade]
.. a small change in algebra, a huge step
for our domain model ..
Wednesday, 23 September 15
91. .. the algebra ..
trait OrderLaw {
def sizeLaw: Seq[ClientOrder] => Seq[Order] => Boolean =
{ cos => orders =>
cos.size == orders.size
}
def lineItemLaw: Seq[ClientOrder] => Seq[Order] => Boolean =
{ cos => orders =>
cos.map(instrumentsInClientOrder).sum ==
orders.map(_.items.size).sum
}
}
laws of the algebra
(domain rules)
Wednesday, 23 September 15
92. Domain Rules as
Algebraic Properties
• part of the abstraction
• equally important as the actual abstraction
• verifiable as properties
Wednesday, 23 September 15
93. .. domain rules verification ..
property("Check Client Order laws") =
forAll((cos: Set[ClientOrder]) => {
val orders = for {
os <- clientOrders.run(cos.toList)
} yield os
sizeLaw(cos.toSeq)(orders) == true
lineItemLaw(cos.toSeq)(orders) == true
})
property based testing FTW ..
Wednesday, 23 September 15
95. a useful pattern for
decoupling algebra from
implementation
Wednesday, 23 September 15
96. Repository
• store domain objects
• query domain objects
• single point of interface of the domain
model
Wednesday, 23 September 15
97. sealed trait AccountRepoF[+A]
case class Query[+A](no: String, onResult: Account => A) extends AccountRepoF[A]
case class Store[+A](account: Account, next: A) extends AccountRepoF[A]
case class Delete[+A](no: String, next: A) extends AccountRepoF[A]
algebra of the repository ..
• pure
• compositional
• implementation independent
continuation hole
Wednesday, 23 September 15
98. object AccountRepoF {
implicit val functor: Functor[AccountRepoF] = new Functor[AccountRepoF] {
def map[A,B](action: AccountRepoF[A])(f: A => B): AccountRepoF[B] =
action match {
case Store(account, next) => Store(account, f(next))
case Query(no, onResult) => Query(no, onResult andThen f)
case Delete(no, next) => Delete(no, f(next))
}
}
}
define a functor for the algebra ..
Wednesday, 23 September 15
99. object AccountRepoF {
implicit val functor: Functor[AccountRepoF] = new Functor[AccountRepoF] {
def map[A,B](action: AccountRepoF[A])(f: A => B): AccountRepoF[B] =
action match {
case Store(account, next) => Store(account, f(next))
case Query(no, onResult) => Query(no, onResult andThen f)
case Delete(no, next) => Delete(no, f(next))
}
}
}
define a functor for the algebra ..
you get a free monad ..
type AccountRepo[A] = Free[AccountRepoF, A]
Wednesday, 23 September 15
100. lift your algebra into the context
of the free monad ..
trait AccountRepository {
def store(account: Account): AccountRepo[Unit] =
liftF(Store(account, ()))
def query(no: String): AccountRepo[Account] =
liftF(Query(no, identity))
def delete(no: String): AccountRepo[Unit] =
liftF(Delete(no, ()))
}
Wednesday, 23 September 15
101. lift your algebra into the context
of the free monad ..
trait AccountRepository {
def store(account: Account): AccountRepo[Unit] =
liftF(Store(account, ()))
def query(no: String): AccountRepo[Account] =
liftF(Query(no, identity))
def delete(no: String): AccountRepo[Unit] =
liftF(Delete(no, ()))
def update(no: String, f: Account => Account): AccountRepo[Unit] = for {
a <- query(no)
_ <- store(f(a))
} yield ()
}
Wednesday, 23 September 15
102. def open(no: String, name: String, openingDate: Option[Date]) =
for {
_ <- store(Account(no, name, openingDate.get))
a <- query(no)
} yield a
build larger abstractions
monadically ..
.. and you get back a free monad
Wednesday, 23 September 15
103. def open(no: String, name: String, openingDate: Option[Date]) =
for {
_ <- store(Account(no, name, openingDate.get))
a <- query(no)
} yield a
build larger abstractions
monadically ..
.. and you get back a free monad
.. with 2 operations chained in sequence
Wednesday, 23 September 15
104. def open(no: String, name: String, openingDate: Option[Date]) =
for {
_ <- store(Account(no, name, openingDate.get))
a <- query(no)
} yield a
build larger abstractions
monadically ..
.. and you get back a free monad
.. with 2 operations chained in sequence
.. just the algebra, no semantics
Wednesday, 23 September 15
105. Essence of the Pattern
• We have built the entire model of
computation without any semantics, just the
algebra
• Just a description of what we intend to do
• Not surprising that it’s a pure abstraction
Wednesday, 23 September 15
106. Essence of the Pattern
• Now we can provide as many interpreters as
we wish depending on the usage / context
• 1 interpreter for testing that tests the
repository actions against an in-memory
data structure
• 1 interpreter for production that uses an
RDBMS
Wednesday, 23 September 15
107. a sample interpreter structure ..
def interpret[A](script: AccountRepo[A], ls: List[String]): List[String] =
script.fold(_ => ls, {
case Query(no, onResult) =>
interpret(..)
case Store(account, next) =>
interpret(..)
case Delete(no, next) =>
interpret(..)
})
Interpret the whole abstraction and
provide the implementation in context
Wednesday, 23 September 15
108. Intuition ..
• The larger algebra formed from each
individual algebra element is merely a
collection without any interpretation,
something like an AST
• The interpreter provides the context and
the implementation of each of the algebra
elements under that specific context
Wednesday, 23 September 15
110. algebraic design
• evolution based on contracts / types /
interfaces without any dependency on
implementation
Wednesday, 23 September 15
111. algebraic design
• evolves straight from the domain use cases
using domain vocabulary (ubiquitous
language falls in place because of this direct
correspondence)
Wednesday, 23 September 15
112. algebraic design
• modular and hence pluggable. Each of the
API that we discussed can be plugged off
the specific use case and independently
used in other use cases
Wednesday, 23 September 15
113. algebraic design
• pure, referentially transparent and hence
testable in isolation
Wednesday, 23 September 15
114. algebraic design
• compositional, composes with the domain
algebra and with the other categorical
algebras inheriting their semantics
seamlessly into the domain model e.g.
effectful composition with kleislis and fail-
fast error handling with monads
Wednesday, 23 September 15
115. When using functional modeling, always try to express
domain specific abstractions and behaviors in terms of more
generic, lawful abstractions. Doing this you make your
functions more generic, more usable in a broader context
and yet simpler to comprehend.
Wednesday, 23 September 15