We're evaluating CQRS/ES for a high-volume subsystem in our app in order to take advantage of distributed systems and ensure uptime. This is my team's first time implementing this architecture, and I'm struggling to finalize the aggregate design because the core entity changes during the transaction.
This subsystem consists of various endpoints (e.g. a web form, a physical kiosk device, and SMS) that authenticate users and allow them to log an activity. In this context, we have a concept of a Session, which tracks the things the user executes during the interaction. At some point in the interaction, the User is identified and is allowed to log an Activity.
Here are the challenges I'm having:
- The session doesn't "belong" to a user until and unless a valid user is authenticated during the process. We are interested in the sessions a user has, but for troubleshooting we're also interested in sessions that don't result in successful user authentication. It doesn't feel right to have a session be on its own for part of the transaction then get moved to a user after authentication.
- The user's activity is the important thing we're getting out of the transaction. That activity is "owned" by the user, but it's also referenced by the session. I'm unclear whether the activity should be an aggregate root, whether the user should be, or whether we should keep focus on the session in this context and use event listeners to build relationships in a read model.
- We are interested in these interactions and activities differently in other contexts. Sometimes we want to view them from the perspective of the endpoint. Other times we want to view them from the perspective of the user. Often for troubleshooting we have to infer the user based on metadata available to the session (Caller ID on an SMS, for example, lets us view if a user is using incorrect syntax in their messages, even if they're not authenticated).
I get that in CQRS/ES we can handle much of this with read models, but it's still unclear which entity/entities should serve as the aggregate root(s), and how commands should be constructed.
On one hand, it makes sense that the Session be the aggregate root, and commands should exist to log the interactions a user has with the endpoint, bind the user to the session once authenticated, and log an activity. Event listeners will then construct read models with the projections we are interested in (sessions by user, sessions by endpoint, activities by user, etc.)
On the other hand, it makes sense to have the Session be an aggregate root interested in the interactions between the user and the endpoint, and have the authenticated User be the aggregate root interested in the logging of an activity, with some sort of command in place on one or the other to connect the session and the user.
Hoping to get some outside perspective, perhaps from others who've gone down a similar road before.