Event sourcing basic question

Hi,

I am quite new to event sourcing and I have a general question regarding a use case.

I know this is not specific to Event Store, but I figured I’d found more expertise here :slight_smile:

I currently have a single event stream per aggregate root.

I have two different aggregate roots, say Room and RoomType.

The behavior of a room depends on what RoomType it is. In order to separate both aggregates, The type of a room is just represented as an ID in the Room aggregate.

The RoomTypes can be managed separately and are a different aggregate.

Now consider the following use case:

When a user invalidates a Room type, all Rooms that have that Roomtype should switch to a fallback Room.

I have thought of several approaches, but all of them seem to be problematic:

  1. Have an event listener listen for a RoomInvalidated-event, and send a SwitchToFallbackRoomType on all Room-aggregates that have that Room-type.
    How would I do this though? There’s no way to know which aggregates have that Roomtype unless I access my readmodel, which seems a bit strange.
    Even if I were to load all aggregates, there’s no way to load only the aggregates of that type as I can’t load a subset of all streams
  2. When reapplying the RoomTypeChanged-events to the Room aggregate, instead of just applying it, do a check to see whether that RoomType still exists, but then again, how would I know which RoomTypes exist (I’d be in the same situation as 1, but inverted)

How would you solve this?

Several things come to mind.

Business motivation

Why does a user (I presume more of an administrator of the system) invalidate a particular type of room? Do the rooms all of sudden stop being of that type? If so, why is that? It would help if I knew what domain this was in (e.g. hotel room, hospital room, meeting room etc …). You haven’t given me much to go on.

Modeling

  • FallbackTo: instead of affecting all rooms, you could merely register on the room type that it has been superseded by another room type. If this happens, you could update your room related projections to contain the fallback room type. if that makes sense for visual or api response purposes. Now, rooms don’t know the fallback room type at this point and that might be good or bad. Any future behavior on the rooms could query the room for its room type and fetch (using the room type repository) the “current” room type. How? You’d just load up the room’s room type, and if it happens to be superseded by another room type, you’d just load up that one (all behind the repository facade), repeating this right up until the point the room type is not superseded. If superseding happens a lot this might be a bad or good idea to begin with, but still, it’s a viable option. The result from the repository can now be used in the use case (the behavior) you were about to invoke that needed to know the room type.

Note: using this approach you’re foregoing the need to notify each and every room about a change of room type, resulting in a lot less messaging, but with the downside of the room not being truly isolated since it needs to rely on another service to find out its current room type. Imagine implementing this using actors, I would assume that to be a bad idea. If we’d take a more classical OO approach, I would be inclined to say this will work out fine.

  • Messaging (take 1): you’ve got some logic that is actively listening for RoomInvalidated, and if that happens, it asks a service of sorts to enumerate the room ids that have that room type, sending out a message to each room, telling that room what its fallback room type is. The service is underpinned by a projection built up of a room’s room type (I’m assuming that the room type is known when a room is added to the system - let’s call it RoomAdded for argument sake). So, you’d maintain a separate projection to be able to answer the question of which rooms currently have a particular room type. The projection could be in memory, it could be another actor backed by some storage, or it could be a simple sql or nosql storage concept.

  • Messaging (take 2): you’ve got some logic that is actively listening for RoomInvalidated, and if that happens, it starts reading either the $all stream (probably a bad idea in this case) or a custom “room” stream (a projected stream if you will) and each time it finds a RoomAdded with the given room type in it, it sends out a message to the room, telling that room what its fallback room type is.

Note: for simplicity reason I’ve assumed that room type is only affected by RoomAdded at this point (later on you’ll have a RoomsRoomTypeChanged as well (perhaps)).

Which solution to pick would be based on the technology involved and the answers I would get from a domain expert (I would be all over him in this case).

Food for your brain …

Regards,

Yves.

Invalidate room type appears to be an optimization on “change a room’s room type to new room type”. A process manager can send the individual commands as @yves mentioned.

I’m wondering if “room type” changes behavior, it might be better to “close the existing room and replace it with another room”. In other words, there is no Room which is doing “switch (roomType)” to alter behavior, there’s a RoomTypeARoom and a RoomTypeBRoom that behave properly.

For reporting, you may need to tie streams together so you can see “what has happened in the space commonly referred to as Room 123”. [This is difficult to write as real world room types don’t suddenly switch, afaik].

Hi Yves,

Thanks for your reply.

A few follow-up questions:

For the first pattern: How would you build such a projection (using event store). I have been reading the docs, but I’m not sure I get how to do that. Note that I’m using one stream per aggregate, so at a given moment I could have the following streams:

room-

room-

roomtype-

From these streams, how could I create a projection to know which room aggregates have a certain roomtype? If I’m not mistaken, I’d have to check every stream to see if it’s a stream for a room, then check the last RoomTypeChanged event to see what RoomType it is. I don’t think that would be a good solution.

The example is completely ficticuous (hence, I’m the domain expert here).

To explain it in more CRUD-like terms:

A user has a default RoomType (which cannot be deleted). A Room has a RoomType (always). When the user deletes a RoomType, I want all the rooms to fall back to the default RoomType.
It might seem strange to delete a RoomType, but for arguments sake, let’s say it’s a valid operation (I could give another example where it’s clearer, but I think that would complicate it)

Hey Kenneth you have definitely found a great source of DDD and ES information. I have been on the board trying to absorb the kind of knowledge that Yves and Kijana offer as well.

The thing I often see with new comers is a fake/made up domain (it was my first attempt on the board as well). That is extremely common as an introductory question.

When you say “The example is completely ficticuous (hence, I’m the domain expert here).”. In truth most of the time that a question stems from a fake and trivial domain the creator is not a domain expert. This is because the trivial/fake domain is so trivial in fact that the creator hasn’t thought of the “real” questions. The real problems that need solving. The questions a real domain expert with a real domain would deal with. So what ends up happening is a thread more about trying to figure out the domain itself rather than the concepts of ES/DDD.

That discussion is valuable in itself. I highly recommend reading http://ziobrando.blogspot.se/ and searching “event storming”. Alberto contributes to this board and you can search for his discussions. I feel they have been extremely helpful in my understanding of modeling.

I have found the absolute best questions to ask/answer first are how would accomplish the task if you didn’t have a computer. That is how a domain expert thinks about it. Not as events and commands.

Cheers and welcome

Phil

Well, I wouldn’t use event store as the projection engine of the state I’d want to query. I would use the projection part of the eventstore to create a new stream (let’s dub it “rooms-by-type”) that contains all “RoomAdded” events (hoisted from all your room- streams). Why? Because that would make the projection subscription very easy to set up. Using a catch-up subscription and something like Projac (you can’t hold promoting my own goods against me) I can now write something along the lines of:

public static readonly SqlProjection Instance = new SqlProjectionBuilder().
When(message => TSql.NonQueryStatementFormat(“INSERT INTO [hotelreactions].[RoomsByType] ([RoomId], [RoomTypeId]) VALUES ({0}, {1})”, TSql.UniqueIdentifier(message.RoomId), TSql.UniqueIdentifier(message.RoomType))).
Build();

where each RoomAdded event gets pushed through it, producing the sql to execute, execute said sql, and have the required state in the relational store. This pattern can be applied to any projection store of your liking, doesn’t have to be sql.

Now that the state is in the store, it becomes trivial to provide a query on top (well, that’s the whole idea, anyway). Whenever a RoomTypeInvalidated event comes in, we have a reaction ready, that queries the rooms that have the roomtype that is being invalidated, and ask each one of those rooms to fallback to the room type of that user (sounds gibberish to me, but I’ll indulge you).

var roomsByTypeQuery = new Func<Guid, Guid[]>(roomTypeId =>
executor.
ExecuteReader(TSql.QueryStatement(“SELECT [RoomId] FROM [hotelreactions].[RoomsByType] WHERE [RoomTypeId] = @RoomId”, new { RoomId = TSql.UniqueIdentifier(roomTypeId) })).
ReadAsArrayOf());
public static readonly Reaction Instance = new ReactionBuilder().
ReactTo(message => Result.SendMany(roomsByTypeQuery(message.RoomTypeId).Select(roomId => new FallBackToUserRoomType { RoomId = roomId, UserId = message.InvalidatorId }))).
Build();

There’s some brittleness to this example (lots of moving pieces), such as the concurrency of invalidating the room type, adding a new room type just before that, the room-by-type projection and the sql based projection playing catch-up and the reaction on the other end using eventual consistent sql state, but no need to worry about all that at this point :slight_smile:

Regards,

Yves.

I think I made the mistake of dumbing down the domain problem (and changing to a different domain in the process) to get a technical answer.

I will try to rephrase my question and use the real domain and the technical issues I have with it:

Domain

The application is a tracker of your bank statements. A user can add accounts (event: AccountCreated) and import or manually add his statements (event: StatementAdded). Then, based on a set of rules, the statements will be put into categories on which you can then run reports. These rules can run either only on import or could be run retroactively on already imported statements.

So, for example, there is a rule which states "if the description of the statement contains “supermarket” then put it in the category “food” ". These rules are dynamic and can be created by the user.

Now here’s the issue: when a user wants to remove a category, I need to make sure that the statements that are currently in that category either fall back to a default category (that can never be removed), fall back to a specific category or just have no category assigned (based on user input when the category is removed).

Technical issues

I have tried some of the things Yves suggested in his last message, namely creating a new stream that contains all the StatementAdded events. However, I’m not even able to create a simple projection that has all events from the account-category. (To clarify: Account is an aggregate which has a stream of AccountCreated and StatementAdded events. Every aggregate has its own stream Account- ). I have tried creating this simple projection:

fromCategory(‘account’);

but when I look at the resulting there are no events, even though there’s an account-stream which has a few events in it.

I have also followed the following tutorial: https://geteventstore.com/blog/20130213/projections-2-a-simple-sep-projection/ , just trying to create an event stream projection of the stats, but it doesn’t seem to work either. I have simply copy-pasted the code from the tutorial, when I click “Start”, it tells me it started, but the UI still tells me “StatsProjection - Stopped (Enabled)” (I made sure to select “continuous” in the creation screen).

I’m a bit at loss on how these projections work. I understand the idea behind it (at least I think I do), but I just can’t find a way to make them work. The console logs are not displaying any errors either.

@Yves: Apart from the technical issue I’m having, I was also wondering about the reason why you would create a new projection which contains all RoomAdded events, then subscribing to it and then writing the SQL. Wouldn’t it be easier to create an event listener to the original event in the first place without going through the projection? I’m not sure I see the benefit of the round trip (at least not when executing everything in a single process).

I know this is a bit of an extensive question and it’s mixing domain modeling questions with pure technical issues. For now, I’d be happy with just a solution of the technical problems I’m having so I can at least play around a bit with the domain modeling. (Although any advise on that is welcome as well :-))

Thanks!

To answer the technical question quickly, that projection is actually already built in - it’s named ByCategory and should be created when you enable projections on your instance. It doesn’t run by default however - you need
to enable it.

Code speaks … https://github.com/yreynhout/ConsoleApplication22 - just a dump, but should get your brain juice flowing (don’t copy paste pretty please)

Things to note …

  • as James mentioned, the projection you are looking for is built in ($by_event_type (becomes $et_) is what I used), you probably didn’t turn on the proper projections (both on the command line and in the ui).

  • you can side step some of the projection hoops, but all depends on how you’ve designed your event contracts. Depending on that, you’re going to require intermediate state, and GES projections are not meant to be used for lots of intermediate state IIRC. So that’s why I suggested projecting that to another store (again doesn’t have to be sql).

  • when I say new projection, I really meant a “projected” or “derived” stream off of other streams, using symbolic links to the events in those.