This document discusses functional patterns in domain modeling, with examples from the financial domain. It begins by defining what a domain model is. It then discusses rich domain models using classes versus lean domain models using algebraic data types and functions. Some common patterns for domain modeling are described, including using algebraic data types to represent entities and functions to represent behaviors. The document provides examples of modeling a financial domain around trading using types and functions. It discusses how this functional approach leads to an algebraic API design that can organically evolve. Overall the document advocates for applying functional programming concepts and patterns to domain modeling.
Your Vision, Our Expertise: TECUNIQUE's Tailored Software Teams
Functional Patterns in Domain Modeling
1. Functional Patterns in
Domain Modeling
with examples from the Financial Domain
@debasishg
https://github.com/debasishg
http://debasishg.blogspot.com
Wednesday, 5 November 14
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, 5 November 14
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, 5 November 14
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, 5 November 14
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, 5 November 14
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, 5 November 14
8. Domain Model
Elements
• Entities & Value Objects - modeled with
types
• Behaviors - modeled with functions
• Domain rules - expressed as constraints
& validations
• Bounded Context - delineates
subsystems within the model
• Ubiquitous Language
Wednesday, 5 November 14
12. Why Functional ?
• Ability to reason about your code - virtues
of being pure & referentially transparent
• Increased modularity - clean separation of
state and behavior
• Immutable data structures
• Concurrency
Wednesday, 5 November 14
14. Bank
Account
Trade
Customer
...
...
...
Problem Domain
...
entities
Wednesday, 5 November 14
15. place
order Problem Domain
Bank
Account
Trade
Customer
...
...
...
do trade
process
execution
...
entities
behaviors
Wednesday, 5 November 14
16. place
order Problem Domain
Bank
Account
Trade
Customer
...
...
...
do trade
process
execution
...
market
regulations
tax laws
brokerage
commission
rates ...
entities
behaviors
laws
Wednesday, 5 November 14
17. place
order Problem Domain
Bank
Account
Trade
Customer
...
...
...
do trade
process
execution
...
market
regulations
tax laws
brokerage
commission
rates ...
entities
behaviors
laws
Wednesday, 5 November 14
18. place
order Problem Domain
Solution Domain
behaviors • Functions
do trade
process
execution
...
• On Types
• Constraints
Wednesday, 5 November 14
19. place
order Problem Domain
Solution Domain
behaviors • Functions
do trade
process
execution
...
• On Types
• Constraints
Algebra
• Morphisms
• Sets
• Laws
Wednesday, 5 November 14
20. place
order Problem Domain
Solution Domain
behaviors • Functions
do trade
process
execution
...
• On Types
• Constraints
Algebra
• Morphisms
• Sets
• Laws
Compose for larger abstractions
Wednesday, 5 November 14
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, 5 November 14
22. Monoid Laws
An algebraic structure
havingsa
• 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) = //..
}
satisfies
op(x, zero) == x and op(zero, x) == x
satisfies
op(op(x, y), z) == op(x, op(y, z))
Wednesday, 5 November 14
23. .. and 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, 5 November 14
24. Pattern #1: Functional Modeling encourages Algebraic API
Design which leads to organic evolution of domain
models
Wednesday, 5 November 14
26. Client places order
- flexible format
1 2
Transform to internal domain
model entity and place for execution
Wednesday, 5 November 14
27. Client places order
- flexible format
1 2
Transform to internal domain
model entity and place for execution
Trade & Allocate to
client accounts
3
Wednesday, 5 November 14
29. Types out of thin air No implementation till now
def clientOrders: ClientOrderSheet => List[Order]
def execute: Market => Account => Order => List[Execution]
def allocate: List[Account] => Execution => List[Trade]
Wednesday, 5 November 14
30. Types out of thin air No implementation till now
def clientOrders: ClientOrderSheet => List[Order]
def execute: Market => Account => Order => List[Execution]
def allocate: List[Account] => Execution => List[Trade]
Just some types & operations on those types
+
some laws governing those operations
Wednesday, 5 November 14
31. Types out of thin air No implementation till now
def clientOrders: ClientOrderSheet => List[Order]
Algebra of the API
def execute: Market => Account => Order => List[Execution]
def allocate: List[Account] => Execution => List[Trade]
Just some types & operations on those types
+
some laws governing those operations
Wednesday, 5 November 14
32. 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)
• The core principle of functional
programming is to decouple the algebra from
the interpreter
Wednesday, 5 November 14
43. 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 :-)
let’s mine some
patterns ..
Wednesday, 5 November 14
45. Just as the doctor
ordered ..
✓Making implicit concepts
explicit
✓Intention revealing
interfaces
✓Side-effect free functions
✓Declarative design
Wednesday, 5 November 14
46. Just as the doctor
ordered ..
✓types as the
Making implicit functions concepts
& explicit
patterns for
on
Claim: With these ✓glue we get all based binding Intention generic revealing
abstractions free using function composition
interfaces
✓Side-effect free functions
✓Declarative design
Wednesday, 5 November 14
47. Pattern #2: DDD patterns like Factories & Specification
are part of the normal idioms of functional programming
Wednesday, 5 November 14
48. /**
* allocates an execution to a List of client accounts
* generates a List of trades
*/
def allocate(accounts: List[Account]): Kleisli[List, Execution, Trade] =
kleisli { execution =>
accounts.map { account =>
makeTrade(account,
execution.instrument,
genRef(),
execution.market,
execution.unitPrice,
execution.quantity / accounts.size
)
}
}
Wednesday, 5 November 14
49. /**
* allocates an execution to a List of client accounts
* generates a List of trades
*/
def allocate(accounts: List[Account]): Kleisli[List, Execution, Trade] =
kleisli { execution =>
accounts.map { account =>
makeTrade(account,
execution.instrument,
genRef(),
execution.market,
execution.unitPrice,
execution.quantity / accounts.size
)
}
}
Wednesday, 5 November 14
50. /**
* allocates an execution to a List of client accounts
* generates a List of trades
*/
def allocate(accounts: List[Account]): Kleisli[List, Execution, Trade] =
kleisli { execution =>
accounts.map { account =>
makeTrade(account,
execution.instrument,
genRef(),
execution.market,
execution.unitPrice,
execution.quantity / accounts.size
)
}
}
Makes a Trade out of the
parameters passed
What about validations ?
How do we handle failures ?
Wednesday, 5 November 14
51. case class Trade (account: Account,
instrument: Instrument,
refNo: String,
market: Market,
unitPrice: BigDecimal,
quantity: BigDecimal,
tradeDate: Date = today,
valueDate: Option[Date] = None,
taxFees: Option[List[(TaxFeeId, BigDecimal)]] = None,
netAmount: Option[BigDecimal] = None
)
Wednesday, 5 November 14
52. must be > 0
case class Trade (account: Account,
instrument: Instrument,
refNo: String,
market: Market,
unitPrice: BigDecimal,
quantity: BigDecimal,
tradeDate: Date = today,
valueDate: Option[Date] = None,
taxFees: Option[List[(TaxFeeId, BigDecimal)]] = None,
netAmount: Option[BigDecimal] = None
)
must be > trade date
Wednesday, 5 November 14
54. monadic validation pattern
def makeTrade(account: Account,
instrument: Instrument,
refNo: String,
market: Market,
unitPrice: BigDecimal,
quantity: BigDecimal,
td: Date = today,
vd: Option[Date] = None): ValidationStatus[Trade] = {
val trd = Trade(account, instrument, refNo,
market, unitPrice, quantity, td, vd)
val s = for {
_ <- validQuantity
_ <- validValueDate
t <- validUnitPrice
} yield t
s(trd)
}
Wednesday, 5 November 14
55. monadic validation pattern
def makeTrade(account: Account,
instrument: Instrument,
refNo: String,
market: Market,
unitPrice: BigDecimal,
quantity: BigDecimal,
td: Date = today,
vd: Option[Date] = None): ValidationStatus[Trade] = {
val trd = Trade(account, instrument, refNo,
market, unitPrice, quantity, td, vd)
val s = for {
_ <- validQuantity
_ <- validValueDate
t <- validUnitPrice
} yield t
s(trd)
}
(monad
comprehension)
Wednesday, 5 November 14
56. monadic validation pattern
Smart Constructor : The
def makeTrade(account: Account,
instrument: Instrument,
refNo: String,
market: Market,
unitPrice: BigDecimal,
quantity: BigDecimal,
td: Date = today,
vd: Option[Date] = None): ValidationStatus[Trade] = {
val trd = Trade(account, instrument, refNo,
Factory Pattern
(Chapter 6)
market, unitPrice, quantity, td, vd)
val s = for {
_ <- validQuantity
_ <- validValueDate
t <- validUnitPrice
} yield t
s(trd)
}
The Specification
Pattern
(Chapter 9)
Wednesday, 5 November 14
57. let’s mine some more
patterns (with types) .. disjunction type (a sum
type ValidationStatus[S] = /[String, S]
type ReaderTStatus[A, S] = ReaderT[ValidationStatus, A, S]
object ReaderTStatus extends KleisliInstances with KleisliFunctions {
def apply[A, S](f: A => ValidationStatus[S]): ReaderTStatus[A, S]
= kleisli(f)
}
type) : either a valid object or a
failure message
monad transformer
Wednesday, 5 November 14
58. monadic validation
pattern ..
def validQuantity = ReaderTStatus[Trade, Trade] { trade =>
if (trade.quantity < 0)
left(s"Quantity needs to be > 0 for $trade")
else right(trade)
}
def validUnitPrice = ReaderTStatus[Trade, Trade] { trade =>
if (trade.unitPrice < 0)
left(s"Unit Price needs to be > 0 for $trade")
else right(trade)
}
def validValueDate = ReaderTStatus[Trade, Trade] { trade =>
trade.valueDate.map(vd =>
if (trade.tradeDate after vd)
left(s"Trade Date ${trade.tradeDate} must be before value date $vd")
else right(trade)
).getOrElse(right(trade))
}
Wednesday, 5 November 14
59. With Functional
Programming
• we implement all specific patterns in terms
of generic abstractions
• all these generic abstractions are based on
function composition
• and encourage immutability & referential
transparency
Wednesday, 5 November 14
60. Pattern #3: Functional Modeling encourages
parametricity, i.e. abstract logic from specific types to
generic ones
Wednesday, 5 November 14
61. case class Trade(
refNo: String,
instrument: Instrument,
tradeDate: Date,
valueDate: Date,
principal: Money,
taxes: List[Tax],
...
)
Wednesday, 5 November 14
62. case class Trade(
refNo: String,
instrument: Instrument,
tradeDate: Date,
valueDate: Date,
principal: Money,
taxes: List[Tax],
...
)
case class Money(amount: BigDecimal, ccy: Currency) {
def +(m: Money) = {
sealed trait Currency
case object USD extends Currency
case object AUD extends Currency
case object SGD extends Currency
case object INR extends Currency
require(m.ccy == ccy)
Money(amount + m.amount, ccy)
}
def >(m: Money) = {
require(m.ccy == ccy)
if (amount > m.amount) this else m
}
}
Wednesday, 5 November 14
63. case class Trade(
refNo: String,
instrument: Instrument,
tradeDate: Date,
valueDate: Date,
principal: Money,
taxes: List[Tax],
...
)
def netValue: Trade => Money = //..
Given a list of trades, find the total net valuation of all trades
in base currency
Wednesday, 5 November 14
65. case class Transaction(id: String, value: Money)
Given a list of transactions for a customer, find the highest
valued transaction
Wednesday, 5 November 14
66. case class Transaction(id: String, value: Money)
def highestValueTxn(txns: List[Transaction]) = {
txns.foldLeft(Money(0, baseCcy)) { (acc, e) =>
Given a list of transactions for a customer, find the highest
valued transaction
acc > e.value
}
}
Wednesday, 5 November 14
69. Instead of the specific collection type (List), we
can abstract over the type constructor using
the more general Foldable
def valueTradesInBaseCcy(ts: List[Trade]) = {
ts.foldLeft(Money(0, baseCcy)) { (acc, e) =>
acc + inBaseCcy(netValue(e))
}
}
def highestValueTxn(txns: List[Transaction]) = {
txns.foldLeft(Money(0, baseCcy)) { (acc, e) =>
acc > e.value
}
}
Wednesday, 5 November 14
76. def mapReduce[F[_], A, B](as: F[A])(f: A => B)
(implicit fd: Foldable[F], m: Monoid[B]) = fd.foldMap(as)(f)
def valueTradesInBaseCcy(ts: List[Trade]) =
mapReduce(ts)(netValue andThen inBaseCcy)
def highestValueTxn(txns: List[Transaction]) =
mapReduce(txns)(_.value)
This last pattern is inspired from Runar’s response on this SoF thread http://stackoverflow.com/questions/4765532/what-does-abstract-over-mean
Wednesday, 5 November 14
77. Takeaways ..
• Moves the algebra from domain specific
abstractions to more generic ones
• The program space in the mapReduce
function is shrunk - so scope of error is
reduced
• Increases the parametricity of our program
- mapReduce is now parametric in type
parameters A and B (for all A and B)
Wednesday, 5 November 14
78. 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.
This is the concept of parametricity and is one of the
Wednesday, 5 November 14