Update 2016/12/10: It’s now on Maven central! I’ve added some precisions below but I have left the post mostly intact for historical purposes.
Update 2016/07/08: The project is now available on GitHub! I plan on making it more generic before publishing it to Maven, I’ll update this post soon with the new details.
Exception handling across microservices can be tedious, let’s see how the Java reflection API can help us ease the pain!
Microservices architecture
When it comes to building a complex application in the cloud, microservices architecture is the newest and coolest kid in town. It has numerous advantages over the more traditional monolithic architecture such as :
- modularization and isolation, which makes the development easier in a big team;
- more efficient scaling of the critical paths of the application;
- possibility to upgrade only a microservice at a time, making the deployments less risky and less prone to unexpected side effects;
- technology independance : by exposing an API with a clearly defined contract with a set of common rules shared by all microservices, you don’t have to care which language or database is used by the microservice.
I could go on for a while on this, microservices are a great way to build applications in the cloud. There are lots of awesome OSS projects from our friends at Netflix and Spring that will help you doing this, from service discovery to mid-tier load balancing and dynamic configuration, there’s a library for most requirements you’ll have to meet. It’s also great to see Spring coming aboard with Spring Cloud collaborating and integrating some of the Netflix librairies into a very useful and simple library to use with your new or existing Spring application!
Caveats
It wouln’t be fair to avoid talking about the downsides of microservices as they do present some challenges and are not suited to everyone and every application out there. Splitting an application into microservices bring some additional concerns like :
- complex configuration management : 10 microservices? 10 configuration profiles, 10 Logback configurations, etc. (using a centralized configuration server can help you on this though);
- performance hit : you need to validate this token? No problem, just make a POST to this endpoint with the token in the body and you’ll get the response in no time! While this is true for most cases, the network overhead, serialization/deserialization process can become a bottleneck and you always have to be resilient for network outages or congestion;
- Interacting with other microservices brings a lot of boilerplate code : whereas a single additional method to a class was needed in a monolithic architecture, in a microservices you need a resource implementing an API, a client, some authorization mechanism, exception handling, etc.
Dynamic exception handling using Feign and reflection
Update 2016/12/10: Since the publication of this article, the library was heavily refactored in order to be generic for your exception hierarchy and to the model returned on your api when an exception is thrown. It now also supports :
- an optional dependency on Spring to support classpath scanning for abstract exception classes;
- a customizable
Decoder
; - a customizable fallback
ErrorDecoder
- better algorithm to instantiate exceptions (it now supports empty and all combinations of
String
andThrowable
constructors); - injection of the
Throwable
message field via reflection; - customization of many of the library’s aspects.
All the details and examples are available in the readme of the project on Github. The rest of the article below is still relevant to show the big picture but bear in mind that it’s not accurate with regards to the published code.
In a monolithic application, handling exceptions is a walk in the park. You try, you catch. However, if something goes wrong during an inter-service call, most of the times you’ll want to propagate this exception or handle it gracefully. The problem is, you don’t get an exception from the client, you get an HTTP code and a body describing the error or you may get a generic exception depending on the client used.
For some of our applications at Coveo, we use Feign to build our clients across services. It allows us to easily build clients by just writing an interface with the parameters, the endpoint and the thrown exceptions like this :
When using the client, you are able to easily decode errors using the ErrorDecoder
interface with the received Response
object when the HTTP code is not in the 200 range. Now, we only need a way to map the errors to the proper exception.
Required base exception
Most of our exceptions here at Coveo inherit from a base exception which defines a readable errorCode
that is unique per exception :
This allows us to translate exceptions on the API into a RestException
object with a consistent error code and message like this :
Using the errorCode
as the key, we can use the reflection API of Java to build up a map of thrown exceptions at runtime and rethrow them like there was no inter-service call!
Using reflection to create a dynamic ErrorDecoder
Alright, let’s dive into the code. First, we need a little POJO to hold the information for instantiation :
Then, we use reflection to get the thrown exceptions from the client in the constructor by passing the Feign interface as a parameter :
With the thrown exceptions in hand, knowing that they inherit from ServiceException
, we extract the errorCode
and the relevant constructors. It supports empty constructor and single String
parameter constructor :
Bonus feature, when the scanned exception is abstract, we use the Spring ClassPathScanningCandidateComponentProvider
to get all the subclasses and add them to the map :
Finally, we need to implement Feign ErrorDecoder
. We deserialize the body into the RestException
object who holds the message
and the errorCode
used to map to the proper exception :
Success!
Now that wasn’t so hard was it? By using this ErrorDecoder
, all the exceptions declared thrown, even the subclasses of abstract base exceptions in our APIs, will get a chance to live by and get thrown on both sides of an inter-service call, with no specific treatment, just some reflection magic!
Hopefully this will come in handy for you, thanks for reading!