Upload
debasish-ghosh
View
3.030
Download
0
Tags:
Embed Size (px)
DESCRIPTION
Presentation delivered at CodeMesh 2014
Citation preview
Functional Patterns in Domain Modeling
with examples from the Financial Domain
@debasishghttps://github.com/debasishghttp://debasishg.blogspot.com
Wednesday, 5 November 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)
Wednesday, 5 November 14
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
• 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
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
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
Wednesday, 5 November 14
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
.. and some Patterns
• Domain object lifecycle patterns
Aggregates - encapsulate object references
Factories - abstract object creation & management
Repositories - manage object persistence & queries
Wednesday, 5 November 14
.. some more Patterns
• Refactoring patterns
Making implicit concepts explicit
Intention revealing interfaces
Side-effect free functions
Declarative design
Specification for validation
Wednesday, 5 November 14
The Functional Lens ..
Wednesday, 5 November 14
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
Problem Domain
Wednesday, 5 November 14
Bank
Account
Trade
Customer
......
...
Problem Domain
...
entities
Wednesday, 5 November 14
Bank
Account
Trade
Customer
......
...
do trade
process execution
place order
Problem Domain
...
entities
behaviors
Wednesday, 5 November 14
Bank
Account
Trade
Customer
......
...
do trade
process execution
place order
Problem Domain
...
market regulations
tax laws
brokerage commission
rates
...
entities
behaviors
laws
Wednesday, 5 November 14
Bank
Account
Trade
Customer
......
...
do trade
process execution
place order
Problem Domain
...
market regulations
tax laws
brokerage commission
rates
...
entities
behaviors
laws
Wednesday, 5 November 14
do trade
process execution
place orderProblem Domain
...
behaviors • Functions• On Types• Constraints
Solution Domain
Wednesday, 5 November 14
do trade
process execution
place orderProblem Domain
...
behaviors • Functions• On Types• Constraints
Solution Domain
• Morphisms• Sets• Laws
Algebra
Wednesday, 5 November 14
do trade
process execution
place orderProblem Domain
...
behaviors • Functions• On Types• Constraints
Solution Domain
• Morphisms• Sets• Laws
Algebra
Compose for larger abstractions
Wednesday, 5 November 14
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
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
.. 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
Pattern #1: Functional Modeling encourages Algebraic API Design which leads to organic evolution of domain
models
Wednesday, 5 November 14
Client places order- flexible format
1
Wednesday, 5 November 14
Client places order- flexible format
Transform to internal domainmodel entity and place for execution
1 2
Wednesday, 5 November 14
Client places order- flexible format
Transform to internal domainmodel entity and place for execution
Trade & Allocate toclient accounts
1 2
3
Wednesday, 5 November 14
def clientOrders: ClientOrderSheet => List[Order]
def execute: Market => Account => Order => List[Execution]
def allocate: List[Account] => Execution => List[Trade]
Wednesday, 5 November 14
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
Wednesday, 5 November 14
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
Just some types & operations on those types+
some laws governing those operations
Wednesday, 5 November 14
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
Just some types & operations on those types+
some laws governing those operations
Algebra of the API
Wednesday, 5 November 14
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
def clientOrders: ClientOrderSheet => List[Order]
def execute: Market => Account => Order => List[Execution]
def allocate: List[Account] => Execution => List[Trade]
let’s mine some patterns ..
Wednesday, 5 November 14
def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s mine some patterns ..
Wednesday, 5 November 14
def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s mine some patterns ..
Wednesday, 5 November 14
def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s mine some patterns ..
Wednesday, 5 November 14
def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s mine some patterns ..
Wednesday, 5 November 14
def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s mine some patterns ..
Wednesday, 5 November 14
Function Composition with Effects
def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
let’s mine some patterns ..
Wednesday, 5 November 14
def clientOrders: ClientOrderSheet => List[Order]
def execute(m: Market, broker: Account): Order => List[Execution]
def allocate(accounts: List[Account]): Execution => List[Trade]
It’s a Kleisli !
let’s mine some patterns ..
Wednesday, 5 November 14
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]
Follow the types
let’s mine some patterns ..
Wednesday, 5 November 14
def tradeGeneration( market: Market, broker: Account, clientAccounts: List[Account]) = {
clientOrders andThen execute(market, broker) andThen allocate(clientAccounts)
}
Implementation follows the specification
let’s mine some patterns ..
Wednesday, 5 November 14
def tradeGeneration( market: Market, broker: Account, clientAccounts: List[Account]) = {
clientOrders andThen execute(market, broker) andThen allocate(clientAccounts)
}
Implementation follows the specificationand we get the Ubiquitous Language for
free :-)
let’s mine some patterns ..
Wednesday, 5 November 14
Nouns first ? Really ?
Wednesday, 5 November 14
✓Making implicit concepts explicit
✓Intention revealing interfaces
✓Side-effect free functions
✓Declarative design
Just as the doctor ordered ..
Wednesday, 5 November 14
✓Making implicit concepts explicit
✓Intention revealing interfaces
✓Side-effect free functions
✓Declarative design
Just as the doctor ordered ..
Claim: With functions & types as the
binding glue we get all these patterns for
free using generic abstractions based on
function composition
Wednesday, 5 November 14
Pattern #2: DDD patterns like Factories & Specification are part of the normal idioms of functional programming
Wednesday, 5 November 14
/** * 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
/** * 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
/** * 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
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
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 > 0
must be > trade date
Wednesday, 5 November 14
Monads ..
Wednesday, 5 November 14
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)}
monadic validation pattern
Wednesday, 5 November 14
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)}
monadic validation pattern
(monad comprehension)
Wednesday, 5 November 14
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)}
monadic validation pattern
The Specification Pattern
(Chapter 9)
Smart Constructor : The Factory Pattern
(Chapter 6)
Wednesday, 5 November 14
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)}
let’s mine some more patterns (with types) .. disjunction type (a sum
type) : either a valid object or a failure message
monad transformer
Wednesday, 5 November 14
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))}
monadic validation pattern ..
Wednesday, 5 November 14
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
Pattern #3: Functional Modeling encourages parametricity, i.e. abstract logic from specific types to
generic ones
Wednesday, 5 November 14
case class Trade( refNo: String, instrument: Instrument, tradeDate: Date, valueDate: Date, principal: Money, taxes: List[Tax], ...)
Wednesday, 5 November 14
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) = { require(m.ccy == ccy) Money(amount + m.amount, ccy) }
def >(m: Money) = { require(m.ccy == ccy) if (amount > m.amount) this else m }}
sealed trait Currencycase object USD extends Currencycase object AUD extends Currencycase object SGD extends Currencycase object INR extends Currency
Wednesday, 5 November 14
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
def inBaseCcy: Money => Money = //..
def valueTradesInBaseCcy(ts: List[Trade]) = { ts.foldLeft(Money(0, baseCcy)) { (acc, e) => acc + inBaseCcy(netValue(e)) }}
Wednesday, 5 November 14
case class Transaction(id: String, value: Money)
Given a list of transactions for a customer, find the highest valued transaction
Wednesday, 5 November 14
case class Transaction(id: String, value: Money)
Given a list of transactions for a customer, find the highest valued transaction
def highestValueTxn(txns: List[Transaction]) = { txns.foldLeft(Money(0, baseCcy)) { (acc, e) => acc > e.value }}
Wednesday, 5 November 14
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 }}
fold on the collection
Wednesday, 5 November 14
zero on Money
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 }}
associative binary operation on Money
Wednesday, 5 November 14
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 }}
Instead of the specific collection type (List), we can abstract over the type constructor using
the more general Foldable
Wednesday, 5 November 14
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 }}
look ma .. Monoid for Money !
Wednesday, 5 November 14
fold the collection on a monoid
Foldable abstracts over the type constructor
Monoid abstracts over the operation
Wednesday, 5 November 14
def mapReduce[F[_], A, B](as: F[A])(f: A => B) (implicit fd: Foldable[F], m: Monoid[B]) = fd.foldMap(as)(f)
Wednesday, 5 November 14
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)
Wednesday, 5 November 14
trait Foldable[F[_]] {
def foldl[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 = foldl(as, m.zero, (b: B, a: A) => m.add(b, f(a)))}
implicit val listFoldable = new Foldable[List] { def foldl[A, B](as: List[A], z: B, f: (B, A) => B) = as.foldLeft(z)(f)}
Wednesday, 5 November 14
implicit def MoneyMonoid(implicit c: Currency) = new Monoid[Money] { def zero: Money = Money(BigDecimal(0), c) def append(m1: Money, m2: => Money) = m1 + m2 }
implicit def MaxMoneyMonoid(implicit c: Currency) = new Monoid[Money] { def zero: Money = Money(BigDecimal(0), c) def append(m1: Money, m2: => Money) = m1 > m2 }
Wednesday, 5 November 14
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
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
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 fundamental building blocks of compositionality in FP.
Wednesday, 5 November 14
Use code “mlghosh2” for a 50% discount
Wednesday, 5 November 14
Image acknowledgements
• www.valuewalk.com
• http://bienabee.com
• http://www.ownta.com
• http://ebay.in
• http://www.clker.com
• http://homepages.inf.ed.ac.uk/wadler/
• http://en.wikipedia.org
Wednesday, 5 November 14
Thank You!
Wednesday, 5 November 14