Successfully reported this slideshow.
Your SlideShare is downloading. ×

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

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 40 Ad

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

Download to read offline

Что приходит на смену Object Oriented Design? Объекты нам дают ощущения соответсвия нашей программы и реального мира. Если у нас вместо (или вместе) с объектами есть функции и данные, то каким получится аналог описания предметной области? Как при этом можно использовать средства языковой абстракции в новых языках программирования?

Что приходит на смену Object Oriented Design? Объекты нам дают ощущения соответсвия нашей программы и реального мира. Если у нас вместо (или вместе) с объектами есть функции и данные, то каким получится аналог описания предметной области? Как при этом можно использовать средства языковой абстракции в новых языках программирования?

Advertisement
Advertisement

More Related Content

Slideshows for you (20)

Viewers also liked (20)

Advertisement

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

More from Fwdays (20)

Advertisement

Recently uploaded (20)

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

  1. 1. Behind OOD // domain modelling in a post-OO world Ruslan Shevchenko Lynx Capital Partners https://github.com/rssh @rssh1
  2. 2. Domain modelling • Representation of business domain objects: • in ‘head’ of people • in code
  3. 3. 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
  4. 4. Traditional OO WAY • Human is an upright, featherless biped with broad, flat nails.
  5. 5. Traditional OO WAY • Human is an upright, featherless biped with broad, flat nails. Open for extensions close for modifications
  6. 6. Traditional OO WAY • Intensional Equality [ mutability ] // same identity thought lifecycle
  7. 7. 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 ?
  8. 8. 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
  9. 9. Domain modelling • Traditional OO way: similar to early philosophy • Very general • Idealistic • Fit to concrete cases may be undefined
  10. 10. 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.
  11. 11. 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.
  12. 12. 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 Unit Unit Internet is Gbin Telephony is Minute and Bandwidth
  13. 13. 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
  14. 14. TariffPlan: Price per limit and charge case class TariffPlan[Limits]( monthlyFee: BigDecimal, monthlyLimits: Limits, excessCharge: BigDecimal )
  15. 15. 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.
  16. 16. 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 }
  17. 17. 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, }
  18. 18. 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”) } ……………. }
  19. 19. 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”) } ……………. }
  20. 20. 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) } }
  21. 21. Subscriber: case class Subscriber( id : Long, name: String, serviceInfos:Map[Service,SubscriberServiceInfo[_,_]], account: BigDecimal, lastPayedDate: DateTime )
  22. 22. 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 = … }
  23. 23. 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
  24. 24. From domain model to implementation. [S1] Improvements/Refactoring space: • Design for failure • Deaggregate • Fluent DSL
  25. 25. 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 }
  26. 26. 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 }
  27. 27. 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)
  28. 28. Deaggregation: trait Repository { def create[T](): T def find[T](id: Id[T]): Try[T] def save[T](obj: T): Try[Boolean] }
  29. 29. 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] }
  30. 30. 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] ….. }
  31. 31. 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
  32. 32. Deaggregation. [S2] Subscriber Service TariffPlan Domain Data/ Aggregates Services SubscriberOperations TariffPlanOperations …. Repository Interpret
  33. 33. 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
  34. 34. 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 }
  35. 35. 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 }
  36. 36. 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) }
  37. 37. 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) ) }
  38. 38. 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]
  39. 39. 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.
  40. 40. 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

×