3

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.

3
  • The system you are describing appears to have very little behavior from a DDD perspective. It seems like you are just recording page loads/ button clicks along with data about "who" triggered them. You have one aggregate Session to which you add a series of Activity. If you do not want "authentication" to be its own Activity, you may need another method on your Session that adds user information (which is then appended to each subsequent Activity). A User does nothing in this system (you likely only need userId). Jan 25 '19 at 20:23
  • There's a little more to it, but you're right that it's generally a process interested in the progress of a transaction and as such doesn't have much behavior on its own outside of recording the interactions. Authentication of the user is handled by a separate service called by the process that initiates the Session. The goal here really is to leverage a command/service bus/event architecture so we can have improved uptime through redundancy on the endpoints and don't have a SQL server as a performance bottleneck (issues we have today). Maybe I'm overthinking it from a DDD perspective. Jan 25 '19 at 20:43
  • I would not expect an append-only log of interactions to bottleneck performance. Are you reading from this log as well? Is there a compelling reason to do so? Jan 25 '19 at 21:50
1

From what I understand you don't have a rich domain model (on the write side); I can't find any real business rules from your question, no invariants that need to be protected. All your requirements seems related only to authentication and authorization.

That being said you can design your entities as separate Aggregates; this will give you the performance that you need. Where you don't need any state you can have stateless services that process commands by emitting events. For each command the service will emit the corresponding event. Technically you could emit events from your Application layer but it's more natural to me to emit them from a Domain service, even if its stateless; maybe it will be promoted to a real Aggregate when the domain gets richer.

Regarding your non-functional requirements like performance and resilience: CQRS is the perfect solution for this. You seem to have a lot of behavior in the read side and CQRS can give you any ReadModel you may think of on any technology you wish.

That activity is "owned" by the user, but it's also referenced by the session

I suggest that your Activities reference the Session and not the other way. You don't need to keep a list of Activities on the Session in the write side, am I right?

... but for troubleshooting we're also interested in sessions that don't result in successful user authentication

Maybe you can get this from the Authentication Bounded context. A Session should start after the user authenticates (before that you don't know that the user is who he claims to be). It feels more natural.

Although you don't have a rich domain model (on the write side) you can still follow the DDD approach at the strategic level, for example, to correctly identify the Bounded contexts, one of the most important things in DDD.

Note: I used "it feels" to compensate for the lack of domain knowledge.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.