Domain events without aggregate

746 views
Skip to first unread message

Fabian Schmied

unread,
Oct 15, 2013, 2:56:06 AM10/15/13
to ddd...@googlegroups.com
Hi list,

we're currently working on a CQRS-based project, and I have a question
about event sourcing, or actually event storing, in conjunction with
domain events that don't logically belong to an aggregate,
specifically about what stream ID to assign to those events.

This is the scenario. We have a bounded context where we keep track of
Person data a user registered in our system. We have commands such as
RegisterPersonBirth and events such as PersonBirthRegistered. These
events are stored in an event store (NEventStore) and published via an
event bus to event handlers that build up read models.

One such read model stores and returns a PersonBirthData DTO from
which a PDF document is generated when the user requests it.

Now, as an additional business requirement, our system needs to track
all requests for generated documents. From the tracking information,
we again need to build read models (and maybe kick off business
processes later), so I want to model this tracking stuff as domain
events (e.g., a DocumentRequested event). Now, this event does not
logically belong to the Person aggregate, and indeed there is no
domain logic at all related to tracking, so it doesn't make sense to
create a new Tracking aggregate or even a Tracking command either. (In
fact, I even consider Document Tracking a different bounded context.)

Therefore, we are going to extend our infrastructure to enable it to
directly put events into the event store and on the event bus without
it going through the usual command/handler/aggregate pipeline.

So far so good, now the actual question; since the whole underlying
infrastructure (NEventStore, event bus, etc.) assume the notion of an
event stream ID/aggregate ID, what stream ID would you assign to the
tracking event?

Options I'm considering:

1 - A single stream ID for all such directly fired events without aggregate.
2 - A dedicated stream ID for all tracking events. Other BCs can use
their own stream ID.
3 - What the heck, just assign it the ID of the corrresponding Person aggregate.

Are there other good options? What are the pros and cons in your point of view?

Thanks, regards,
Fabian

Philip Jander

unread,
Oct 15, 2013, 3:53:02 AM10/15/13
to ddd...@googlegroups.com
Hi Fabian,
> (In
> fact, I even consider Document Tracking a different bounded context.)
This sounds highly likely, unless you are creating a birth certificate
administrative solution, where one could argue that point ;)

> Therefore, we are going to extend our infrastructure to enable it to
> directly put events into the event store and on the event bus without
> it going through the usual command/handler/aggregate pipeline.
Why is the request for a document not a command?
> So far so good, now the actual question; since the whole underlying
> infrastructure (NEventStore, event bus, etc.) assume the notion of an
> event stream ID/aggregate ID, what stream ID would you assign to the
> tracking event?
Beware of frameworks imposing non business oriented structure to your
solution ;)
>
> Options I'm considering:
>
> 1 - A single stream ID for all such directly fired events without aggregate.
I would recommend against that. To me, an event stream / event source /
aggregate should be bounded by design. I.e. it principially should not
be able to grow beyond a certain (possibly unknown) number of events.
Besides, if you were to use a single ID, that should at least be limited
to that specific kind of request. But you should be aware that you are
implicitly creating a "all requests there ever were" aggregate. Does
this make sense from a business perspective?
> 2 - A dedicated stream ID for all tracking events. Other BCs can use
> their own stream ID.
2a: all tracking events: see 1.
2b: only request tracking events: see 1.

> 3 - What the heck, just assign it the ID of the corrresponding Person aggregate.
Does the request event modify the bahavior of your Person aggregate? If
not, it is foreign to it and probably shouldn't go there.

>
> Are there other good options? What are the pros and cons in your point of view?
4. You could use a single stream id per request. That would make each
request its own aggregate. If (and only if) you intend to extend the
behavior beyond mere registration of the request, this might make some
sense.
5. Do you have a requestee modelled? If so, maybe the requests are best
modelled as part of that?
6. Why not use a dedicated tracking store instead of a general event
store? If your "maybe some business processes" is likely to remain in
the maybe state for some time, this might well be the easiest option. If
you ever need to go to 4., you can still generate the events at that time.
7. As an in-between 2b and 4, you could, if you can identify a
businesswise significant time period, use a single ID for "all requests
from (e.g.) fiscal year xxx". This is ok technically but still dubious
from a business perspective.
8. As an alternative to 3 (if the correlation between requests and
persons is the most significant attribute of the request): Use a single
ID as a stream ID for all requests corresponding to a single person. Do
not recycle the Person's ID directly, but use a 1-on-1 mapping of Person
IDs and Request IDs.

Summary: it depends ;) Without further information, I would probably go
for 6, 5, 8, 7, picking the first that is well applicable, in that order.

Cheers
Phil

>
> Thanks, regards,
> Fabian
>

Fabian Schmied

unread,
Oct 15, 2013, 4:27:05 AM10/15/13
to ddd...@googlegroups.com
Phil,

Wow, thanks for that extensive discussion, you've got some nice ideas.
Answers inline.

>> Therefore, we are going to extend our infrastructure to enable it to
>> directly put events into the event store and on the event bus without
>> it going through the usual command/handler/aggregate pipeline.
>
> Why is the request for a document not a command?

Mainly because generating a document is not a domain action, but
rather a transformation of a read model. There is no domain logic at
all in there, no state change; there's just the requirement to track
that the user clicked on the "generate document" button. I'm recording
something that _already happened_.
I could still model it as a command to an "empty" domain, but it just
doesn't match my feeling of what a command is; it is already an event
when it comes to existence.

>> So far so good, now the actual question; since the whole underlying
>> infrastructure (NEventStore, event bus, etc.) assume the notion of an
>> event stream ID/aggregate ID, what stream ID would you assign to the
>> tracking event?
>
> Beware of frameworks imposing non business oriented structure to your
> solution ;)

Yeah. But there is limited time, and in fact the idea that the stream
of tracking events needs some kind of identity is technically okey.
(If it hadn't, that would be more or less identical to my option #1.)

>> 1 - A single stream ID for all such directly fired events without
>> aggregate.
>
> I would recommend against that. To me, an event stream / event source /
> aggregate should be bounded by design. I.e. it principially should not be
> able to grow beyond a certain (possibly unknown) number of events.
> Besides, if you were to use a single ID, that should at least be limited to
> that specific kind of request. But you should be aware that you are
> implicitly creating a "all requests there ever were" aggregate. Does this
> make sense from a business perspective?

From a business perspective, that aggregate doesn't make sense. It
doesn't have a domain counterpart that the user would understand
(other than "tracking records" or "document generation log"). I don't
believe such an aggregate would ever be hydrated. The only reason I'm
storing the events is so that they can be replayed when new read
models are added.

Now, since that is my only reason for storing them rather than just
putting them on the event bus, I should probably view it from that
perspective as well: how would I benefit in replaying from a single
stream ID (#1) vs. a tracking stream ID (#2a) vs. a document request
tracking stream ID (#2b)? There'll probably never be a read model or
business process that depends on _all_ such "global" events; having to
replay them all could however become a performance problem. So no #1.

>> 2 - A dedicated stream ID for all tracking events. Other BCs can use
>> their own stream ID.
>
> 2a: all tracking events: see 1.
> 2b: only request tracking events: see 1.

In the light of what I wrote above, I should probably go for #2b
rather than #2a and #1, since it is the most useful. All read models
generated would usually be interested only in a single category of
events (though there would be multiple event types in that category).
So I'd use the stream ID to categorize events among those lines.

>> 3 - What the heck, just assign it the ID of the corrresponding Person
>> aggregate.
>
> Does the request event modify the bahavior of your Person aggregate? If not,
> it is foreign to it and probably shouldn't go there.

Yep.

>> Are there other good options? What are the pros and cons in your point of
>> view?
>
> 4. You could use a single stream id per request. That would make each
> request its own aggregate. If (and only if) you intend to extend the
> behavior beyond mere registration of the request, this might make some
> sense.

Yes, I could, but I wouldn't really benefit from a request tracking
"item" having its own identity; neither from a business perspective,
nor from a technical one.

> 5. Do you have a requestee modelled? If so, maybe the requests are best
> modelled as part of that?

No, requestees are not modeled. (For now :) )

> 6. Why not use a dedicated tracking store instead of a general event store?
> If your "maybe some business processes" is likely to remain in the maybe
> state for some time, this might well be the easiest option. If you ever need
> to go to 4., you can still generate the events at that time.

Well, that's not much different from #2a, or is it? Since the the
tracking store is more or less a log of request tracking events,
there's not much difference from using a single event stream instead,
I think.
I'd probably go this route if I required specific features from the
tracking store that an event store couldn't give me.

> 7. As an in-between 2b and 4, you could, if you can identify a businesswise
> significant time period, use a single ID for "all requests from (e.g.)
> fiscal year xxx". This is ok technically but still dubious from a business
> perspective.

In fact, this might even be good from a business perspective, since
there might be requirements such as "after three years, the tracking
logs must be purged". I'll need to talk to the domain expert. Thanks
for giving me that idea :)

> 8. As an alternative to 3 (if the correlation between requests and persons
> is the most significant attribute of the request): Use a single ID as a
> stream ID for all requests corresponding to a single person. Do not recycle
> the Person's ID directly, but use a 1-on-1 mapping of Person IDs and Request
> IDs.

I think, from a business perspective, the correlation between requests
and persons is important, but not the most significant attribute. Time
is probably more significant. Which might go more into a direction of
#7.

> Summary: it depends ;) Without further information, I would probably go for
> 6, 5, 8, 7, picking the first that is well applicable, in that order.

Sure, it always does. Thanks for the discussion!

Fabian

Chris Sampson

unread,
Oct 15, 2013, 8:25:48 AM10/15/13
to ddd...@googlegroups.com
May be this is a crosscutting type concern. You could just have an "Audit service" that gets called in the controller actions (if mvc) and it just writes a "history" table in your read model. Pretty simple.



Fabian

--
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.

Chris Sampson

unread,
Oct 15, 2013, 8:27:44 AM10/15/13
to ddd...@googlegroups.com
..Or is it a real business event and thus why it needs to be in your event store?

Greg Young

unread,
Oct 15, 2013, 2:23:55 PM10/15/13
to ddd...@googlegroups.com
Why not do a tracking stream per stream?

Eg doc444-tracking

You can easily combine these into a single stream.

Cheers,

Greg
--
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.

Fabian Schmied

unread,
Oct 15, 2013, 3:19:03 PM10/15/13
to ddd...@googlegroups.com
> May be this is a crosscutting type concern. You could just have an "Audit
> service" that gets called in the controller actions (if mvc) and it just
> writes a "history" table in your read model. Pretty simple.

Yep, a good and simple idea. However, I'd like to publish events via
the event bus (rather than just writing a history table) because those
tracking events are relevant business events that might be subscribed
by different read models, process managers, and used for integration
with external systems.

Fabian Schmied

unread,
Oct 15, 2013, 3:21:31 PM10/15/13
to ddd...@googlegroups.com
> Why not do a tracking stream per stream?
>
> Eg doc444-tracking
>
> You can easily combine these into a single stream.

I'm not sure I understand, could you elaborate on this? (Note that I'm
on NEventStore, not GetEventStore.)
Also, I don't currently have an event stream for documents, as those
documents are just transformations of read models (that are themselves
a projection of events from the Person domain).

Thanks,
Fabian

Greg Young

unread,
Oct 16, 2013, 10:21:14 AM10/16/13
to ddd...@googlegroups.com
Neventstore will support event store within the next weeks btw.

What I meant was your aggregate is stored as a stream now. Just make a second stream next to it to but further events in.

Cheers,

Greg

sebastian

unread,
Oct 16, 2013, 10:48:29 AM10/16/13
to ddd...@googlegroups.com
Can't wait to see how their multi-event commits are handled
Sebastian Good

Greg Young

unread,
Oct 16, 2013, 12:38:24 PM10/16/13
to ddd...@googlegroups.com
Quite simple actually. Just put commitid on the metadata :)
Sebastian Good

--
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.
Reply all
Reply to author
Forward
0 new messages