Enterprise Integration Zone is brought to you in partnership with:

Andrzej is a DZone MVB and is not an employee of DZone and has posted 24 posts at DZone. View Full User Profile

Custom Exceptions or Domain Events?

06.11.2014
| 2558 views |
  • submit to reddit

The topic of custom exceptions is surprisingly controversial. Some argue that exceptions shouldn't be used for anything related to "business", while others (me included) say that exceptions like InsufficientFunds are fine - I even wrote a whole chapter about using custom exceptions to simplify controllers in my Rails Refactoring book.

I've read an interesting blog post today about custom exceptions (here - unfortunately it's in Polish), where the author advocates for using "business exceptions".

The example comes from a dialling application, so there are the following exceptions:

  • CallAlreadyInProgressException, 
  • IncorrectNumberException, 
  • NumberAlreadyDialedException


When I first looked at the code, I nodded in agreement. Given such domain, those exceptions make total sense to me. I'd implement it the same way.

When I linked to this post in our team chat, Mirek (who has more knowledge about DDD and CQRS than I do) said that it'd be better to implement with domain events. This surprised me a bit, as when the topic of custom exceptions is brought up, domain events are rarely shown as an alternative.

What's the difference between custom exceptions and domain events?

Look at this code:

# CUSTOM EXCEPTIONS
class CallAlreadyInProgress < StandardError
end
class Dialer
  def call(number)
    raise CallAlreadyInProgress.new if call_already_in_progress?
  end
end
dialer = Dialer.new
begin
  dialer.call(number)
rescue CallAlreadyInProgress
  ui_notifier.send_message("Call already in progress")
end
# with domain events
class CallAlreadyInProgress < Event 
end
class ConnectionEstablished  < Event
end
class Dialer
  def call(number)
    if call_already_in_progress?
      CallAlreadyInProgress.new.publish()
      return 
    end
    ConnectionEstablished.new.publish()
  end
end
# those two can be in different places of our project
event_bus.register(CallAlreadyInProgress, ui_notifier.send_message("Call already in progress"))
dialer.call(number)
Domain events decouple the call to the method from returning the result. This is at the core of the CQRS approach - one of the most inspiring architectures I've ever read about. In short - any system operation is either a Command (a change to the system) or a Query (a read from the system). This rule drives the whole architecture.


In the code, it means that when you call dialler.call(number), you're not directly interested in the result, even to the point, that the method can fail (raise an exception). You made a command and that's it.

Now, obviously, you do need to know about the failure. That's why you publish an event, using whatever mechanism under the hood. It can be a simple singleton EventBus class, which just keeps a map of objects interested in certain events and notifies them if any such event happens. In our case, we could have a UINotifier class listening to the CallAlreadyInProgress event and sending a special UI message to the user of the system (the technical details are not important here - it can be via polling or Web Sockets).

There is another difference - with events we need to "publish" the fact that all went fine. What was implicit with exceptions (no exception is raised - success), here needs to be explicit. We publish the "ConnectionEstablished" event.

This creates a nice simplification of the whole code around it. It may make it a bit more indirect, but it's actually very simple. All of the pieces of code involved do just one thing.

Published at DZone with permission of Andrzej Krzywda, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)