Representing errors as messages rather than exceptions.

171 views
Skip to first unread message

Michael Ainsworth

unread,
May 22, 2016, 1:34:12 AM5/22/16
to DDD/CQRS
In a CQRS/ES system, commands are inputs ("do this") and events are outputs ("this done"). Persisting events is critical in order to "reconstruct the current state" of aggregates. Persisting commands is optional, but can be quite helpful (e.g., for debugging and tracing commands).

However, I've had an idea (although most likely not an original one) that exceptions are actually outputs as well, albeit ones that do not mutate state, and that persisting these exceptions provides a number of benefits similar to persisting commands. To use a simple example, the SignIn command would either produce a UserSignedIn event or a UserNotSignedIn exception (with a "reason code" representing the particular reason, such as an invalid password). An event handler could subscribe to the UserNotSignedIn exception and if there were multiple failed sign ins, it could issue a command to disable the user's account for the next hour.

Using a message bus instead of (or that transforms) exceptions also:

- Allows a subscription to errors (e.g., an administrator can see all the "failures" happening in near real time).
- Allows better end-user support. "I see you tried to order product X. The error reported to you was that you need to agree to the terms and conditions. Please visit http://example.com/terms-and-conditions and click the 'I agree' button."
- Allows sagas/process managers to respond to typical events ("OrderPlaced") as well as errors ("OrderNotConfirmed").
- Is one mechanism of achieving asynchronous commands. E.g., the client issues a command with a unique command UUID, then subscribes to events and errors related to that command.

Most articles I've read recommend throwing an exception to indicate failure. Do people have experience and recommendations with using exceptions as messages, and do you have any recommended reading in the area?

Chris Sampson

unread,
May 22, 2016, 9:44:26 AM5/22/16
to ddd...@googlegroups.com
Are they really exceptions,? they sound just like events to me. Exceptions should be truly exceptional, as in you don’t expect them.

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

emragins

unread,
May 22, 2016, 10:29:21 AM5/22/16
to DDD/CQRS
Jumping on to that, it's worth remembering that commands are intended to pass, and the UI should accommodate that -- such as not enabling the confirm button until the terms are checked. Or you can have sagas to look for stale orders that were never placed to send follow up emails.

It sounds like you're interested in more of a user-tracking log, which certainly has merits. I'm not sure treating exceptions as events is the best way to accomplish that, though I admit that from a newbie standpoint it's an intriguing idea.

Danil Suits

unread,
May 22, 2016, 4:47:57 PM5/22/16
to DDD/CQRS
Do people have experience and recommendations with using exceptions as messages, and do you have any recommended reading in the area?

I haven't seen any references on this topic.

That won't stop me from having my own recommendations, but the usual disclaimer applies.


However, I've had an idea (although most likely not an original one) that exceptions are actually outputs as well, albeit ones that do not mutate state, and that persisting these exceptions provides a number of benefits similar to persisting commands.

I have two problems with this, as described thus far.

First, if you abandon exceptions, you give up the ability of the caller to distinguish between success and failure.  This may or may not be an issue (if the command was sent on an asynchronous channel, it doesn't matter, after all -- the caller is waiting for an event on the other side).  It might also open up the question of "why throw rather than no-op?"

Second, the events in our book of record are histories of the entities we are tracking.  So I don't understand where an exception event would go?  Into the history of the aggregate we were going to change?  Into the history of a different aggregate that we haven't loaded yet?  Were we intending to modify that same aggregate if the command succeeded?  In the same transaction?  What are you going to do if the attempt to persist the exception fails?

Put another way, are the exceptions a domain concern, or an application concern?

If they really are a domain concern, then why aren't we using a protocol in the domain to track each instance?  The process managers in our system are a part of our book of record, so put in the effort and get it right.  Then you can set up all the reports your business needs on those events.

On the other hand, if the protocol in question isn't of interest to our business users; handle the exceptions in the application layer.  Drop in some off the shelf logging/monitoring framework and go back to solving valuable business problems.

 

Michael Ainsworth

unread,
May 23, 2016, 2:19:10 AM5/23/16
to DDD/CQRS
First, if you abandon exceptions, you give up the ability of the caller to distinguish between success and failure.  This may or may not be an issue (if the command was sent on an asynchronous channel, it doesn't matter, after all -- the caller is waiting for an event on the other side).  It might also open up the question of "why throw rather than no-op?"

That's not necessarily true. Both outputs (events and errors) are tied back to the originating command, and therefore an API could perform an abstraction to provide a successful/failed response.

For example, assume that a browser sends an HTTP request, which is handled by nginx, who passes the HTTP request on to a FastCGI process. The FastCGI process takes user input (supplied as JSON/XML/etc) and instantiates a command. It then subscribes to both the error and event(s) that could possibly be produced by the command (including a suitable timeout) BEFORE dispatching the command on the command bus. It could then determine whether the command succeeded or failed. Essentially, the FastCGI process becomes a "reliability API" built on top of the asynchronous API, much like TCP provides reliable connections IP packets.

Second, the events in our book of record are histories of the entities we are tracking.  So I don't understand where an exception event would go?  Into the history of the aggregate we were going to change?  Into the history of a different aggregate that we haven't loaded yet?  Were we intending to modify that same aggregate if the command succeeded?  In the same transaction?  What are you going to do if the attempt to persist the exception fails?

For an SQL backend, you'd have "command_log", "event_log", "error_log" tables. In regards to failure to persist an exception, how is this any different to failure to persist a command or an event. They're all just POCOs.

Put another way, are the exceptions a domain concern, or an application concern? If they really are a domain concern, then why aren't we using a protocol in the domain to track each instance?

The error objects WOULD BE the protocol. A command is received, and either an error is produced or one-or-more events are produced. It's just another way to handle the "bus.reply()" scenario, where a saga needs to be aware of a failure.

Douglas Gugino

unread,
May 23, 2016, 8:41:35 AM5/23/16
to DDD/CQRS
A ha !  from a Business method perspective; but of course what you say is true. 




Reply all
Reply to author
Forward
0 new messages