How to do event sourcing for complex aggregate roots

已查看 1,024 次
跳至第一个未读帖子

Vikas Pandey

未读,
2018年5月4日 13:18:382018/5/4
收件人 DDD/CQRS
Hi

I am trying to design a system with event sourcing. My domain model is quite complex and includes multiple hierarchy levels. 

class ParentAR{
         id;
         someAttributes;
         List<ChildEntity> children;
}

class ChildEntity{
        id;
        someAttributes;
        List<ChildOfChildEntity> childrenOfChild;
}

class ChildOfChildEntity{
        id;
        someAttributes.
}

I am confused if I should create events always with respect to AR or I should create events with respect to child entity. Child entity cannot exist without Parent entity. But UI not always want whole complex AR but could very well be interested in single child entity.

Should I always apply child events via AR or I could create multiple projections of AR and each projection could choose what type of events it want to apply?

Daniel Hardt

未读,
2018年5月4日 17:06:152018/5/4
收件人 DDD/CQRS
Hi.

So i think you mix here things a little bit up.
So replay the events to get the current state should only "apply via AR". This should be happpen, when you need to perform a command to your AR. Because often you need a full consistent model to perform business rules for commands and to use a projection to check business rules can end up with eventually consitency and a check to a business rule can corrupt your state. The projection itself is first at all only a read model concern. But! Sometime it makes sense to use a projected read model also in the "write model". But these situations are rare. For performance issues you should consider snapshots and not use a read model projection of your Aggregate.

So if you want to check business rules for commands, than you "replay" the events to your AR (with all entities that makes sense for this aggregate)
For the read model side you are free to build any projection you need.
So it can make sense, that only events for a child entity from your aggregate build up a read model for a special view or something.

Danil Suits

未读,
2018年5月4日 23:45:172018/5/4
收件人 DDD/CQRS

How to do event sourcing for complex aggregate roots

I haven't seen any patterns for doing that which are actually pleasant.

The closest I've seen calls for passing an event factory as one of the arguments.  The factory has state; it tracks of the events it is asked to create.  When you are done with the business logic, you query the factory to get a list of changes.  In effect, your aggregate is the journal -- the entities just help to calculate new journal entries.

The most common approach I've seen is to treat each entity as a wrapper around a snapshot of its state, and a running tally of unpersisted events.  When it's time to save the events, you walk the tree to get them. 

Rickard Öberg

未读,
2018年5月5日 00:25:532018/5/5
收件人 ddd...@googlegroups.com
This is how I do it, based on Greg's "SimpleCQRS" example code. The
aggregate is a Function<CommandWithMetadata,EventsWithMetadata>, when it
gets a command the specific method handler is invoked, which creates
events, and these are collected after handler method invoke to create the
EventsWithMetadata response, which are then either sent to event store (if
EventSourcing is used) or applied in read model database (if EventSourcing
is not used).

Pretty straightforward, and I'm surprised so few code examples posted here
use that template. Like, why not? Doing snapshot diffs is hell.

/Rickard

Daniel Hardt

未读,
2018年5月5日 04:46:322018/5/5
收件人 DDD/CQRS
I try to figure it out, why a possible complex AR is so much different from an simpel AR in terms of event sourcing.
If a business behavior happens in your system, than you call one method in your aggregate root.
In times before event sourcing, you have mutated your state of your complex aggregate with the data that comes in from the command or before we used commands in form of the incomming parameters.

void PleaseDoSomeBehavior(Command behavior)

or as we write our domain models in the past without commands

void PleaseDoSomeBehavior(int countOfSomething, string someInfoStringHere, Guid orderId)


So i am thinking, when a command comes into your complex AR there will be only one or in very special cases a little more events created. But only a few and not for every entity in your aggregate.
This event contain all necessary data to mutate your whole aggregate, you do not have to split your command into like 20 events, because you aggregate has 20 entities.

The only way, i am aware, that you get multiple events, is if you using domain events to trigger other behaviours in other aggregates, but in that case the other aggregate roots build up there own events and it is not the concern of you "Complex-AR" to process this events.

Even if a use case (service method) calls multiple behavior methods in your AR, you do not get a amout of events, that makes greg approche void for handling commands and apply events to an aggregate.

Do you have an example for a complex AR, where a command generate massive events and not only one or two?
I try to get my head around the problem.

Daniel Hardt

未读,
2018年5月5日 04:56:252018/5/5
收件人 DDD/CQRS
is it possible, that you try to generate events for every mutation in a sub entity of your aggregate?

AR -> behaviorMethod(command) -> generateEventForAggregateRoot -> calls subMethodFromSubEntity -> generatesEventInSubEntityOnlyForSubEntity -> ... SubSub ... and so on?

why would you do that? I only can imagine, that you would have a fine grain of events to build amount of read models for every single entity. But that also possible, if you use one or two events per behavior.

but maybe i am totally wrong and i had no use case in mind for this approach to build up a tree of events inside a aggregate.


Am Samstag, 5. Mai 2018 05:45:17 UTC+2 schrieb Danil Suits:

Rickard Öberg

未读,
2018年5月7日 00:12:212018/5/7
收件人 ddd...@googlegroups.com
On Sat, May 5, 2018 at 4:46 PM Daniel Hardt <i...@dieselmeister.com> wrote:
> So i am thinking, when a command comes into your complex AR there will be
only one or in very special cases a little more events created. But only a
few and not for every entity in your aggregate.
> This event contain all necessary data to mutate your whole aggregate, you
do not have to split your command into like 20 events, because you
aggregate has 20 entities.

> The only way, i am aware, that you get multiple events, is if you using
domain events to trigger other behaviours in other aggregates, but in that
case the other aggregate roots build up there own events and it is not the
concern of you "Complex-AR" to process this events.

I have a different way of thinking of the purpose of commands and events,
that will make the above not true in many cases. To me, a command reflects
the intention of the user, so whatever parameters are captured by the UI to
perform an action, that is what is in the command. The events are NOT the
past tense of this verb-ish thing, but rather related to changes in state
of domain concepts. In my domain, the simplest example where this is indeed
a 1-1 mapping might be "like video" command -> "liked video" event. But for
example the Register command in my system leads to the following events
being output:
CreatedUser
ChangedSegment
AddedUserTag (e.g. add student tag if email matches uni domains)
UpdatedEmail
ChangedPassword
ActivatedUser
ChangedUserStatus
AddedTracking

As you can see above, each of those events are related to nouns, fields,
statuses, etc. rather than being "Registered" or somesuch. Because they
relate to domain concepts rather than the command they are highly reusable.
If you change your email in the account settings that is the same
UpdatedEmail event, rather than "ChangedAccount". This makes events
reusable in many different contexts, simplifies any reporting based on
events, and allows you to extend the system often without having to create
any new events at all.

So yes, in many cases 1 command leads to 1 event, but when there's any kind
of complexity going on, there will probably be more than 1 event triggered,
and has nothing to do with external systems. For example, UpdatedEmail
event above triggers handlers that update our mailing list as well as
customer support systems, which I could trivially add after these events
were created in code, as the events are not aware of any such external
systems.

/Rickard

Greg Young

未读,
2018年5月7日 02:19:292018/5/7
收件人 ddd...@googlegroups.com
Another good example of this can be seen in finance PlaceOrder ... It can result in OrderPlaced, A series of TradeOccureds (possibly with an OrderPlaced), a series of trade with an ordercancelled, etc etc.



--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+unsubscribe@googlegroups.com.
Visit this group at https://groups.google.com/group/dddcqrs.
For more options, visit https://groups.google.com/d/optout.



--
Studying for the Turing test

Vikas Pandey

未读,
2018年5月7日 02:38:432018/5/7
收件人 DDD/CQRS
You are  right that I have events for sub and sub sub entity. It is not only just for my read model but since my AR is too complex and business commands can change state of entities as well. My problem is not the number of events per behaviour. I know that one behavior will typically generate one event. My problem is my behaviors are for sub sub sub entities as well. I am not sure if I should break my AR model to reduce complexity or not. I do not want to break my AR model because all the sub sub entities cannot exist without their parent.
Problem is event if my AR is so complex if I have to create a complete AR it will degrade my performance because of applying sub sub entity events.

In most of the cases my caller will not be even interested in these sub entities. They might make a seperate query for getting these sub entities. In such case  should I keep different projections of my AR? one in which all sub entity events could apply and one where there will not be sub entity details in AR but only AR details?

Vikas Pandey

未读,
2018年5月7日 02:49:042018/5/7
收件人 DDD/CQRS
What you have explained here is the case when one event can trigger another command. In this case you updatedEmailAddress lead to another command to updateMailingList. 

What I could understand from reading your earlier replies is that replay of an event is completely dependent on your AR model. If you have different projections of AR one with some details and one with many complex details, then all that changes is the amount of events you are going to replay. for simple AR projection you might just replay events directly related to AR state and not the child entities but for complext AR projection you might fetch more events even related to sub entities.

All that matters is which events you fetch during your query call. 

Rickard Öberg

未读,
2018年5月7日 03:11:392018/5/7
收件人 ddd...@googlegroups.com
On Mon, May 7, 2018 at 2:49 PM Vikas Pandey <dj.vi...@gmail.com> wrote:

> What you have explained here is the case when one event can trigger
another command. In this case you updatedEmailAddress lead to another
command to updateMailingList.

Absolutely, but that is a separate system reacting to state changes in the
main domain. The main domain, which "UpdatedEmail" comes form, has no
knowledge of these other things that react to those changes.

/Rickard

Alberto Brandolini

未读,
2018年5月7日 03:35:582018/5/7
收件人 ddd...@googlegroups.com
Hi all, 

I would probably give a try to a more simpler aggregates, instead of fewer complex ones.

There's too little in this thread to tell whether the "very complex aggregate constraint" must be accepted or can be challenged, but I'd like to see where a different model will lead me.

Alberto


photo
Alberto Brandolini
Pragmatic Visionary, Avanscoperta

+39 0546 646007 | +39 347 6005027 | alberto.b...@avanscoperta.it

www.avanscoperta.it

Via S. Silvestro 168 48018 Faenza (RA) - Italy

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.

Daniel Hardt

未读,
2018年5月7日 11:53:252018/5/7
收件人 DDD/CQRS
And here i see problem. The events you mention are for itself for behavior, but why would you generate a high number of events, when you create a user. The event usercreated can contain most of the data, maybe not all, but on my opinion this is an exessive use of events. For only one simple command. And that is what i mean. When your system creates 1 million user, you have to store 10 million events for a simple task, like a create user command.

In greg's example in financial systems it makes sense, by placing an order to create maybe a partial succeeded orders and split the rest to a pending ask ot bid or somethings. But there, it is necessary. In your example you do to much for a simple command, at least the halve of your events are overhead.
In a context of another behaviours, it makes sense to have an fitting events for like change address oder email or something.
But for every property, every subentity only for the sake of having events that changes only on property is to munch in my opinion.
Keep it simple. If you have all these behaviors, you need all these events. Okay. But, keep it simple. Keep in mind, that you have to replay all these events. And it is more perfomant when a CreatedUser event already set the password and email and so one, instead generate for all these little steps an extra event,even if you had an separate command for changing the address that generates the addressChanged event.


Daniel Hardt

未读,
2018年5月7日 12:15:582018/5/7
收件人 DDD/CQRS
@Rickard

As I longer think about your approach to split the create in small events, that are already there... It sounds good, even if it looks exessive in the first place. In the words of the greatest philosopher of my youth "Narf...!" :D thank you for the input.

Rickard Öberg

未读,
2018年5月7日 22:33:322018/5/7
收件人 ddd...@googlegroups.com
On Mon, May 7, 2018 at 11:53 PM Daniel Hardt <i...@dieselmeister.com> wrote:
> Keep it simple. If you have all these behaviors, you need all these
events. Okay. But, keep it simple. Keep in mind, that you have to replay
all these events. And it is more perfomant when a CreatedUser event already
set the password and email and so one, instead generate for all these
little steps an extra event,even if you had an separate command for
changing the address that generates the addressChanged event.

I think you might be conflating "simple" and "easy". Is it easier to turn
"Register" into just one "CreatedUser". Yes. At that point. Sure. Is it
simple? Not in the least. Simple means to treat each thing separately, to
not complect things together that don't belong together, and that is what
the multi-event approach enables. My mailing list and customer support
integrations just need to understand one event to track email changes. With
your suggested "simple" approach it would have to be changed every time we
introduce a new screen that allows email addresses to be changed. And any
projection would have to know about all the different events that relate to
the thing they want to project, rather than the one or few events in the
approach I described.

So I would contend that letting events be related to domain aspects rather
than commands ("screens") is simpler, which in the long run will also be
easier.

/Rickard

Greg Young

未读,
2018年5月7日 23:32:502018/5/7
收件人 ddd...@googlegroups.com

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+unsubscribe@googlegroups.com.

Visit this group at https://groups.google.com/group/dddcqrs.
For more options, visit https://groups.google.com/d/optout.

Danil Suits

未读,
2018年5月11日 09:42:072018/5/11
收件人 DDD/CQRS


On Saturday, May 5, 2018 at 12:25:53 AM UTC-4, Rickard Öberg wrote:

Pretty straightforward, and I'm surprised so few code examples posted here
use that template. Like, why not?

My current answer: it puts the modeling of the events in the wrong place.

Think ledger -- we track changes by adding entries to an append only data structure that lives in the domain model.  If you need to answer a query (currentBalance?), you fold/reduce.

Making the ledger domain agnostic, with a bunch of reflection magic to connect it back to the domain model, strikes me as a step in the wrong direction.

You want an event sourced shopping cart?  Great -- figure out the name for the ledger of changes and put it into your domain model.

Rickard Öberg

未读,
2018年5月13日 00:30:252018/5/13
收件人 ddd...@googlegroups.com
On Fri, May 11, 2018 at 9:42 PM Danil Suits <danil...@gmail.com> wrote:
> On Saturday, May 5, 2018 at 12:25:53 AM UTC-4, Rickard Öberg wrote:


>> Pretty straightforward, and I'm surprised so few code examples posted
here
>> use that template. Like, why not?


> My current answer: it puts the modeling of the events in the wrong place.

> Think ledger -- we track changes by adding entries to an append only data
structure that lives in the domain model. If you need to answer a query
(currentBalance?), you fold/reduce.

Exactly.

> Making the ledger domain agnostic, with a bunch of reflection magic to
connect it back to the domain model, strikes me as a step in the wrong
direction.

Domain agnostic? Reflection magic? What are you talking about? The example
I had was the aggregate as a function from command to a list of events. The
command is an instance of a specific command class. The events are
instances of specific event classes. What's this agnostic thing you speak
ok?

/Rickard
已删除帖子

Danil Suits

未读,
2018年5月14日 17:46:162018/5/14
收件人 DDD/CQRS


On Sunday, May 13, 2018 at 12:30:25 AM UTC-4, Rickard Öberg wrote:
Domain agnostic? Reflection magic? What are you talking about? The example
I had was the aggregate as a function from command to a list of events. The
command is an instance of a specific command class. The events are
instances of specific event classes. What's this agnostic thing you speak
ok?

My fault.

I was speaking of the domain agnostic implementation details within SimpleCQRS.AggregateRoot.

Had I written your original post more carefully, I would have noticed that you were describing your interface, rather than your implementation. 
回复全部
回复作者
转发
0 个新帖子