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.
Upcoming SlideShare
From functional to Reactive - patterns in domain modeling
Next
Download to read offline and view in fullscreen.

50

Share

Functional Patterns in Domain Modeling

Download to read offline

Presentation delivered at CodeMesh 2014

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

Functional Patterns in Domain Modeling

  1. 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. 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. 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. 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. 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. 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
  7. 7. Wednesday, 5 November 14
  8. 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
  9. 9. .. 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
  10. 10. .. 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
  11. 11. The Functional Lens .. Wednesday, 5 November 14
  12. 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
  13. 13. Problem Domain Wednesday, 5 November 14
  14. 14. Bank Account Trade Customer ... ... ... Problem Domain ... entities Wednesday, 5 November 14
  15. 15. place order Problem Domain Bank Account Trade Customer ... ... ... do trade process execution ... entities behaviors Wednesday, 5 November 14
  16. 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. 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. 18. place order Problem Domain Solution Domain behaviors • Functions do trade process execution ... • On Types • Constraints Wednesday, 5 November 14
  19. 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. 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. 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. 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. 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. 24. Pattern #1: Functional Modeling encourages Algebraic API Design which leads to organic evolution of domain models Wednesday, 5 November 14
  25. 25. Client places order - flexible format 1 Wednesday, 5 November 14
  26. 26. Client places order - flexible format 1 2 Transform to internal domain model entity and place for execution Wednesday, 5 November 14
  27. 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
  28. 28. def clientOrders: ClientOrderSheet => List[Order] def execute: Market => Account => Order => List[Execution] def allocate: List[Account] => Execution => List[Trade] Wednesday, 5 November 14
  29. 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. 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. 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. 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
  33. 33. let’s mine some patterns .. def clientOrders: ClientOrderSheet => List[Order] def execute: Market => Account => Order => List[Execution] def allocate: List[Account] => Execution => List[Trade] Wednesday, 5 November 14
  34. 34. let’s mine some patterns .. def clientOrders: ClientOrderSheet => List[Order] def execute(m: Market, broker: Account): Order => List[Execution] def allocate(accounts: List[Account]): Execution => List[Trade] Wednesday, 5 November 14
  35. 35. let’s mine some patterns .. def clientOrders: ClientOrderSheet => List[Order] def execute(m: Market, broker: Account): Order => List[Execution] def allocate(accounts: List[Account]): Execution => List[Trade] Wednesday, 5 November 14
  36. 36. let’s mine some patterns .. def clientOrders: ClientOrderSheet => List[Order] def execute(m: Market, broker: Account): Order => List[Execution] def allocate(accounts: List[Account]): Execution => List[Trade] Wednesday, 5 November 14
  37. 37. let’s mine some patterns .. def clientOrders: ClientOrderSheet => List[Order] def execute(m: Market, broker: Account): Order => List[Execution] def allocate(accounts: List[Account]): Execution => List[Trade] Wednesday, 5 November 14
  38. 38. let’s mine some patterns .. def clientOrders: ClientOrderSheet => List[Order] def execute(m: Market, broker: Account): Order => List[Execution] def allocate(accounts: List[Account]): Execution => List[Trade] Wednesday, 5 November 14
  39. 39. let’s mine some patterns .. def clientOrders: ClientOrderSheet => List[Order] def execute(m: Market, broker: Account): Order => List[Execution] def allocate(accounts: List[Account]): Execution => List[Trade] Function Composition with Effects Wednesday, 5 November 14
  40. 40. let’s mine some 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 ! patterns .. Wednesday, 5 November 14
  41. 41. let’s mine some patterns .. 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 Wednesday, 5 November 14
  42. 42. let’s mine some patterns .. def tradeGeneration( market: Market, broker: Account, clientAccounts: List[Account]) = { clientOrders andThen execute(market, broker) andThen allocate(clientAccounts) } Implementation follows the specification Wednesday, 5 November 14
  43. 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
  44. 44. Nouns first ? Really ? Wednesday, 5 November 14
  45. 45. Just as the doctor ordered .. ✓Making implicit concepts explicit ✓Intention revealing interfaces ✓Side-effect free functions ✓Declarative design Wednesday, 5 November 14
  46. 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. 47. Pattern #2: DDD patterns like Factories & Specification are part of the normal idioms of functional programming Wednesday, 5 November 14
  48. 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. 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. 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. 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. 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
  53. 53. Monads .. Wednesday, 5 November 14
  54. 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. 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. 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. 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. 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. 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. 60. Pattern #3: Functional Modeling encourages parametricity, i.e. abstract logic from specific types to generic ones Wednesday, 5 November 14
  61. 61. case class Trade( refNo: String, instrument: Instrument, tradeDate: Date, valueDate: Date, principal: Money, taxes: List[Tax], ... ) Wednesday, 5 November 14
  62. 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. 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
  64. 64. def inBaseCcy: Money => Money = //.. def valueTradesInBaseCcy(ts: List[Trade]) = { ts.foldLeft(Money(0, baseCcy)) { (acc, e) => acc + inBaseCcy(netValue(e)) } } Wednesday, 5 November 14
  65. 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. 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
  67. 67. fold on the collection 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
  68. 68. zero on Money associative binary operation 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 } } Wednesday, 5 November 14
  69. 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
  70. 70. look ma .. Monoid for 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 } } Wednesday, 5 November 14
  71. 71. fold the collection on a monoid Foldable abstracts over the type constructor Monoid abstracts over the operation Wednesday, 5 November 14
  72. 72. 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
  73. 73. 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
  74. 74. 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
  75. 75. 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
  76. 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. 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. 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
  79. 79. Use code “mlghosh2” for a 50% discount Wednesday, 5 November 14
  80. 80. 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
  81. 81. Thank You! Wednesday, 5 November 14
  • andhdo

    Feb. 2, 2021
  • GopalSarmaAkshintala

    Feb. 2, 2020
  • messacz

    Aug. 21, 2017
  • pmonster

    Apr. 15, 2017
  • EricYin1

    Feb. 3, 2017
  • rfred999

    Dec. 4, 2016
  • khajavi

    Oct. 24, 2016
  • SergeyTitov2

    Sep. 15, 2016
  • iamakingbee1

    Feb. 6, 2016
  • AlessandroLacava

    Feb. 5, 2016
  • KonstantinSilin

    Jan. 29, 2016
  • prayagupd

    Jan. 10, 2016
  • nomimic

    Oct. 13, 2015
  • RyanKwon1

    Oct. 13, 2015
  • moongux

    Oct. 2, 2015
  • underbellpark

    Oct. 1, 2015
  • NamWonCho

    Oct. 1, 2015
  • sungheelee777

    Oct. 1, 2015
  • cccnam5158

    Oct. 1, 2015
  • carin137

    Oct. 1, 2015

Presentation delivered at CodeMesh 2014

Views

Total views

7,881

On Slideshare

0

From embeds

0

Number of embeds

393

Actions

Downloads

142

Shares

0

Comments

0

Likes

50

×