Thoughts on how to integrate with 3rd party libraries when you’re a library author

Yoda in a Santa's costume, carrying a magic wandWhen building .NET systems for clients, it’s just awesome how much free open source that’s available at your fingertips – and with tools like NuGet around, it’s become extremely easy to rapidly pull in various colors and sizes of Legos to snap together and build on. Except, sometimes those Legos don’t snap 🙁

Different types of libraries and frameworks are more or less susceptible to Lego-snap-issues, depending on their place in the stack. That is, libraries with many incoming references are inherently problematic in this regard, the ubiquitous examples being logging libraries and serialization libraries.

When I built Rebus, one of my solemn goals was to make Rebus dependent on .NET BCL ONLY. All kinds of integration with 3rd party libraries would have to be made via small dedicated adapter projects, because this way – in the case where there’s a conflict with one of your own dependencies – only that single adapter would have to be either built against your version of the problematatic dependency, or switched for something else.

Serialization with Rebus

Rebus serializes message bodies by using an ISerializeMessages abstraction. And since Rebus’ default transport message serialization format is JSON, and I didn’t feel like re-re-re-inventing any wheels, I decided to pull in the nifty JSON.NET package in order to implement Rebus’ JsonMessageSerializer that is part of the core Rebus.dll. But that clearly violates Rebus’ goal of having no dependencies besides what’s in the .NET framework – so how did I solve that?

Simple: ILmerge JSON.NET into Rebus! Simple solution, very effective.

Maybe there’s something I’m being ignorant about here, but I don’t get why projects like e.g. RavenDB keeps having issues with their JSON.NET dependency. Why didn’t Ayende just merge it from the start?

Update January 7th 2013: Well, it appears that they did just that for RavenDB 2: RavenDB-220

Logging with Rebus

And then there’s logging – in order to log things good, all Rebus classes must be given access to a logger – and I like it when the logger is named after the classes it is being used from, which always turns out to be a piece of really useful information when you’re trying to track down where stuff went wrong some time in the past.

Ideally, I wanted a syntax that resembles the Log4Net one-liner, that you’ll most likely encounter if you come across code that uses Log4Net for logging – each class usually initializes its own logger like this:

thus retrieving a static readonly logger instance named after the calling class. So, originally I started out by copying the Log4Net API, providing adapters for console logging, Log4Net logging, etc.

That worked perfectly for a while, but later on, since I spent so much time writing integration tests for Rebus, I also wanted the logging mechanism to be swappable at runtime – i.e. I wanted it so that Rebus could have its logger swapped from e.g. a ConsoleLogger to a NullLogger in a performance test, where all the console output would otherwise interfere with the measurements.

Therefore, my logging “abstraction” is a bit more involved. But it accomplishes all goals: All classes can have a static logger instance, named after the class, swappable at runtime, without introducting dependencies on external stuff, and with very little (read: an acceptable amount of) ceremony (too much for my tastes actually, but I accept this one because of the goodness it brings).

Check out what a class must do in order to get a Rebus logger:

As you can see, the logger is set when the Changed event is raised – and by taking control of the add/ remove operations of the event, I can ensure that the logger is set when each subscriber first subscribes. This way, the logger is immediately initialized by the currently configured static IRebusLoggingFactory, and in the event that the factory is changed, all logger instances are changed.

The Changed event is implemented like this:

The last thing is to provide a meaningful name to the logger – i.e. I wanted the name of the calling class to be used, so the call to GetCurrentClassLogger() had to do some StackFrame trickery in order to fullfil the promise made by its title. The implementation is pretty simple:

As you can see, it just skips one level on the call stack, i.e. it gets the calling method, and then we can use the standard reflection API to get the type that has this method. Easy peasy!

This was an example on how two of the more troublesome dependencies can be dealt with, allowing your library or framework to be effectively dependency-free.

One thought on “Thoughts on how to integrate with 3rd party libraries when you’re a library author

  1. “Maybe there’s something I’m being ignorant about here, but I don’t get why projects like e.g. RavenDB keeps having issues with their JSON.NET dependency. Why didn’t Ayende just merge it from the start?”

    It’s because users of Raven must be able to configure and customize the serializer through its public API (like creating custom converters and quite advanced schema rules) and I guess making wrappers for all of Json.net’s many configuration options was considered a worse option. I believe next version of Raven will have the Json.net source code internalized into it to resolve the issue.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.