The document discusses domain modeling approaches in a post-object-oriented world. It outlines issues with traditional OO domain modeling and proposes an alternative approach. This involves:
1. Describing a limited set of domain objects and relationships as an "algebra" rather than a universal ontology.
2. Using existential equality where identity is defined by attributes rather than identity.
3. Building a simple domain model for a toy billing system to demonstrate the approach.
4. Discussing how the domain model can be implemented and improved, including handling errors, deaggregating components, and using internal domain-specific languages.
3. Domain modelling.
• Outline typical OOD issues
• Build
• simple domain model for toy billing system.
// scala, can be mapped to java.
• internal DSL
4. 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
5. Traditional OO WAY
• Human is an upright, featherless biped with
broad, flat nails.
6. Traditional OO WAY
• Human is an upright, featherless biped with
broad, flat nails.
Open for extensions close for modifications
7. Traditional OO WAY
• Human is an upright, featherless biped with
broad, flat nails.
Open for extensions close for modifications
8. Traditional OO WAY
• Intensional Equality [ mutability ]
// same identity thought lifecycle
9. 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 ?
10. 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
11. Domain modelling
• Traditional OO way: similar to early
philosophy
• Very general
• Idealistic
• Fit to concrete cases may be undefined
12. Domain modelling
• Post OO way: describe limited set of objects
and relationships.
• Algebra instead Ontology
• Existential equality [identity == same
attributes]
• Rules of algebra <=> rules of reality.
13. 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
14. 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
15. TariffPlan: Price per limit and charge
case class TariffPlan[Limits](
monthlyFee: BigDecimal,
monthlyLimits: Limits,
excessCharge: BigDecimal
)
16. 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)
def acceptPayment(u:Subscriber, payment: Payment)
}
// Aggregate
// let’s add to Subscriber all fields, what needed.
17. 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
}
18. 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,
}
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. 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. Subscriber:
case class Subscriber(
id : Long,
name: String,
serviceInfos:Map[Service,SubscriberServiceInfo[_,_]],
account: BigDecimal,
lastPayedDate: DateTime
)
case class SubscriberServiceInfo[S<:Service,L<:S#Limits](
service: S,
tariffPlan: tariffPlan[L],
amountUsed:Double
)
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. From domain model to implementation. [S1]
Improvements/Refactoring space:
• Errors handling
• Deaggregate
• Fluent DSL
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)
32. Deaggregation. [S2]
Subscriber
Service TariffPlan
Domain Data/ Aggregates Services
SubscriberOperations
TariffPlanOperations
….
Repository
Interpret
- Not for all cases
- Loss
- generality of repository
- simply logic
- Gain
- simple repository operations
- more efficient data access.
33. DSL: Domain Specific Language.
Idea: fluent syntax for fluent operations.
• Syntax sugar, can be used by non-programmers
• ‘Micro-interpreter/compiler’
• Internal/External
Let’s build simple Internal DSL for our tariff plans.
40. DSL: (100 hrn) montly (1 gb) speed 100 excess ((2 hrn) per (1 gb))
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]
41. Post-OOD domain modelling
Domain 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.
42. 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