I have an application service that receives certain exceptions from the domain layer and possibly underlying layers.
I would like to translate received exceptions to application-specific exceptions, but would prefer to separate the code that performs the translation, so that it does not make the code in the service class more difficult to understand.
Is there a way to accomplish this in Scala?
Thanks in advance!
As at some previous occasions, I will answer my own question. I will do it with an example that uses pure Scala.
In this very simplistic example, there is one domain class that is used by an application service. In addition there is a client of the service.
The domain class is written to throw different kinds of generic exceptions:
Then there is the application service class that calls the above domain class.
As you can see, the service class do not contain any exception handling or translation. This is the goal of this exercise - I do not want to clutter the service class, containing application logic, with error handing, logging, exception translation etc. Despite this, I want the service to throw application-specific exceptions to clients in case of errors.
As an interlude and in order to make the example complete, here are the classes implementing the application-specific exceptions:
Then there is a client of the application service, which also is the starter class of this example:
If we run the example program in its current incarnation, we obtain the following output:
In three of the four cases, the client received an exception that I did not want it to receive.
So, how do we fix this without adding code to the service class and use only pure Scala?
The solution I have devised uses traits. I separate all the exception translation code into the following trait:
To bring the above trait together with the service class, I implement a child class to the service class:
Finally, we modify the client to instantiate the child service class instead of the original service class. Note also that I have uncommented one import:
If we now run the example program, we get the following output:
Trying with a message that is too short
Received a MessageTooShortException as expected: The message 'a' is too short
Trying with a number that is too small (negative)
Received a NumberTooSmallException as expected: The number '-1' is too small
Trying with a number that breaks an assumption
Received a AssumptionBrokenException as expected: Assumption broken: assumption failed: the result of doubling the number must not become 4
Trying with good parameters, which should result in an exception that should be passed through
Received IOException as expected: cannot read data
I succeeded in separating application logic, in the application service, and exception translation!
There is more, but interested readers need to wait for my next book to read about the details.
Thanks for sharing your solution, Ivan. Many of us around here are only just starting to learn Scala, so it's always good to see how other people solve problems like this.
No more Blub for me, thank you, Vicar.
Joined: Oct 04, 2006
I have further improved the solution I earlier posted. Changes are:
Introduce a trait as interface of the service.
Remove the service child class that mixed in the trait(s) implementing non-business concerns.
Perform mixin of the trait(s) implementing non-business concerns at service instance creation time.
Eliminated the MyApplicationServiceExceptionHandling trait.
Eliminated the class MyApplicationServiceWithExceptionTranslation.
Added a trait to perform logging (non-business concern).
Code speaks louder than words, so here is the modified example program. The domain and exception classes are the same as in the above post, so please refer to that post for implementation of those classes.
Note that the order in which the traits are mixed in is significant. Try experimenting with the logging trait first and look at the exceptions logged.
I feel that this new solution is more elegant than the previous one. One possible drawback may be that it will be difficult to re-use traits implementing non-business concerns with different services.