an algebraic approach to functional domain modeling

96
An Algebraic Approach to Functional Domain Modeling Debasish Ghosh @debasishg Functional Conf 2016

Upload: debasish-ghosh

Post on 16-Apr-2017

2.434 views

Category:

Software


5 download

TRANSCRIPT

Page 1: An Algebraic Approach to Functional Domain Modeling

An Algebraic Approach to Functional Domain Modeling

Debasish Ghosh @debasishg

Functional Conf 2016

Page 2: An Algebraic Approach to Functional Domain Modeling

Domain Modeling

Page 3: An Algebraic Approach to Functional Domain Modeling

Domain Modeling(Functional)

Page 4: An Algebraic Approach to Functional Domain Modeling

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)

Page 5: An Algebraic Approach to Functional Domain Modeling

The Functional Lens ..

“domain API evolution through algebraic composition”

Page 6: An Algebraic Approach to Functional Domain Modeling

The Functional Lens ..

“domain API evolution through algebraic composition”

Building larger domain behaviours out of smaller ones

Page 7: An Algebraic Approach to Functional Domain Modeling

The Functional Lens ..

“domain API evolution through algebraic composition”

Use compositionof pure functions and types

Page 8: An Algebraic Approach to Functional Domain Modeling
Page 9: An Algebraic Approach to Functional Domain Modeling
Page 10: An Algebraic Approach to Functional Domain Modeling
Page 11: An Algebraic Approach to Functional Domain Modeling
Page 12: An Algebraic Approach to Functional Domain Modeling

Your domain model is a function

Page 13: An Algebraic Approach to Functional Domain Modeling

Your domain model is a function

Page 14: An Algebraic Approach to Functional Domain Modeling

Your domain model is a collection of functions

Page 15: An Algebraic Approach to Functional Domain Modeling

Your domain model is a collection of functions

some simpler models are ..

Page 16: An Algebraic Approach to Functional Domain Modeling

https://msdn.microsoft.com/en-us/library/jj591560.aspx

Page 17: An Algebraic Approach to Functional Domain Modeling

A Bounded Context

• has a consistent vocabulary

• a set of domain behaviours modelled as functions on domain objects implemented as types

• each of the behaviours honour a set of business rules

• related behaviors grouped as modules

Page 18: An Algebraic Approach to Functional Domain Modeling

Domain Model = ∪(i) Bounded Context(i)

Page 19: An Algebraic Approach to Functional Domain Modeling

Domain Model = ∪(i) Bounded Context(i)

Bounded Context = { m[T1,T2,..] | T(i) ∈ Types }

• a module parameterised on a set of types

Page 20: An Algebraic Approach to Functional Domain Modeling

Domain Model = ∪(i) Bounded Context(i)

Bounded Context = { m[T1,T2,..] | T(i) ∈ Types }

Module = { f(x) | p(x) ∈ Domain Rules }

• domain function• on an object of type x• composes with other functions• closed under composition

• business rules

Page 21: An Algebraic Approach to Functional Domain Modeling

• Functions / Morphisms

• Types / Sets

• Composition

• Rules / Laws

Page 22: An Algebraic Approach to Functional Domain Modeling

• Functions / Morphisms

• Types / Sets

• Composition

• Rules / Laws algebra

Page 23: An Algebraic Approach to Functional Domain Modeling

Domain Model Algebra

Page 24: An Algebraic Approach to Functional Domain Modeling

Domain Model Algebra

(algebra of types, functions & laws)

Page 25: An Algebraic Approach to Functional Domain Modeling

Domain Model Algebra

(algebra of types, functions & laws)

explicit• types• type constraints• expression in terms of other generic algebra

Page 26: An Algebraic Approach to Functional Domain Modeling

Domain Model Algebra

(algebra of types, functions & laws)

explicit verifiable• types• type constraints• expr in terms of other generic algebra

• type constraints• more constraints if you have DT• algebraic property based testing

Page 27: An Algebraic Approach to Functional Domain Modeling

Problem Domain

Page 28: An Algebraic Approach to Functional Domain Modeling

Bank

Account

Trade

Customer

......

...

Problem Domain

...

entities

Page 29: An Algebraic Approach to Functional Domain Modeling

Bank

Account

Trade

Customer

......

...

do trade

process execution

place order

Problem Domain

...

entities

behaviors

Page 30: An Algebraic Approach to Functional Domain Modeling

Bank

Account

Trade

Customer

......

...

do trade

process execution

place order

Problem Domain

...

market regulations

tax laws

brokerage commission

rates

...

entities

behaviors

laws

Page 31: An Algebraic Approach to Functional Domain Modeling

do trade

process execution

place order

Solution Domain

...

behaviorsFunctions

([Type] => Type)

Page 32: An Algebraic Approach to Functional Domain Modeling

Bank

Account

Trade

Customer

......

...

do trade

process execution

place order

Solution Domain

...

entities

behaviorsfunctions

([Type] => Type)

algebraic data type

Page 33: An Algebraic Approach to Functional Domain Modeling

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

Page 34: An Algebraic Approach to Functional Domain Modeling

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

...

Page 35: An Algebraic Approach to Functional Domain Modeling

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

Page 36: An Algebraic Approach to Functional Domain Modeling

Domain Model = ∪(i) Bounded Context(i)

Bounded Context = { m[T1,T2,..] | T(i) ∈ Types }

Module = { f(x) | p(x) ∈ Domain Rules }

• domain function• on an object of type x• composes with other functions• closed under composition

• business rules

Domain Algebra

Domain Algebra

Page 37: An Algebraic Approach to Functional Domain Modeling

Client places order- flexible format

1

Page 38: An Algebraic Approach to Functional Domain Modeling

Client places order- flexible format

Transform to internal domainmodel entity and place for execution

1 2

Page 39: An Algebraic Approach to Functional Domain Modeling

Client places order- flexible format

Transform to internal domainmodel entity and place for execution

Trade & Allocate toclient accounts

1 2

3

Page 40: An Algebraic Approach to Functional Domain Modeling

def clientOrders: ClientOrderSheet => List[Order]

def execute: Market => Account => Order => List[Execution]

def allocate: List[Account] => Execution => List[Trade]

Page 41: An Algebraic Approach to Functional Domain Modeling

def clientOrders: ClientOrderSheet => List[Order]

def execute[Account <: BrokerAccount]: Market => Account => Order => List[Execution]

def allocate[Account <: TradingAccount]: List[Account] => Execution => List[Trade]

Page 42: An Algebraic Approach to Functional Domain Modeling

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

Page 43: An Algebraic Approach to Functional Domain Modeling

def clientOrders: ClientOrderSheet => List[Order]

def execute: Market => Account => Order => List[Execution]

def allocate: List[Account] => Execution => List[Trade]

• Types (domain entities)• Functions operating on types (domain behaviors)• Laws (business rules)

Page 44: An Algebraic Approach to Functional Domain Modeling

def clientOrders: ClientOrderSheet => List[Order]

def execute: Market => Account => Order => List[Execution]

def allocate: List[Account] => Execution => List[Trade]

• Types (domain entities)• Functions operating on types (domain behaviors)• Laws (business rules)

Algebra of the API

Page 45: An Algebraic Approach to Functional Domain Modeling

trait Trading[Account, Trade, ClientOrderSheet, Order, Execution, Market] {

def clientOrders: ClientOrderSheet => List[Order]

def execute: Market => Account => Order => List[Execution]

def allocate: List[Account] => Execution => List[Trade]

def tradeGeneration(market: Market, broker: Account, clientAccounts: List[Account]) = ???}

parameterized on typesmodule

Page 46: An Algebraic Approach to Functional Domain Modeling

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

Page 47: An Algebraic Approach to Functional Domain Modeling

def clientOrders: ClientOrderSheet => List[Order]

def execute: Market => Account => Order => List[Execution]

def allocate: List[Account] => Execution => List[Trade]

let’s do some algebra ..

Page 48: An Algebraic Approach to Functional Domain Modeling

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 ..

Page 49: An Algebraic Approach to Functional Domain Modeling

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 ..

Page 50: An Algebraic Approach to Functional Domain Modeling

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 ..

Page 51: An Algebraic Approach to Functional Domain Modeling

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 ..

Page 52: An Algebraic Approach to Functional Domain Modeling

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 ..

Page 53: An Algebraic Approach to Functional Domain Modeling

def f: A => List[B]

def g: B => List[C]

def h: C => List[D]

.. a problem of composition ..

Page 54: An Algebraic Approach to Functional Domain Modeling

.. a problem of composition with effects ..

def f: A => List[B]

def g: B => List[C]

def h: C => List[D]

Page 55: An Algebraic Approach to Functional Domain Modeling

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 ..

Page 56: An Algebraic Approach to Functional Domain Modeling

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 !

Page 57: An Algebraic Approach to Functional Domain Modeling

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

.. 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]

Page 58: An Algebraic Approach to Functional Domain Modeling

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]

Domain algebra composed with the categorical algebra of a Kleisli Arrow

.. function composition with Effects ..

Page 59: An Algebraic Approach to Functional Domain Modeling

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]

.. that implements the semantics of our domain algebraically ..

.. function composition with Effects ..

Page 60: An Algebraic Approach to Functional Domain Modeling

def tradeGeneration( market: Market, broker: Account, clientAccounts: List[Account]) = {

clientOrders andThen execute(market, broker) andThen allocate(clientAccounts)

}

Implementation follows the specification

.. the complete trade generation logic ..

Page 61: An Algebraic Approach to Functional Domain Modeling

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 ..

Page 62: An Algebraic Approach to Functional Domain Modeling

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

Page 63: An Algebraic Approach to Functional Domain Modeling

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 allow customisable handling of errors that may occur within our

domain behaviors ..

.. function composition with Effects ..

Page 64: An Algebraic Approach to Functional Domain Modeling

more algebra, more types

Page 65: An Algebraic Approach to Functional Domain Modeling

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

return type constructor

Page 66: An Algebraic Approach to Functional Domain Modeling

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

return type constructor

What happens in case the operation fails ?

Page 67: An Algebraic Approach to Functional Domain Modeling

Error handling as an Effect

• pure and functional

• with an explicit and published algebra

• stackable with existing effects

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

Page 68: An Algebraic Approach to Functional Domain Modeling

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

.. stacking of effects ..

M[List[_]]

Page 69: An Algebraic Approach to Functional Domain Modeling

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

.. stacking of effects ..

M[List[_]]: M is a Monad

Page 70: An Algebraic Approach to Functional Domain Modeling

type Response[A] = String \/ Option[A]

val count: Response[Int] = some(10).rightfor { maybeCount <- count} yield { for { c <- maybeCount // use c } yield c}

Monad Transformers

Page 71: An Algebraic Approach to Functional Domain Modeling

type Response[A] = String \/ Option[A]

val count: Response[Int] = some(10).rightfor { 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

Page 72: An Algebraic Approach to Functional Domain Modeling

type Response[A] = String \/ Option[A]

val count: Response[Int] = some(10).rightfor { 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

Page 73: An Algebraic Approach to Functional Domain Modeling

Monad Transformers

• collapses the stack and gives us a single monad to deal with

• order of stacking is important though

Page 74: An Algebraic Approach to Functional Domain Modeling

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

.. stacking of effects ..

case class ListT[M[_], A] (run: M[List[A]]) { //..

Page 75: An Algebraic Approach to Functional Domain Modeling

type StringOr[A] = String \/ Atype 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 ..

Page 76: An Algebraic Approach to Functional Domain Modeling

def execute(market: Market, brokerAccount: Account) =

kleisli[List, Order, Execution] { order =>

order.items.map { item => Execution(brokerAccount, market, ..) }

}

Page 77: An Algebraic Approach to Functional Domain Modeling

private def makeExecution(brokerAccount: Account, item: LineItem, market: Market): String \/ Execution = //..

def execute(market: Market, brokerAccount: Account) =

kleisli[Valid, Order, Execution] { order =>

listT[StringOr](

order.items.map { item =>

makeExecution(brokerAccount, market, ..)

}.sequenceU

) }

Page 78: An Algebraic Approach to Functional Domain Modeling

List(aggregates)

Algebra of types

Page 79: An Algebraic Approach to Functional Domain Modeling

List(aggregates)

Disjunction(error accumulation)

Algebra of types

Page 80: An Algebraic Approach to Functional Domain Modeling

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Algebra of types

Page 81: An Algebraic Approach to Functional Domain Modeling

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Page 82: An Algebraic Approach to Functional Domain Modeling

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Monad

Page 83: An Algebraic Approach to Functional Domain Modeling

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Monad Monoid

Page 84: An Algebraic Approach to Functional Domain Modeling

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Monad MonoidCompositional

Page 85: An Algebraic Approach to Functional Domain Modeling

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Monad Monoid

Offers a suite of functional combinators

Page 86: An Algebraic Approach to Functional Domain Modeling

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Monad Monoid

Handles edge cases so your domain logic remains clean

Page 87: An Algebraic Approach to Functional Domain Modeling

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Monad Monoid

Implicitly encodes quite a bit of domain rules

Page 88: An Algebraic Approach to Functional Domain Modeling

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]

.. the algebra ..

Page 89: An Algebraic Approach to Functional Domain Modeling

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]

.. the algebra ..

functions

Page 90: An Algebraic Approach to Functional Domain Modeling

.. the algebra ..

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]

types

Page 91: An Algebraic Approach to Functional Domain Modeling

.. the algebra ..

composition

def tradeGeneration(market: Market, broker: Account, clientAccounts: List[Account]) = {

clientOrders andThen execute(market, broker) andThen allocate(clientAccounts)}

Page 92: An Algebraic Approach to Functional Domain Modeling

.. 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)

Page 93: An Algebraic Approach to Functional Domain Modeling

Domain Rules as Algebraic Properties

• part of the abstraction

• equally important as the actual abstraction

• verifiable as properties

Page 94: An Algebraic Approach to Functional Domain Modeling

.. 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 ..

Page 95: An Algebraic Approach to Functional Domain Modeling

https://www.manning.com/books/functional-and-reactive-domain-modeling

Page 96: An Algebraic Approach to Functional Domain Modeling

Thank You!