DDD DYSFUNCTIONAL...3/13/2019 reveal.js 1/133 DYSFUNCTIONAL DDD
Transcript of DDD DYSFUNCTIONAL...3/13/2019 reveal.js 1/133 DYSFUNCTIONAL DDD
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 1/133
DYSFUNCTIONALDYSFUNCTIONALDDDDDD
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 2/133
JAREKJAREKWizard, Anarchitect, Coder
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 3/133
JAREKJAREKWizard, Anarchitect, Coder
> 25 years of coding
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 4/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 5/133
DISCLAIMERDISCLAIMER
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 6/133
DISCLAIMERDISCLAIMERI tried not to attack anyone personally, actually I dolike my colleagues doing DDD, and I have learned alot from some of them
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 7/133
DISCLAIMERDISCLAIMERI tried not to attack anyone personally, actually I dolike my colleagues doing DDD, and I have learned alot from some of themnot an expert, (some experience wih CQRS/ES notDDD)
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 8/133
WHY DDD?WHY DDD?People talk about itObject orientedworldLike mixing concepts
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 9/133
My problem with DDD
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 10/133
DDD story resembles me
UMLwhat happened toAgile
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 11/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 12/133
cool
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 13/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 14/133
What??
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 15/133
AGILE COOLAGILE COOLTests, TDDCI,Code overcommentscontact with user
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 16/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 17/133
AGILE REALITYAGILE REALITYboringmeetingsagile coaches,certificatesagile toolsbooksconferencestrainingsvelocity :-)
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 18/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 19/133
There is nothing wrong about money.
I work for money!
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 20/133
But who likes marketing b...it?!
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 21/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 22/133
For years I was scared of DDD marketing
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 23/133
BOOKSBOOKS
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 24/133
Make logic of the system visible in code
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 25/133
DDD GOOD PARTSDDD GOOD PARTScommunityconstantimprovementlots of patterns /ideasinteresting stories
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 26/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 27/133
DDD bad(?) parts
marketingexample projectsover-engineeringpartly toxiccommunityhard to see the point
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 28/133
If a tool, methodology, framework makes simple casecomplex
it will probably not make a real life, complex projectsimple
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 29/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 30/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 31/133
BUTBUT
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 32/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 33/133
DDDDDD
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 34/133
HARD WAYHARD WAY
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 35/133
Skipping some essential parts.
Like bounded context etc.
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 36/133
DDD is mostly not about technology
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 37/133
FINDING COMMONFINDING COMMONLANGUAGELANGUAGE
Ubiquitous Language
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 38/133
Some concepts are useful, but have nothing to do withFP
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 39/133
I just love to code more
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 40/133
And you wil not pay me areal money (for a fancy car) :-(
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 41/133
LETS PLAY SNAKELETS PLAY SNAKE(MULTIPLAYER)(MULTIPLAYER)
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 42/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 43/133
COMMANDSCOMMANDS
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 44/133
data SnakeCommand = SetDirection { wantedDirection :: SnakeDirection } | MakeStep | Begin { initName :: String , initCell :: SnakeCell }
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 45/133
COMMANDSCOMMANDSuser or subsytem wants to do somethingcommands may be associated withvalidation
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 46/133
EVENTSEVENTSdata SnakeEvent = DirectionChanged { newDirection :: SnakeDirection } | StepMade | Killed | Born { bornName :: String , bornCell :: SnakeCell } deriving (Eq, Show, Generic)
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 47/133
EVENTEVENTit has happenedno validation (it really happened)a single command is associated in 0..nevents
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 48/133
Fact
You will not find anything about this in Eric Evans book(blue))
Even though nowadays DDD community seems to beall around those concepts
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 49/133
EVENT SOURCINGEVENT SOURCINGWhat if just stored only events?
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 50/133
[ { "event": { "tag": "Born", "bornName": "aa", "bornCell": { "cellY": 18, "cellX": 14 } }, "snakeId": "4d09ac06-0375-4cb0-ad08-c70d14968677" }, { "event": { "tag": "DirectionChanged",
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 51/133
COMMANDCOMMANDSOURCING?SOURCING?
Also possible... but in fact harder - validation is aproblem
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 52/133
VALUE OBJECTVALUE OBJECTimmutable...represents value (from reallife)has no identityproperties define equality
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 53/133
type SnekeId = String type NickName = Text data SnakeDirection = SnakeUp | SnakeRight | SnakeDown | SnakeLeft
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 54/133
ENTITYENTITYhas identityin OOP may be mutableID defines equalityentity may contain valueobjects
entity may contain entities
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 55/133
data SnakeCell = SnakeCell { cellX :: Int , cellY :: Int } data SnakeState = Alive { direction :: SnakeDirection , cells :: [SnakeCell] , maxLength :: Int } | Dead | Init data Snake = Snake { name :: String
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 56/133
Easy?
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 57/133
SnakeCell(x,y) is value object orentity?
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 58/133
To think
I have never introduced type SnakeEntity =(SnakeId, Snake)
Only have SnakeState which does not (physically)contain Id
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 59/133
To think
I have never introduced type SnakeEntity =(SnakeId, Snake)
Only have SnakeState which does not (physically)contain Id
an Entity without id??
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 60/133
Some DDD concepts may not be explicitly existing incode
(SnakeId, Snake)
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 61/133
It gets worse
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 62/133
AGGREGATEAGGREGATE
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 63/133
Cluster of objects (entities, value objects, + )
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 64/133
Aggregate remains consistent
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 65/133
Keeps invariants
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 66/133
Transactions should not cross aggregates
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 67/133
AGGREGATE ROOTAGGREGATE ROOTSelected entity from Aggregate (root)outside world communicates with it (sendscommands)outside world only keeps reference to this rootobject
command handlerevent handler
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 68/133
Typeclass source:
class Aggregate s where data Error s :: * data Command s :: * data Event s :: * execute :: s -> Command s -> Either (Error s) (Event s) apply :: s -> Event s -> s seed :: s
https://gist.github.com/Fristi/7327904
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 69/133
COMMAND HANDLERCOMMAND HANDLERexecuteCommand :: Snake -> SnakeCommand -> [SnakeEvent]
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 70/133
BETTER COMMANDBETTER COMMANDHANDLERHANDLER
executeCommand :: Snake -> SnakeCommand -> Either MyError [SnakeEvent]
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 71/133
EVENT HANDLEREVENT HANDLERapplyEvent::Snake-> SnakeEvent -> Snake
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 72/133
SUMMARYSUMMARYdefine commandsdefine eventsselect root Entitydefine commandshandlerdefine events handler
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 73/133
Real Command handler
executeCommand :: SnakeData -> SnakeCommand -> [SnakeEvent] executeCommand SnakeData {state = Alive {}} MakeStep = [StepMaexecuteCommand _ MakeStep = [] executeCommand SnakeData {state = Alive {direction = od}} SetD | opposite = [] | otherwise = [DirectionChanged {newDirection = nd}] where opposite = V.dirIs0 $ V.dirPlus newVec currentVec newVec = V.dirVector nd currentVec = V.dirVector od executeCommand anySnake SetDirection {} = [] executeCommand SnakeData {state = Alive {}} Die = [Killed] executeCommand _ Die = [] executeCommand SnakeData {state = Init} Begin {initName = d, iexecuteCommand _ Begin {} = []
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 74/133
Real event handler
applyEventX snake@(SnakeData {state = alive@Alive {}}) Directi makeRes $ snake { state = alive{direction = nd} } applyEventX snake@(SnakeData {state = Alive {}}) Killed = makeapplyEventX SnakeData {state = Init} Born {bornName = nm, born newSnake = SnakeData { name = nm , state = initial newCells = [cell], removedCells = [] } where initialState = Alive { direction = SnakeUp, cells = [applyEventX snake@(SnakeData {state = alive@Alive {maxLength = makeRes snake { state = alive { maxLength = n+3} } applyEventX _ _ = error "todo"
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 75/133
Modelling with events, commands is not needed inDDD
It was not even considered in an original DDD book
behaviour first seem to be quite efficient
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 76/133
Event storming
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 77/133
Alternative to command handler / Aggregate
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 78/133
Commands
data SnakeCommand = SetDirection { wantedDirection :: SnakeDirection } | MakeStep | Begin { initName :: String , initCell :: SnakeCell } | Die
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 79/133
Have You seen that before?
data SnakeCommand next = SetDirection { wantedDirection :: SnakeDirection } | MakeStep next | Begin { initName :: String , initCell :: SnakeCell } next | Die
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 80/133
Free monad DSL
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 81/133
Seems to be more usable in sequencing
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 82/133
In typical REST we have one http call -> onecommand. Sequencing is not that needed.
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 83/133
HOW TO FINDHOW TO FINDAGGREGATES?AGGREGATES?
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 84/133
Whole system as an aggregate?
(One Big Aggregate)
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 85/133
REPOSITORYREPOSITORY
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 86/133
Remember DAO?
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 87/133
Magic...
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 88/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 89/133
loadEntity::Id->IO EntitysaveEntity::Id->Entity->IO()etc...
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 90/133
Fact Lots of magic Java frameworks trace state ofobjects and automatically persist changes to database
This means that a sensible repository save methodmay look like:
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 91/133
void save(MyObject t) { }
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 92/133
Yep, this works. There are lot of such projects.
void save(MyObject t) { }
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 93/133
But what in case of event sourcing?
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 94/133
data SnakeAggregate = SnakeAggregate { state :: SnakeState, uncommittedEvents :: [SnakeEvent] }
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 95/133
Common pattern in DDD style event sourcing is to savethose uncommited events
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 96/133
I find it unnatural
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 97/133
I started to send commands to a Repository
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 98/133
applyCommand :: SnakesRepo-> SnakeId -> Snake.SnakeCommand -> IO SnakesRepo
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 99/133
This is so unDDD
applyCommand :: SnakesRepo-> SnakeId -> Snake.SnakeCommand -> IO SnakesRepo
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 100/133
Makes for more sense than repeating a code with saveevents
In my aggregates I do not have those uncommitedevents (is this a domain?)
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 101/133
CQSCQSCommand Query separation
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 102/133
If you ask (Query) do not change the state
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 103/133
If You change state (Command) do not expect result
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 104/133
A Stackvoid stack.push( T t); T stack.pop();
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 105/133
A CQS Stackvoid stack.push( T t); T stack.top(); void stack.pop();
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 106/133
Simple?
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 107/133
what if called on empty stack?void stack.pop(); // boom
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 108/133
OO world consensus:
commands may return exceptions, some status, etc.
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 109/133
Is an error not a result?
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 110/133
In FP world
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 111/133
push::Stack a->a->Stack a top::Stack a->a pop::Stack a->Stack a
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 112/133
In FP world each operation gives a result
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 113/133
IO ()
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 114/133
Is it really bad?
whatAPop::Stack a->(Stack a, a)
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 115/133
Actually I do not see much sense in classical CQS
nice to have separated queriesError/Exception is an resultIt only makes API easier to use ... in some mediocrelanguagesasync?
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 116/133
push::Stack a -> MonadAsync (Stack a)
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 117/133
CQRSCQRSCommand Query Response Segregation
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 118/133
CQRS ~ CQS on a higher level
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 119/133
Write/Command model - Aggregates
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 120/133
Read/Query model - Projections
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 121/133
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 122/133
Some CQRS principles
Critical: Events application cannot use any externaldata (projection)So�er: Commands should not use projection toproduce EventsYou can always recreate Aggregates using eventsYou can always recreate multiple projections usingevents
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 123/133
Fact
In less impure languages it is easy to make amistake
LocalDate.now() someRandom.nextInt()
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 124/133
Fact
In typical Event souring operations likefindMeIdsOfAllAggregates are performed
using projections
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 125/133
My game field projection
data PlaneState = PlaneState { allSnakes :: Repo.SnakesMap , allCells :: CellsMap , changes :: Changes } deriving (Show, Generic) -- projection applyEvent :: PlaneState -> Repo.SnakeQualifiedEvent -> (Plane
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 126/133
Used by browser
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 127/133
Projections are great:
potentialperformancemake UI code simpler
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 128/133
I use them to detect collisions
If I find collision on a gameField I send Die commandto snake
I read few times this is wrong...
But I do not see better solution (other than One BigAggregate)
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 129/133
In CQRS some operations are quite hardnextId::Sequence->Int
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 130/133
CODECODEdsnake - haskell rest server, (yesod)
Work in progress
Please, do not use it as a sensible DDD exampleresource (yet)
https://github.com/jarekratajski/dysfunctional_ddd
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 131/133
RESOURCES ONRESOURCES ONDDD/CQRSDDD/CQRS
Bottega presentations
"The" books
http://CQRS.nu (FAQ) page
https://github.com/ddd-by-
examples/event-source-cqrs-sample byKuba Nabrdalik
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 132/133
https://github.com/vlingo vlingo platform
3/13/2019 reveal.js
http://localhost:8000/?print-pdf#/ 133/133
Q?Q?