Si vous pensez que le Domain Driven Design c’est seulement pour Java EE et que le reactive programming rend le code illisible et prématurément optimisé, cette présentation va vous surprendre. Venez voir comment DDD + CQRS + EventSourcing se conjuguent parfaitement avec Akka pour construire des systèmes robustes dans un environnement concurrentiel.
Par Xavier Bucchiotty, consultant chez Xebia France
11. User Story
As the Alliance
!
I want to be informed of updates in state of my
squadrons
!
In order to command the retreat
of a squadron if it remains only one XWing
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
12. Tasks
Notification from XWing
to Squadron Notification from
Squadron to Alliance
Implements
Squadron#retreat
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
13. Notification from XWing
to Squadron
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class XWing{
var healthPoints = 1
!!!
def receiveTorpedoFrom(sender){
!
println(s”$this is dead”)
}
}
14. • EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class XWing{
var healthPoints = 1
!
val squadron = parent
!
def receiveTorpedoFrom(sender){
squadron.remove(this)
println(s”$this is dead”)
}
}
Notification from XWing
to Squadron
15. } Remember
No bidirectional
references
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class XWing{
var healthPoints = 1
!
val squadron = parent
!
def receiveTorpedoFrom(sender){
squadron.remove(this)
println(s”$this is dead”)
}
Notification from XWing
to Squadron
16. Notification from XWing
to Squadron
} What would be
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class XWing{
var healthPoints = 1
!
val squadron = parent
!
def receiveTorpedoFrom(sender){
squadron.remove(this)
println(s”$this is dead”)
}
visibility of
remove?
17. • EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class XWing{
var healthPoints = 1
!
!
def receiveTorpedoFrom(sender){
println(s”$this is dead”)
}
!
}
Notification from XWing
to Squadron
18. • EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class XWing{
var healthPoints = 1
!
val life = Promise[Unit]()
!
def receiveTorpedoFrom(sender){
life.success(null)
println(s”$this is dead”)
}
!
def endOfLife = life.future
}
Notification from XWing
to Squadron
19. Notification from XWing
to Squadron
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class XWing{
var healthPoints = 1
!
val life = Promise[Unit]()
!
def receiveTorpedoFrom(sender){
life.success(null)
println(s”$this is dead”)
}
!
def endOfLife = life.future
}
class Squadron{
var xwings = Set[XWing]()
!
xwings.foreach(xwing =>
xwing.endOfLife.map(_ =>
xwings -= xwing
)
)
!
}
20. Notification from XWing
to Squadron
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class XWing{
var healthPoints = 1
!
val life = Promise[Unit]()
!
def receiveTorpedoFrom(sender){
life.success(null)
println(s”$this is dead”)
}
!
def endOfLife = life.future
}
class Squadron{
var xwings = Set[XWing]()
!
xwings.foreach(xwing =>
xwing.endOfLife.map(_ =>
xwings -= xwing
)
)
!
} Loosely coupled
21. Notification from XWing
to Squadron
But we starts with
asynchronous
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class XWing{
var healthPoints = 1
!
val life = Promise[Unit]()
!
def receiveTorpedoFrom(sender){
life.success(null)
println(s”$this is dead”)
}
!
def endOfLife = life.future
}
class Squadron{
var xwings = Set[XWing]()
!
xwings.foreach(xwing =>
xwing.endOfLife.map(_ =>
xwings -= deadXWing
)
)
!
} Loosely coupled
22. Notification from XWing
to Squadron
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class XWing{
var healthPoints = 1
!
val life = Promise[Unit]()
!
def receiveTorpedoFrom(sender){
life.success(null)
println(s”$this is dead”)
}
!
def endOfLife = life.future
}
class Squadron{
var xwings = Set[XWing]()
!
xwings.foreach(xwing =>
xwing.endOfLife.map(_ =>
xwings -= xwing
)
)
!
}
23. Tasks
Notification from XWing
to Squadron Notification from
Squadron to Alliance
Implements
Squadron#retreat
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
24. Notification from
Squadron to Alliance
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class Alliance{
val squadrons = Seq[Squadron]()
!
!!!!!!!
}
class Squadron{
var xwings = Set[XWing]()
!
xwings.foreach(xwing =>
xwing.endOfLife.map(_ =>
xwings -= deadXWing
)
)
!
def alliance = ???
}
25. Notification from
Squadron to Alliance
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class Alliance{
val squadrons = Seq[Squadron]()
!
!!!!!!!
}
class Squadron{
var xwings = Set[XWing]()
!
xwings.foreach(xwing =>
xwing.endOfLife.map(_ =>
xwings -= deadXWing
)
)
!
def alliance = ???
} Remember again
No bidirectional
references
26. Notification from
Squadron to Alliance
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class Alliance{
val squadrons = Seq[Squadron]()
!
!!!!!!!
}
class Squadron{
var xwings = Set[XWing]()
!
xwings.foreach(xwing =>
xwing.endOfLife.map(_ =>
xwings -= deadXWing
)
)
!!
}
33. • EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class Squadron{
var xwings = Set[XWing]()
def retreat() = {
travelTo(base)
}
!!!!!
}
Implements
Squadron#retreat
That does not
stop XWings to
fire
34. • EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class Squadron{
var fighting = false
def retreat() = {
figthing = false
travelTo(base)
}
!
def attack(deathStar){
travel(deathStar)
fighting = true
do {
xwings.foreach(_.attack(deathStar))
} while (deathStar.alive && fighting)
}
}
Implements
Squadron#retreat
Will make
the tests
green
35. • EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class Squadron{
var fighting = false
def retreat() = {
figthing = false
travelTo(base)
}
!
def attack(deathStar){
travel(deathStar)
fighting = true
do {
xwings.foreach(_.attack(deathStar))
} while (deathStar.alive && fighting)
}
}
Implements
Squadron#retreat
But wait
we have
a shared
mutable state
36. User Story
As the Alliance
!
I want to display each modification in the status
of my squadron
!
In order to see evolution of the battle
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
37. Tasks
Get current status
as String
from a Squadron
Notification at the end
of travels
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
38. • EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class Squadron{
var xwings = Set[XWing]()
!
xwings.foreach(xwing =>
xwing.endOfLife.map { _ =>
!!
}
)
!!!!!
}
Notification at the end
of travels
39. • EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class Squadron{
var xwings = Set[XWing]()
!
xwings.foreach(xwing =>
xwing.endOfLife.map { _ =>
notifyListeners()
!
}
)
!
def attack(deathStar: DeathStar){
!
travelTo(deathStar)
notifyListeners()
!
}
!
def retreat(){
travelTo(base)
notifyListeners()
}
}
Notification at the end
of travels
Will make
the tests
green
40. Tasks
Get current status
as String
from a Squadron
Notification at the end
of travels
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
41. • EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class Squadron{
var xwings = Set[XWing]()
!!
!!!!!
}
Get current status
as String
from a Squadron
42. • EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
class Squadron{
var xwings = Set[XWing]()
!
status(“idle“)
def attack(deathStar){
status(“traveling“)
notifyListeners()
!
travelTo(deathStar)
status(“figthing“)
notifyListeners()
}
!
…
!!
}
Get current status
as String
from a Squadron
That’s too
much,
there must be
another way
43. Backlog
Make the travel asynchronous
so many squadrons can move
at the same time
Persist state of squadrons
to spawn them on new VM
if lost
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
59. transaction
Root entity
ensures
consistency of
the whole
aggregate at
any time
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
60. transaction
!
can see
4 XWings
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
61. transaction
When the Alliance
can see the changes?
!
can see
3 XWings
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
62. transaction
!
can see
3 XWings
From an external
point of view, the
aggregate is
eventually
consistent!
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
63. transaction
From an external
point of view, the
aggregate is
eventually
consistent!
Root entity
ensures
consistency of
the whole
aggregate at
any time
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
66. CQRS
Command Query Responsibility Segregation
Forget about
POJOs and Java Beans
getters/setters.
Do semantical methods!
You can model your
domain twice! Once
per usage.
!
Command ≠ Query
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
67. EventSourcing
Forget about Hibernate
and other ORMs.
Persists meaningful
past events.
!
Command ≠ Event
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
68. Functional
EventSourcing
x (Aggregate) (Actions) Events Decision
Command State
making
Events x State
(Aggregate)
apply State
(Aggregate)
+
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
69. Functional
EventSourcing
You don’t even
need to persist
complete aggregates
x (Aggregate) (Actions) Events Decision
Command State
making
Events x State
(Aggregate)
state !
apply State
(Aggregate)
+
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
70. Functional
EventSourcing
x (Aggregate) Events Decision
Command State
making
Events x State
(Aggregate)
apply State
(Aggregate)
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
71. Functional
EventSourcing
source: @thinkbeforecoding
x (Aggregate) Events Decision
Command State
making
https://github.com/thinkbeforecoding/FsUno.Prod
Events x State
(Aggregate)
!
apply State
(Aggregate)
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
74. Event Actions
But where are
the views?
Command
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
75. And the views?
You can keep
specific views
inside aggregates.
You’ll bother
the root for
minor subjects. Lost(red-1)
Reporter
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
76. And the views?
You read events log
and build
ad-hoc views.
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
!
In « real-time »
or on demand.
Listener
Lost(red-1)
77. In DDD, aggregates and
entities have unique
IDs.
So does the actors
with paths and name
In DDD, value object
are everywhere and
are immutable.
So does case classes.
Immutability
help reasoning
in concurrent
world.
In DDD, aggregates
encapsulate states,
so does actors
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
100. Algebras
Fire x Fighting Lost
Fighting x Lost Fighting
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
101. Algebras
Akka killing feature
Fire x Fighting Lost
!
receive as Partial Function
Fighting x Lost Fighting
context.become()
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
102. Algebras
Think about functions
validating inputs
for some outputs
Fire x Fighting Lost
Fighting x Lost Fighting
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
103. Algebras
Finite State Machine
Fire x Fighting Lost
!
Fighting x Lost Fighting
For The Win
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
104. Algebras
NOW THE
CODE
Fire x Fighting Lost
Fighting x Lost Fighting
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
140. akka-cluster
Blue squadron Red squadron
Green squadron
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
141. akka-cluster
Affected Affected
Blue squadron Red squadron
Affected Affected
Green squadron
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
142. akka-cluster
Affected Affected
Blue squadron Red squadron
Affected Affected
Green squadron
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC
143. Final notes
Aggregate hides
implementation
details
Implementation
is focused on a
bounded context of
the whole domain
Think about messages,
Focus your attention
on interaction
over data
The less actors have
interlocutors, the better.
Take care about
message senders.
• EBIA ALLIANCE = XEBIA + XEBIALABS + THIGA + UX REPUBLIC