8

I'm in the process of adopting DDD concepts for designing our next projects, and more specifically CQRS.

After reading a LOT of stuff I'm now trying to implement a simple Proof Of Concept.

The thing is I'm stuck right after I started :p

I'm trying to apply this approach to a simple user registration process, where steps are:

  • User fills the registration form & submit the request
  • The app creates the user
  • The app authenticates the user (auto log in)
  • The app sends a verification email to the user
  • The app redirect the user somewhere else with a confirmation message

From an implementation point of view, what I get so far is:

  • The controller action maps the request data to a RegisterCommand object
  • The controller action asks the Command Bus to handle the RegisterCommand
  • The command handler (UserService) "register" method creates a new User object (whether by a new command or a factory object)
  • The model raises a RegisterEvent
  • The command handler asks the repository to store the new user object

That's it, the controller action doesn't know about any of that.

So, my guess is, since everything in this context HAS TO be done synchronously (except for the email sending), I can use a direct/synchronous command bus, and in the controller action, right after the command bus invocation, I can query for a read only User (query database) and if it exists assume that everything went well, so I can give the user a confirmation message.

The automatic log in process being handled by an Event Handler.

Assuming that this is correct, what if something goes wrong, how to inform the user with the correct information ?

A common example is often used in articles we can find over the internet: A customer pays his order by using an expired credit card. The system accepts the request, informs the user that everything is OK, but the user receives an email a few minutes later telling him that his order could not be processed.

Well, this scenario is acceptable in many cases, but for some other it is just not possible. So where are the examples dealing with these use cases ? :p

Thank you !

Benjamin
  • 382
  • 4
  • 12
  • are these processors running in the same thread? or you have another service running elsewhere processing commands/events? – Sarmaad Apr 27 '13 at 14:46
  • We use a scripting language, so there is no thread. A queuing server could eventually exists, but not for this use case I think. – Benjamin Apr 27 '13 at 16:49

2 Answers2

4

I think this registration use case is closer to the paying for an order use case than you think.

Most of the CQRS thought leaders suggest validating on the read side before issuing a command, thus giving your command a higher probability of success.

If the validation fails on the read side, you know how to handle this - make the user pick another name before you even send off the registration command. If validation succeeds, send the command - now you're talking probably a few hundred microseconds AT MOST where another user could've come in and taken the same username between the time you validated the command and sent it off. Highly unlikely.

And in the very rare case when that does happen, you act in the same as way as the expired credit card example - the next time the user logs in, you present them with an explanation and a form to submit a new username - or send them an email saying "hey - someone else has that username, please click here to select a new one". Why does this work? Because you have a unique ID for that user.

Look at a user registration page like Twitter. As soon as you enter a username, it does a little Ajax call and says "nope, this is taken" or "this one is good!" That's pre-validation.

I hope this helps!

Dan
  • 326
  • 2
  • 9
  • Well, I'm ok with the scenario you described. The problem is that during a registration process, no one wants users waiting for the server to have handled the request. You can't just fake it, like you would when user add a comment for instance, by immediatly showing his message with javascript. More commonly, users can immediatly use the service. We can also imagine a 3 steps registration process: 1. User enter username/email/password 2. User is automatically logged in and invited to complete his profile 3. User is redirected to his home page (whether it would be a feed or anything else) – Benjamin May 04 '13 at 12:39
  • I think it would be OK. As long as you're not using the username as the ID (instead use a surrogate ID - a GUID or something), the user can be logged in and taken to his homepage because these will be using the surrogate ID to make decisions, not the username. – Dan May 04 '13 at 23:20
  • This makes sense in theory, but in a real context this won't be sufficient. If the user is redirected after his registration, he expects being able to start using the service. You cannot reasonably say that he must wait to do anything, this would be an awful user experience. On the homepage you probably want to show more information than just the user ID, and you certainly don't want the user to perform any operation without being sure he is fully authenticated in the system, and UI data are not enough to ensure that. – Benjamin May 14 '13 at 18:44
  • I start to think that maybe this is typically an use case where we shouldn't use CQRS at all. But then, it makes the whole thing more complicated, because we need to support several ways of handling users requests. One that fit with CQRS and DDD concepts, and others that don't. Fowler said "I prefer to follow this principle when I can, but I'm prepared to break it to get my pop.", but how can you do that without introducing complexity ? – Benjamin May 14 '13 at 18:48
  • You seem to be largely disconnected from a ton of services out there that are willing to put up with the eventual consistency (e.g. user must confirm registration using email). Nothing is stopping you from doing synchronous calls, but you know what you're getting into from a scale POV. CQRS is not asynchrony. But you get that. CQRS is not eventing. But you get that. What you don't seem to get is that this is a very business centric decision, not a technical one. As I said before, your user won't know a damn thing of you restoring a backup that does not contain his registration. – Yves Reynhout May 15 '13 at 21:22
  • Yeah, you're right, I need to change my way of thinking about everything, I try hard but I'm not there yet ;-) Anyway thank you for your help – Benjamin May 23 '13 at 16:55
1

The problem with contrived examples is that you can change your mind about how the "domain" functions, so there's little use in discussing this example in particular. The basic premise you seem to forego is that we must assume that things are just going to work. Everything else is about risk and mitigating it. Taking this example, if I ask you, what if I lost 1 user registration in 100000? What if I lost 1 out of 10? Why would that happen? Do I have bigger problems at that point in time? Would future users be likely to register again when the system comes back online and works as expected? When would that be? What if we monitored our quality of service and prevent users from registering because we can't assure the quality they've come to associate with our brand? What if the server exploded, or the datacenter got nuked? Do we want to protect against that? You see, there is no right answer. Just various shades of grey. So how do we mitigate the risk? We could make things synchronous but that is only a guarantee at that limited point in time. What if I had to restore a backup that's 2 hours old (e.g. because the disk corrupted)? That's 2 hours of registered users lost (maybe). These things happen ... I just wanted to point out the relativity of what I consider a false sense of security. Mitigate it, invest in what you can't afford to lose, make sure you have a good audit trail. Probably not the answer you were looking for ...

Yves Reynhout
  • 2,982
  • 17
  • 23
  • I'm ok with all these aspects of "eventual consistency" which in the end are not really an issue since as you mentioned we have to accept that "things are just going to work" 99.99% of the time. But in some cases, from a user experience point of view, I have to let the user use the service right after he submitted his registration request, but then what ? I have to let him send a bunch of commands, like creating contents, by relying only on what the client assumes about user data ? I mean, user authorizations is a sensible subject for instance. – Benjamin May 15 '13 at 10:22
  • No, you finish the registration process. Then you continue. Easy. Want to provide better UX? Why not let the user continue as a temporary user (until user confirms registration) and implicitly associate that with his registered user account? Or just make the read model update for this particular use case synchronous? Lots of options, IYAM. – Yves Reynhout May 15 '13 at 21:28