An Algebraic Approach to Functional Domain Modeling

Post on 16-Apr-2017

2.432 views 5 download

Transcript of An Algebraic Approach to Functional Domain Modeling

An Algebraic Approach to Functional Domain Modeling

Debasish Ghosh @debasishg

Functional Conf 2016

Domain Modeling

Domain Modeling(Functional)

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)

The Functional Lens ..

“domain API evolution through algebraic composition”

The Functional Lens ..

“domain API evolution through algebraic composition”

Building larger domain behaviours out of smaller ones

The Functional Lens ..

“domain API evolution through algebraic composition”

Use compositionof pure functions and types

Your domain model is a function

Your domain model is a function

Your domain model is a collection of functions

Your domain model is a collection of functions

some simpler models are ..

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

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

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

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

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

• a module parameterised on a set of types

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

• Functions / Morphisms

• Types / Sets

• Composition

• Rules / Laws

• Functions / Morphisms

• Types / Sets

• Composition

• Rules / Laws algebra

Domain Model Algebra

Domain Model Algebra

(algebra of types, functions & laws)

Domain Model Algebra

(algebra of types, functions & laws)

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

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

Problem Domain

Bank

Account

Trade

Customer

......

...

Problem Domain

...

entities

Bank

Account

Trade

Customer

......

...

do trade

process execution

place order

Problem Domain

...

entities

behaviors

Bank

Account

Trade

Customer

......

...

do trade

process execution

place order

Problem Domain

...

market regulations

tax laws

brokerage commission

rates

...

entities

behaviors

laws

do trade

process execution

place order

Solution Domain

...

behaviorsFunctions

([Type] => Type)

Bank

Account

Trade

Customer

......

...

do trade

process execution

place order

Solution Domain

...

entities

behaviorsfunctions

([Type] => Type)

algebraic data type

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

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

...

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

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

Client places order- flexible format

1

Client places order- flexible format

Transform to internal domainmodel entity and place for execution

1 2

Client places order- flexible format

Transform to internal domainmodel entity and place for execution

Trade & Allocate toclient accounts

1 2

3

def clientOrders: ClientOrderSheet => List[Order]

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

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

def clientOrders: ClientOrderSheet => List[Order]

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

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

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

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)

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

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

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

def clientOrders: ClientOrderSheet => List[Order]

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

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

let’s do some algebra ..

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

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

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

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

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

def f: A => List[B]

def g: B => List[C]

def h: C => List[D]

.. a problem of composition ..

.. a problem of composition with effects ..

def f: A => List[B]

def g: B => List[C]

def h: C => List[D]

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

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 !

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]

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

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

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

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

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

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

more algebra, more types

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

return type constructor

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

return type constructor

What happens in case the operation fails ?

Error handling as an Effect

• pure and functional

• with an explicit and published algebra

• stackable with existing effects

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

.. stacking of effects ..

M[List[_]]

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

.. stacking of effects ..

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

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

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

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

Monad Transformers

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

• order of stacking is important though

def clientOrders: Kleisli[List, ClientOrderSheet, Order]

.. stacking of effects ..

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

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

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

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

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

}

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

) }

List(aggregates)

Algebra of types

List(aggregates)

Disjunction(error accumulation)

Algebra of types

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Algebra of types

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Monad

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Monad Monoid

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Monad MonoidCompositional

List(aggregates)

Disjunction(error accumulation)

Kleisli(dependency injection)

Future(reactive non-blocking computation)

Algebra of types

Monad Monoid

Offers a suite of functional combinators

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

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

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

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

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

.. the algebra ..

composition

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

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

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

Domain Rules as Algebraic Properties

• part of the abstraction

• equally important as the actual abstraction

• verifiable as properties

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

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

Thank You!