"После OOD: как моделировать предметную область в...

40
Behind OOD // domain modelling in a post-OO world Ruslan Shevchenko Lynx Capital Partners https://github.com/rssh @rssh1

Transcript of "После OOD: как моделировать предметную область в...

Page 1: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Behind OOD // domain modelling in a post-OO world

Ruslan Shevchenko Lynx Capital Partners

https://github.com/rssh @rssh1

Page 2: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Domain modelling

• Representation of business domain objects: • in ‘head’ of people • in code

Page 3: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Domain modelling

• Traditional OO way: have layer of classes, which corresponds to domain objects.

• Extensibility via inheritance in some consistent Universal Ontology

• Intensional Equality [identity != attribute]

• Object instance <=> Entity in real world

Page 4: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Traditional OO WAY

• Human is an upright, featherless biped with broad, flat nails.

Page 5: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Traditional OO WAY

• Human is an upright, featherless biped with broad, flat nails.

Open for extensions close for modifications

Page 6: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Traditional OO WAY

• Intensional Equality [ mutability ]

// same identity thought lifecycle

Page 7: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Traditional OO WAY

• Object in code <=> Object in real world

class Person { int getId(); String getName(); int getAge();

Set<Person> getChilds() }

— thread-safe ?

— persistent updates ?

Page 8: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Domain modelling

• Traditional OO way: have layer of classes, which corresponds to domain objects.

• Extensibility via inheritance in some consistent Universal Ontology

• Intensional Equality [identity != attribute]

• Object instance <=> Entity in real world

Page 9: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Domain modelling

• Traditional OO way: similar to early philosophy

• Very general

• Idealistic

• Fit to concrete cases may be undefined

Page 10: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Domain modelling

• Post OO way: describe limited system of relationship and provide implementation

• Algebra instead Ontology

• Existential equality [identity == same attributes]

• Elements/Services instead Objects.

Page 11: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Domain modelling

• Post OO way: describe limited system of relationship and provide implementation

• Algebra instead Ontology

• Existential equality [identity == same attributes]

• Elements/Services instead Objects.

Page 12: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Domain modelling: where to start.

• Example: description of small billing system

Subscriber

Billing System

Service PaymentPlanuse in accordance with

Service is Internet | Telephony

PaymentPlan is Monthly Fee for Quantity Limit and

Overhead cost per UnitUnit Internet is Gb in

Telephony is Minute and Bandwidth

Page 13: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

TariffPlan: Use limit and quantity from service.

sealed trait Service { type Limits def quantity(l:Limits): BigDecimal }

case object Internet extends Service { case class Limits(gb:Int,bandwidth:Int) def quantity(l:Limits) = l.gb }

case object Telephony extends Service { type Limits = Int def quantity(l) = l

Page 14: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

TariffPlan: Price per limit and charge

case class TariffPlan[Limits]( monthlyFee: BigDecimal, monthlyLimits: Limits, excessCharge: BigDecimal )

Page 15: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Subscriber ? Service ?

case class Subscriber( id, name, … )

trait BillingService { def checkServiceAccess(u:Subscriber,s:Service):Boolean

def rateServiceUsage(u:Subscriber,r:ServiceUsage):Subscriber

def ratePeriod(u:Subscriber, date: DateTime)

}

// Aggregate// let’s add to Subscriber all fields, what needed.

Page 16: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

adding fields for Subscriber aggregates.

case class Subscriber(id, name, serviceInfos:Map[Service,SubscriberServiceInfo], account: BigDecimal, ….. ) case class SubscriberServiceInfo[S<:Service,L<:S#Limits]( service: S, tariffPlan: tariffPlan[S,L], amountUsed:Double ) trait BillingService { def checkServiceAccess(u:Subscriber,s:Service):Boolean = u.serviceInfos(s).isDefined && u.account == 0

}

Page 17: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

adding fields for Subscriber aggregates.

case class Subscriber(id, name, serviceInfos:Map[Service,SubscriberServiceInfo[_,_]], account: BigDecimal, ….. ) case class ServiceUsage(service, amount, when)

trait BillingService { def rateServiceUsage(u:Subscriber,r:ServiceUsage):Subscriber = serviceInfo(r.service) match { case Some(SubscriberServiceInfo(service,plan,amount)) => val price = …. u.copy(account = u.account - price, serviceInfo = serviceInfo.updated(s, }

Page 18: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

adding fields for Subscriber aggregates.

trait BillingService { def rateServiceUsage(u:Subscriber,r:ServiceUsage):Subscriber = serviceInfo(r.service) match { case Some(SubscriberServiceInfo(service,plan,amount)) => val price = …. u.copy(account = u.account - price, serviceInfo = serviceInfo.updated(s, ServiceInfo(service,plan,amount+r.amount)) ) case None => throw new IllegalStateException(“service is not enabled”) } ……………. }

Page 19: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

adding fields for Subscriber aggregates.

trait BillingService { def rateServiceUsage(u:Subscriber,r:ServiceUsage):Subscriber = serviceInfo(r.service) match { case Some(SubscriberServiceInfo(service,plan,amount)) => val price = …. u.copy(account = u.account - price, serviceInfo = serviceInfo.updated(s, ServiceInfo(service,plan,amount+r.amount)) ) case None => throw new IllegalStateException(“service is not enabled”) } ……………. }

Page 20: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Subscriber aggregates [rate: lastPayedDate]

case class Subscriber(id, name, serviceInfos:Map[Service,SubscriberServiceInfo[_,_]], account: BigDecimal, lastPayedDate: DateTime )

trait BillingService { def ratePeriod(u:Subscriber,date:DateTime):Subscriber = if (date < u.lastPayedDate) u else { val price = ….. subscriber.copy(account = u.account - price, lastPayedDate = date+1.month) } }

Page 21: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Subscriber:

case class Subscriber( id : Long, name: String, serviceInfos:Map[Service,SubscriberServiceInfo[_,_]], account: BigDecimal, lastPayedDate: DateTime )

Page 22: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Subscriber:

case class Subscriber( id : Long, name: String, internetServiceInfo: ServiceInfo[Internet,Internet.Limits], telephonyServiceInfo: ServiceInfo[Telephony,Telephony.Limits], account: BigDecimal, lastPayedDate: DateTime ) {

def serviceInfo(s:Service):SubscriberServiceInfo[s.type,s.Limits] = ….

def updateServiceInfo[S<:Service,L<:S#Limits]( serviceInfo:SubscriberServiceInfo[S,L]): Subscriber = … }

Page 23: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

From domain model to implementation. [S1]

Subscriber Service TariffPlan

Domain Data/ Aggregates Services

SubscriberOperations TariffPlanOperations ….

Repository

DD — contains only essential data

Services — only functionality

Testable. No mess with implementation.

Service calls — domain events

Page 24: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

From domain model to implementation. [S1]

Improvements/Refactoring space:

• Design for failure

• Deaggregate

• Fluent DSL

Page 25: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Design for failure:

trait BillingService { def checkServiceAccess(u:Subscriber,s:Service):Boolean

def rateServiceUsage(u:Subscriber,r:ServiceUsage):Subscriber

def ratePeriod(u:Subscriber, date: DateTime): Subscriber

def acceptPayment(u:Subscriber, payment:Payment):Subscriber

}

Page 26: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Design for failure:

trait BillingService { def checkServiceAccess(u:Subscriber,s:Service): Boolean

def rateServiceUsage(u:Subscriber,r:ServiceUsage):Try[Subscriber]

def ratePeriod(u:Subscriber, date: DateTime): Try[Subscriber]

def acceptPayment(u:Subscriber, payment:Payment): Subscriber

}

Page 27: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Design for failure:

sealed trait Try[+X]

case class Success[X](v:X) extends Try[X]

case class Failure(ex:Throwable) extends Try[Nothing]

when use Try / traditional exception handling?

Try — error recovery is a part of business layers. (i.e. errors is domain-related)

Exception handling — error recovery is a part of infrastructure layer. (i. e. errors is system-related)

Page 28: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Deaggregation:

trait Repository { def create[T](): T

def find[T](id: Id[T]): Try[T]

def save[T](obj: T): Try[Boolean]

}

Page 29: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Deaggregation:

trait Repository { def create[T](): T def find[T](id: Id[T]): Try[T] def save[T](obj: T) : Try[Unit]

………..

def subscriberServiceInfo[S<:Service,L<:S#Limits] (id: Id[Subscriber], s:S): SubscriberServiceInfo[S,L]

def updateSubsriberServiceInfo[S<:Service,L<:S#Limits] ( id: Id[Subscriber],s:S,si:SubscriberServiceInfo[S,L]): Try[Unit]

}

Page 30: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Deaggregation:

trait BillingService { def checkServiceAccess(r:Repository, uid:Id[Subscriber], s:Service): Try[Boolean]

def rateServiceUsage(r: Repository, uid:Id[Subscriber], r:ServiceUsage):Try[Subscriber]

….. }

Page 31: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Deaggregation:

trait BillingService { val repository: Repository

def checkServiceAccess(uid:Id[Subscriber], s:Service): Try[Boolean]

def rateServiceUsage(uid:Id[Subscriber], r:ServiceUsage):Try[Subscriber]

….. }

// BillingService operations interpret repository

Page 32: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Deaggregation. [S2]

Subscriber Service TariffPlan

Domain Data/ Aggregates Services

SubscriberOperations TariffPlanOperations ….

Repository

Interpret

Page 33: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

TariffPlan: DSL

case class TariffPlan[Limits]( monthlyFee: BigDecimal, monthlyLimits: Limits, excessCharge: BigDecimal )

TariffPlan(100,Limits(1,100),2)

TariffPlan(montlyFee=100, Internet.Limits(gb=1,bandwidth=100), 2)

100 hrn montly (1 gb) speed 100 excess 2 per 1 gb

// let’s build

Page 34: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

TariffPlan: DSL

(100 hrn) montly (1 gb) speed 100 excess (2 hrn) per (1 gb)

trait TariffPlanDSL[S <: Service, L <: S#Limits] {

implicit class ToHrn(v: Int) { def hrn = this

def monthly(x: LimitExpression) = TariffPlan(v, x.limit, 0)

def per(x: QuantityExpression)

}

1 hrn = 1.hrn = new ToHrn(1).hrn

trait LimitExpression{ def limit: L }

type QuantityExpression { def quantity: Int }

Page 35: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

TariffPlan: DSL

(100 hrn) montly (1 gb) speed 100 excess (2 hrn) per (1 gb)

object InternetTariffPlanDSL extends TariffPlanDSL[Internet.type, Internet.Limits]

implicit class Gb(v: Int) extends LimitExpression with QuantityExpression{ def gb = this

def limit = Internet.Limits(v,100)

def quantity = x

}

Page 36: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

TariffPlan: DSL

(100 hrn) montly (1 gb) speed 100 excess ((2 hrn) per (1 gb))

trait TariffPlanDSL[S,L]

case class PerExpression(money: ToHrn, quantity: QuantityExpression)

trait RichTariffPlan(p: TariffPlan[L]) { def excess(x: PerExpression) = p.copy(excessCost=x.v)

}

Page 37: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

TariffPlan: DSL

(100 hrn) montly (1 gb) speed 100 excess ((2 hrn) per (1 gb))

object InternetTariffPlanDSL[S,L]

trait RichTariffPlan(p: TariffPlan[L]) extends super.RichTariffPlan(p) { def speed(x: Int) = p.copy( monthlyLimits = p.monthlyLimits.copy( bandwidth = x) )

}

Page 38: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

DSL

Internal

External

Need some boilerplate code.

Useful when developers need fluent business domain object notation.

Internally - combination of builder and interpreter patterns

Useful when external users (non-developers) want to describe domain objects. Internally - language-mini-interpreter. // [scala default library include parser combinators]

Page 39: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Post-OOD domain modelling

Immutable Data Objects

‘OO’ Objects with behavior

• Closed world. • Different lifecycle can be described by different types • Composition over inheritance

Domain Services• Open world. • No data, only functionality. Calls can be replayed. • Traditional inheritance

Infrastructure Services• Interpreters of ‘domain services’ functions

// phantom types.

Page 40: "После OOD: как моделировать предметную область в пост-объектном мире" Руслан Шевченко

Thanks for attention.

Fully - implemented ‘tiny’ domain model and DSL:

https://github.com/rssh/scala-training-materials/tree/master/fwdays2015-examples/

Ruslan Shevchenko Lynx Capital Partners

https://github.com/rssh @rssh1