Varying the Behavior of an Aggregate Over Time

458 views
Skip to first unread message

Johanna Belanger

unread,
Apr 24, 2013, 3:09:33 PM4/24/13
to ddd...@googlegroups.com

 I have also found the answer I was looking for in a link posted by Johanna ( https://groups.google.com/d/msg/dddcqrs/toU9nBeXWkQ/hyVX8QjS6gIJ) about loading the same stream into a different aggregate when the original aggregate transitions to a new state. Previously I was trying to do this by creating another aggregate out of the first one but now I can see how it could be done differently. Only need to solve snapshotting problem when taking this road.

State transitions are one example of the need to vary an aggregate's behavior over time. Other examples are changes to business policies, laws, specifications, and maybe bug fixes, after an aggregate is in production. 

In any of these cases, we have to be careful to respect consistency boundaries and not to accidentally introduce conflicting views of history. The choice of behavior depends on the state of the aggregate (or the point in the aggregate's timeline), so it makes sense to keep the choice and the state in the same consistency boundary. Tracking changes of behavior explicitly inside the aggregate ensures that an aggregate can always apply historical events consistently.

Consequently, I'm currently thinking an aggregate's event stream should only be *changed* by a single aggregate. The "different views" on the stream could be implemented by varying the behavior inside the aggregate (using the State or Strategy patterns, for example). If snapshots are used, the aggregate can produce a snapshot that contains what it will need to handle current and possible future states.

Sometimes it might be desirable to load an aggregate's event stream into a different aggregate, effectively "branching" the aggregate. But I don't think the branch should be allowed to write to the original aggregate's event stream.

Does anyone have a different opinion on this?

A simple example, using the Account Open/Closed states from the linked thread:

class Account
{
   private AccountBehavior currentAccount;
   private ActiveAccount activeAccount;   
   
   public DoSomethingActive()
   {
      currentAccount.DoSomethingActive();
      ...apply resulting events...
   }

   ...other commands...

   private apply(AccountCreated event)
   {   currentAccount = new ActiveAccount();

   private apply(AccountClosed event)
   {   currentAccount = new ClosedAccount();}
   
   private apply(AccountReactivated event)
   {   currentAccount = activeAccount;}

   ...other applys....

   private interface AccountBehavior
   {
       void DoSomethingActive();
   }

   private class ActiveAccount: AccountBehavior
   {  
       void DoSomethingActive()
       {
        ...DidSomethingActive event
       }
   }

   private class ClosedAccount: AccountBehavior
   {   
       void DoSomethingActive()
       {
        ...DidNotDoSomethingActiveBecauseAccountIsClosed event
       }
   }
}

Greg Young

unread,
Apr 24, 2013, 3:40:15 PM4/24/13
to ddd...@googlegroups.com

this is perfectly ok a stream can be modeled by many aggregates and in many cases changed by many!

--
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.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

João Bragança

unread,
Apr 24, 2013, 3:48:55 PM4/24/13
to ddd...@googlegroups.com
How would this work in practice? Are these aggregates in the same BC? If A and B are writing to the same stream, would A just skip over events it doesn't understand when it rehydrated?

Johanna Belanger

unread,
Apr 24, 2013, 3:49:29 PM4/24/13
to ddd...@googlegroups.com
I thought you might say that! =) 

What is necessary, then, to protect all invariants and maintain a consistent view of history within an aggregate's stream?

Greg Young

unread,
Apr 24, 2013, 3:49:59 PM4/24/13
to ddd...@googlegroups.com

can even be different bcs at times but yes if they dont understand skip

Johanna Belanger

unread,
Apr 24, 2013, 4:07:04 PM4/24/13
to ddd...@googlegroups.com
So as long as they don't write the same events they don't end up affecting each others state. Therefore no broken invariants and no inconsistent history, right? Then is it correct to say no two aggregates should write the same type of event to a single aggregate's event stream?

Greg Young

unread,
Apr 24, 2013, 4:18:07 PM4/24/13
to ddd...@googlegroups.com

to be fair i have only used the strategy twice... however it fits in well with the event sourcing mental model.

Philip Jander

unread,
Apr 24, 2013, 4:38:32 PM4/24/13
to ddd...@googlegroups.com
Am 24.04.2013 21:49, schrieb Johanna Belanger:
> I thought you might say that! =)
>
> What is necessary, then, to protect all invariants and maintain a
> consistent view of history within an aggregate's stream?

I'd say that the type of aggregate allowed write access to a stream
would have to be a projection of that stream. How that for a
chicken-and-egg problem? :)
But it would solve the unique write access problem, provided that any
aggregate bails out as soon as that stream becomes the realm of another
aggregate.
However, I think this is only feasible in a functional style model where
the current aggregate is a live projection of the stream (committed or
uncommitted).

Using a common oo domain model, i believe it is far easier to implement
the behaviour you seek by using a strategy pattern. Effectively the
aggregate would remain the same but be allowed to change its behaviour,
kindof like an erlang actor.
Technically that would make the strategy a singleton entity within that
aggregate.

What do you think about this approach?

Cheers
Phil

Greg Young

unread,
Apr 24, 2013, 4:40:26 PM4/24/13
to ddd...@googlegroups.com
it depends.

Quite often in the object (or actor) world its better to model with
multiple that go through a lifecycle than a single with varying
states. An example can be seen here
http://codebetter.com/gregyoung/2010/03/09/state-pattern-misuse/
> --
> 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.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>



--
Le doute n'est pas une condition agréable, mais la certitude est absurde.

df

unread,
Apr 24, 2013, 4:47:16 PM4/24/13
to ddd...@googlegroups.com
Say for example an OpenAccount aggregate is closed and emits an AccountClosedEvent. From then on the stream is accessed through ClosedAccount aggregate. Then does it mean that ClosedAccount needs to guard all its methods to ensure that it is indeed a closed account stream that has been loaded into the aggregate. Same applies to the OpenAccount aggregate, it would need to guard its methods to ensure it can't be used once it has been closed through the OpenAccount aggregate? Is this the way to approach such a scenario?

@yreynhout

unread,
Apr 24, 2013, 7:41:38 PM4/24/13
to ddd...@googlegroups.com
Don't see a reason why a stream should be coupled to a class. Has little todo with how we model (e.g. aggregates). At the end of the day, you just need to know which stream you need to append to. As for invariants protection, that's not really a stream related concern (code changes all the time, yet our stream and its events don't).

Time might be a natural boundary to split up stream's along (e.g. New stream for each quarterly period, ...).

Johanna Belanger

unread,
Apr 25, 2013, 2:25:50 AM4/25/13
to ddd...@googlegroups.com
Wow, I have to say this conversation is blowing my mind...

I was under the impression that since an aggregate's state is a function of its event stream, and an aggregate can't reject events, then the stream better not leave the aggregate with broken invariants! It seems like writing to an aggregate's event stream let's you touch its private state.

What am I missing?

Johanna Belanger

unread,
Apr 25, 2013, 2:31:05 AM4/25/13
to ddd...@googlegroups.com
"the stream better not leave the aggregate with broken invariants"

Um, my wording was a little ambiguous there... I meant that an aggregate must not have broken invariants after being rehydrated from its event stream.

@yreynhout

unread,
Apr 25, 2013, 4:33:32 AM4/25/13
to ddd...@googlegroups.com
You are misinterpreting what I'm saying (or I didn't express myself well). I'm not saying break the invariants. I just do not consider it a stream concern. Yes, you still need to enforce invariants in the code that has loaded the stream. Simplified, I'd say that invariants are enforced using the state directly and the stream indirectly. I donnow if I'm making sense. It's clear in my head but not in my wording at this point. Sounds a bit like what I've written about here: http://seabites.wordpress.com/2013/02/24/a-role-to-play/

Philip Jander

unread,
Apr 25, 2013, 6:46:14 AM4/25/13
to ddd...@googlegroups.com
Am 24.04.2013 22:40, schrieb Greg Young:
> it depends.
>
> Quite often in the object (or actor) world its better to model with
> multiple that go through a lifecycle than a single with varying
> states. An example can be seen here
>
modeling with multiple, you will need some kind of supervisor / wrapper
responsible for coordinating the "multiple" contributing to a single
event stream. Then you supervisor becomes your new aggregate, as per
"consistency boundary" definition? ;)

Cheers
Phil

Philip Jander

unread,
Apr 25, 2013, 6:55:58 AM4/25/13
to ddd...@googlegroups.com
Am 25.04.2013 08:25, schrieb Johanna Belanger:
> Wow, I have to say this conversation is blowing my mind...
>
> I was under the impression that since an aggregate's state is a
> function of its event stream, and an aggregate can't reject events,
> then the stream better not leave the aggregate with broken invariants!
> It seems like writing to an aggregate's event stream let's you touch
> its private state.
>
> What am I missing?
>
Not much. You have just stumbled upon a problem which is (at least as
fas as i am concerned) somewhat unresolved.

My 2cts:
Conceptually, events (as in business history) exist before aggregates.
Business description should be way more stable than a particular
implementation. Technically, when we talk about event sourcing, a stream
of events originate from a single aggregate (a group if you model
aggregate state change with a few aggregates instead of just internally
within the aggregate). There, the events exist to serve the aggregate's
history in that particular implementation.

Both views collide if you either consider changes to your aggregate
structure, or if you model behavior affecting a single event stream with
multiple aggregates (your initial question).

The problem is well illustrated by considering an interaction point
between two distinct (event sourced) aggregates: conceptually, the
interaction is a single event, yet I would commonly publish two distinct
events on both aggregates' event streams.

Surely you can share the event, but that would lead towards a "global"
event stream, where individual aggregates load off the total stream,
somewhat obliterating the concept of an aggregate.

Maybe the term "event" exists in two contexts in the ubiquitous language
of event-based/soured modling ;)

@yves: a worthwhile thing to ponder over a cqrsbeer... maybe I'd better
bring a bottle of rum...

Cheers
Phil


Philip Jander

unread,
Apr 25, 2013, 7:03:15 AM4/25/13
to ddd...@googlegroups.com
PS: although codebetter seems to be down, I remember the post I think.

I agree that distinct interfaces for separate states are better, call it
Liskov principle for states. Although, if you consider commands against
the now group of aggregates, you just push out the moment of surprise to
the client of the system, so Liskov comes back at you nonetheless ;)

Johanna Belanger

unread,
Apr 25, 2013, 3:04:38 PM4/25/13
to ddd...@googlegroups.com
Ok, right. Agreed with a capital A, enforcing invariants is not a stream concern.

I hope that post of mine didn't sound snide. I wrote it lightheartedly, but I probably should have thrown in a =D because re-reading it this morning I can see it sounding different than I meant it. Ah, written communication... =) 

Just to make sure we are on the same page, when you say "you still need to enforce invariants in the code that has loaded the stream", do you mean it needs to check that its invariants are intact after it has loaded the stream, or that it shouldn't write events that break invariants?

And thanks for linking that post, I forgot about it, and it was a good one. I remember the first time I read it, in that context it was an a-ha moment. More on that in a bit...

Johanna Belanger

unread,
Apr 25, 2013, 3:36:20 PM4/25/13
to ddd...@googlegroups.com
Hi Phillip,

Thanks for your explanations, sorry for going dark on you, I was thinking... and then sleeping... =)

Let me respond to a few of your points. We are thinking very much the same, and you've explained eloquently.

"However, I think this is only feasible in a functional style model where 
the current aggregate is a live projection of the stream (committed or 
uncommitted). "

Are people using event-sourcing differently than this? I thought this was a given.

"Using a common oo domain model, i believe it is far easier to implement 
the behaviour you seek by using a strategy pattern. 
What do you think about this approach?"

I definitely think the strategy pattern is useful here.

"Conceptually, events (as in business history) exist before aggregates. 
Business description should be way more stable than a particular 
implementation."

Yes, this is one reason I think event sourcing is so brilliant. It gives us freedom to change our models willy-nilly.

"Both views collide if you either consider changes to your aggregate 
structure"

Nice point to bring into the discussion.

I'll try to summarize my thoughts at present in another post.

Regards,
Johanna

Johanna Belanger

unread,
Apr 25, 2013, 5:21:41 PM4/25/13
to ddd...@googlegroups.com
How did you implement selecting which aggregate should handle a command at a given time, and what did you do if you received a command that the selected aggregate couldn't handle?

Thanks for your input =)
Johanna

@yreynhout

unread,
Apr 25, 2013, 6:13:10 PM4/25/13
to ddd...@googlegroups.com
The latter.

Johanna Belanger

unread,
Apr 25, 2013, 7:39:42 PM4/25/13
to ddd...@googlegroups.com
Here's my current take on the example:

Given the requirement “When the system is asked to DoSomethingActive on an Account, if the Account is Active then SomethingActive should be Done. If the Account is Closed, then it should record that SomethingActiveWasNotDoneBecauseTheAccountIsClosed.”

To implement this we need to:

-Select and hydrate the object that will fulfill the DoSomethingActive command.

-If the Account is Open, publish SomethingActiveDone.

-If the Account is Closed, publish SomethingActiveNotDoneBecauseAccountIsClosed.

Whoever selects the object would use the event stream to determine the current state of the aggregate (open or closed). If we use multiple aggregates, and the selector were to make the wrong choice, then we end up with a broken invariant. So the selector needs to be responsible for protecting the aggregate’s invariant. Does that make the selector the aggregate root for both aggregates? What about the DDD guideline that all access to the aggregate should go through the aggregate root? Does that mean all access to either aggregate would need to go through the selector? Could we argue that aggregate root is a role whose implementation could be shared by multiple classes?

Once the object is selected, it must be asked to do something active. This is where we meet Greg's state pattern article, which concludes:

 "Do some of the data/behavior only make sense in certain states? If so use three separate classes. Going along with this, if we find “throws” in our state implementors we should realize through LSP that we are doing something bad." 

The way that I wrote the requirement, I would say all of the states do have behavior for all of the commands and none of them throw. ClosedAccount publishes a different event than OpenAccount, but both publish an event.

If, instead, I write the requirement as "If the Account is Closed, then it should do nothing." then as Greg pointed out, state pattern would be an LSP violation.

Looking forward to all of your thoughts,

Johanna

@yreynhout

unread,
Apr 26, 2013, 8:53:11 AM4/26/13
to ddd...@googlegroups.com
- You might want to rephrase this because the ubiquitous language seems fractured.
- Here's something to ponder on: What if I were to make the state an object is in part of my invariant definition?
- As for putting invariant checking in an object other than the Aggregate's root, reread p. 144 of The Blue Book.

Philip Jander

unread,
Apr 26, 2013, 12:43:02 PM4/26/13
to ddd...@googlegroups.com
Hi Johanna,
>
> "However, I think this is only feasible in a functional style model where
> the current aggregate is a live projection of the stream (committed or
> uncommitted). "
>
> Are people using event-sourcing differently than this? I thought this
> was a given.

Not in the sense I meant. I assume (without hard data) that the majority
of event sourced domain models are implemented using class-oriented
techniques for the aggregate itself, and projecting the even stream into
state fields at the time of loading the aggregate (possibly cached by
using a snapshot). Then methods on the class instance are called by
command handlers and other objects. In response, the aggregate collects
uncommited events, at the same time updating state fields. These methods
commonly return void or unit. Beyond the concept of projecting events
into state, there is not much functional programming involved.

The approach I had in mind was that every method returns the updated
aggregate as an immutable object. A new call must be targeted at that
new object which in turn returns another, newer version of the
aggregate. Subsequent calls on a single immutable instance would return
different branches (as in git) of the aggregate with only a single
committable without conflicts.

Why? With the approach you mentioned initially, the aggregate you
obtained from a repository may have to be replaced with an instance of
another class in response to an operation. If you do not want to limit
yourself to a single operation per unit of work, you need some kind of
support for swapping the instances. The second, functional approach
seems quite appropriate for this. With the first approach, you would
have to determine whether the instance is still applicable after each
and every method call. This is why I stated that I consider the strategy
pattern easier to implement with a non-functional class-oriented model.

Cheers,
Phil

Johanna Belanger

unread,
Apr 26, 2013, 3:29:56 PM4/26/13
to ddd...@googlegroups.com
Inline...


On Friday, April 26, 2013 5:53:11 AM UTC-7, @yreynhout wrote:
- You might want to rephrase this because the ubiquitous language seems fractured.
 
"When a customer wants to charge a purchase to their account, if their account is active then we charge the purchase to it. If their account is closed, we reject the charge." "What does it mean to reject a charge?" "We notify the customer that their purchase has not been made because their account is closed." =)

- Here's something to ponder on: What if I were to make the state an object is in part of my invariant definition?
A closed account should reject charges.
An open account should accept charges. (A very liberal policy!)
Not sure I'm following where you are leading. Are you saying thinking of it this way shows that using multiple aggregates is more natural?

- As for putting invariant checking in an object other than the Aggregate's root, reread p. 144 of The Blue Book.
Well, look at that! Very interesting. Must be time to reread the Blue Book.

Johanna Belanger

unread,
Apr 26, 2013, 3:54:27 PM4/26/13
to ddd...@googlegroups.com
Aha! Yes. 

That is why this approach seems leaky to me. In the class-oriented style, it leaks implementation detail and possibly even behavior into the command handler. In a statically-typed language, it would even force you to use an interface across both aggregates, which is not always a good idea, as Greg's state pattern article pointed out. 

In a dynamic language and a functional style model, it isn't leaky or less flexible.

But maybe some view it as acceptable for a command handler to know about aggregate implementation, similar to the Factory in Blue Book p. 144? For the purpose of having clean and simple aggregates?

@yreynhout

unread,
Apr 27, 2013, 1:12:18 PM4/27/13
to ddd...@googlegroups.com
Maybe a case for System.Dynamic, maybe not. Regardless, the statemachine is always there. We're just talking convenience at this point.

@yreynhout

unread,
Apr 27, 2013, 1:27:30 PM4/27/13
to ddd...@googlegroups.com
I'm trying to say that the open/closed status is part of the invariant (in which case it would be satisfied across the entire aggregate AND time). Think of it as a function that must always return true, e.g. f = state == closed || (state == open && ...)

Still, I get the impression we're discussing two things, not one. One is about making sure invariants that are applicable across time and aggregate representations (e.g. using different classes) get adhered to. The other is about invariants not being applicable if you're in a different state of the lifecycle machine.

Philip Jander

unread,
Apr 28, 2013, 10:15:06 AM4/28/13
to ddd...@googlegroups.com

>
> But maybe some view it as acceptable for a command handler to know
> about aggregate implementation, similar to the Factory in Blue Book p.
> 144? For the purpose of having clean and simple aggregates?
>
Absolutely. All dependencies point inward towards the domain model. It
follows that the command handler calling into the domain should have
knowledge of the domain model. Even if its job is "only" to load an
aggregate from a repository and call a method unpacking the command
arguments, it already has quite intimate knowledge of the domain design.

Cheers
Phil

Johanna Belanger

unread,
Apr 29, 2013, 3:12:35 PM4/29/13
to ddd...@googlegroups.com
Ah, I see. I was taking for granted that the state was part of the invariant. I haven't been able to think of an example where that wasn't sufficient to make the invariant applicable across the lifecycle. It seems sort of tautological.

My concern was more about making sure they get adhered to.

Johanna Belanger

unread,
Apr 29, 2013, 3:18:40 PM4/29/13
to ddd...@googlegroups.com
Cool. It will be interesting trying out this broader viewpoint.
Reply all
Reply to author
Forward
0 new messages