2
votes

I'm trying to wrap my head around CQRS. I'm drawing from the code example provided here. Please be gentle I'm very new to this pattern.

I'm looking at a logon scenario. I like this scenario because it's not really demonstrated in any examples i've read. In this case I do not know what the aggregate id of the user is or even if there is one as all I start with is a username and password.

In the fohjin example events are always fired from the domain (if needed) and the command handler calls some method on the domain. However if a user logon is invalid I have no domain to call anything on. Also most, if not all of the base Command/Event classes defined in the fohjin project pass around an aggregate id.

In the case of the event LogonFailure I may want to update a LogonAudit report.

So my question is: how to handle commands that do not resolve to a particular aggregate? How would that flow?

    public void Execute(UserLogonCommand command)
    {
        var user = null;//user looked up by username somehow, should i query the report database to resolve the username to an id?

        if (user == null || user.Password != command.Password)
            ;//What to do here? I want to raise an event somehow that doesn't target a specific user
        else
            user.LogonSuccessful();
    }
1
I wrote a MembershipProvider on CQRS+ES, it's one of my attempts at it if you want to take a look - Iridio
Thanks this helped. It seems to ignore logging of logon attempts that don't match an existing username though. Which is at the core of my question - Sam
The failed attempt are stored in the Faliedpassword attempt and FailedPasswordQuestionAndAnswerAttempt and relative datetime - Iridio

1 Answers

3
votes

You should take into account that it most cases CQRS and DDD is suitable just for some parts of the system. It is very uncommon to model entire system with CQRS concepts - it fits best to the parts with complex business domain and I wouldn't call logging user in a particularly complex business scenario. In fact, in most cases it's not business-related at all. The actual business domain starts when user is already identified.

Another thing to remember is that due to eventual consistency it is extremely beneficial to check as much as we can using only query-side, without event creating any commands/events.

Assuming however, that the information about successful / failed user log-ins is meaningful I'd model your scenario with following steps

  1. User provides name and password
  2. Name/password is validated against some kind of query database
  3. When provided credentials are valid RegisterValidUserCommand(userId) is executed which results in proper event
  4. If provided credentials are not valid RegisterInvalidCredentialsCommand(providedUserName) is executed which results in proper event

The point is that checking user credentials is not necessarily part of business domain.

That said, there is another related concept, in which not every command or event needs to be business - related, thus it is possible to handle events that don't need aggregates to be loaded.

For example you want to change data that is informational-only and in no way affects business concepts of your system, like information about person's sex (once again, assuming that it has no business meaning).

In that case when you handle SetPersonSexCommand there's actually no need to load aggregate as that information doesn't even have to be located on entities, instead you create PersonSexSetEvent, register it, and publish so the query side could project it to the screen/raport.