@johnbywater @SKleanthous @RagingKore That was a long interesting thread :) and there are many things to discuss here.
After discussion with Eric and other DDD practitioners, the tactical patterns are very concrete, but come from more abstract ideas.
@johnbywater @SKleanthous @RagingKore The aggregate is implemented as a class that references other entities and value objects to maintain consistency. But the more general description is that this is something that is defined by a consistency boundary. What is inside is consistent, what's outside *doesn't have to*
@johnbywater @SKleanthous @RagingKore Once this is said, an aggregate should not have to call another's method aggregate as part of it's own decision. (or they would become consistent together, and be a single aggregate).
@johnbywater @SKleanthous @RagingKore (warning: I doesn't mean it's forbidden to do so, just that it should still work the same if you make the call asynchronous)
@johnbywater @SKleanthous @RagingKore In this setting, the repository tend to be useless. Aggregates are always manipulated by the application layer, not needing an domain abstraction.
@johnbywater @SKleanthous @RagingKore The state "inside" the aggregate will not be used by other aggregates by definition (using it directly for it to be consistent, back to a single aggregate). so it's purely private.
@johnbywater @SKleanthous @RagingKore Other aggregates can still rely on views of this state. This is the idea behind CQRS. These views are conceptually async (but again, use sync as long as switching to async doesn't break the model)
@johnbywater @SKleanthous @RagingKore Now, about the functional version of Event Sourcing.
The whole reason of an aggregate is to change over time, in a consistent fashion. (If it's constant, consistency is trivial). And and aggregate never change for no reason (there is a stimulus, we work in discrete time)
@johnbywater @SKleanthous @RagingKore We call Command this stimulus, a request to change. The aggregate is itself responsible for the change.
To decide what happens, it need the information on the stimulus (the Command).
@johnbywater @SKleanthous @RagingKore With only the Command, it will always react the same way to the same stimulus. So there is no change, so we don't need to talk about consistency and aggregate.
@johnbywater @SKleanthous @RagingKore So we need to have information about what happened before, to take different decisions depending on prior history.
@johnbywater @SKleanthous @RagingKore we chose to return decision as a data structure called an Event that describe what is happening to the aggregate given a stimulus and prior history.
@johnbywater @SKleanthous @RagingKore That is the more general way to express it. Then there are many variations on how to implement it, and many shortcuts can be taken.
@johnbywater @SKleanthous @RagingKore Conceptually you need the history to take decision. But you can condense history to a state. There are now 2 ways to do it:
* Mutate state fields directly inside the data structure
* Fold the list of events with an evolution function to compute current state
@johnbywater @SKleanthous @RagingKore It is possible to construct state from the history, but not always possible to reconstruct history from state.
@johnbywater @SKleanthous @RagingKore But using pure decide/evolve functions doesn't mean you have to save events and rebuild from them:
state = loadstate()
events = decide(command, state)
newstate = events.fold(evolve,state)
savestate(newstate)
@johnbywater @SKleanthous @RagingKore If something needs a view of the state, you can update it on the fly, or compute current view state from events, or just take the full list of events (which is a view also).
@johnbywater @SKleanthous @RagingKore The interesting thing with event list for history is that they define a monoid.
And the lifted evolve function (State -> Event list -> State) defines an external monoid (evolve is an external op, that respect monoidal properties of the Event list).
@johnbywater @SKleanthous @RagingKore So it describes a dynamical system where time is still monoidal, but discrete and evenmential (Kairos). usual description use clock time (Chronos), as real numbers or integers).
@johnbywater @SKleanthous @RagingKore The chose to use the term decider for a specific reason. It is isomorphic to aggregate, but has a specific shape that has interesting properties, especially it composes.
@johnbywater @SKleanthous @RagingKore It also isolates the application from the domain in a very efficient way. And can be run in many different styles. Then you can use these properties to take shortcuts.
@johnbywater @SKleanthous @RagingKore Because you have guarantees from these properties.
@johnbywater @SKleanthous @RagingKore Also, there is no 'one list of events', history is the set of all the homomorphic structures that contains enough information to know what happened and take next decision. The we chose one representation for implementation.
@johnbywater @SKleanthous @RagingKore The Java code in the book (classic approach) is taking several shortcuts:
Taking decision and apply it in the same method:
DoSomething cmd state =
events = decide cmd state
newstate = events.Fold(evolve,state)
return newstate
@johnbywater @SKleanthous @RagingKore Second shortcut is mutation:
class Agg
field state = initialState
doSomething cmd =
events = decide cmd this.state
this.state <- events.fold(evolve,this.state)
@johnbywater @SKleanthous @RagingKore Third shortcut is inlining everything
class Agg
field someStateField
field otherStateField
...
doSomething(cmdArg, otherCmdArg )
mutateSomeStateFieldsOnSomeDecisionFromCmdArgsAndPreviousStateFieldValues()
@johnbywater @SKleanthous @RagingKore Those shortcuts are not inherently bad. For instant it could give better perf in some context (mutability, less allocations, parts of code closer to each other), but for each shortcut you loose some properties and some guarantees.
@johnbywater @SKleanthous @RagingKore If this is a deliberate trade-off, then OK. But most often, it just that the underlying structure has not been seen, and some ad-hoc code was thrown at the problem.

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Jérémie Chassaing

Jérémie Chassaing Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @thinkb4coding

10 Jul 20
After several discussions about event sourcing today, I think it's time to clarify things. Thread ⬇ 1/8
When talking about Query eventual consistency, views autorebuild with 0 downtime deployment, snapshots, automatic failover, we're in the Advanced Non Functional Requirements column with high availability, low latency, distributed systems, so infra is advanced and non obvious 2/8
This is normal, you cannot have high quality of service under scale and hope it will work like magic. You'll have to solve some problems, and all solutions will not be obvious. Distributed systems are hard. 3/8
Read 9 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!

Follow Us on Twitter!

:(