Damian Hickey

Mostly software and .NET related. Mostly.

RavenDB NuGet Packages: Coarse grained vs fine grained and sub-dependencies

This topic has recently come up on the RavenDB list (1, 2) and Ayende’s blog. I've been down that road of fine-grained packages (internal code) and back again so this is my opinion based on recent experience. The current opinion of the RavenDB team is that they want to avoid having 10s of nuget packages.

So, is 10’sof nuget packages really a problem and if so, for whom and how?

The Package Consumer

From the package consumer side, find grained packages allows them to pick and choose precisely what they want without crufting up their project's references with unnecessary extraneous references. (Unnecessary references are a pet hate of mine). There are a number of OSS projects that appear to be doing this successfully such as Ninject , ServiceStack and NServiceBus.

One of the cosumer’s concerns is that if they do pick 2 packages where one is dependent on the other, is that of package versioning and updating. If they were to pick RavenDB-Client and (a hypothetical) RavenDB-Client(Debug), they expect that at the precise moment one package is updated, then the other one is done so too, such that updating a solution is easy. That is unless RavenDB team is exercising flawless semantic versioning, which I doubt.

The other concern, regardless of a coarse-grained or fined grained packaging strategy, is that of package sub-dependencies. Despite the best intentions of authors with semver and specifying package version ranges in nuspec files , this is an area of brittleness, as was recently demonstrated by a recent log4net package update.Also specifying a package dependency because your package uses it internally unfairly makes your consumer dependent on it. Introduce another package that has the same dependency but maybe a different version ant they are at risk of runtime exceptions, deployment conflicts and having to  perform brittle assembly redirect hacks.

Currently, adding a RavenDB-Client package to a class library adds the following 8 references:

image

… and the following package dependencies:

image

My fresh class library is now dependent on a specific Logging Framework, some MVC stuff that has nothing to do with what I am doing and a Community Technology Preview library that may or may not have redistribution licence concerns. This isn’t a great experience. A brief analysis:

  1. AsyncCtpLibrary’s usage is entirely internal to Raven.Client.Lightweight could be ilmerged and internalized. Example projects that do this approach include Automapper and Moq.
  2. Newtonsoft.Json is exposed through Raven.Client.Lightweight’s exported types so is a real dependency.
  3. NLog? There is a better way.
  4. Raven.Abstractions appears to contain both client side and server side concerns. The client side ones could be extracted and placed into Raven.Client.Lightweight and referenced by server side assemblies. (Perhaps, don’t know enough to say for sure)
  5. Raven.Client.MvcIntegration and .Debug are entirely optional and could be provided by separate packages, if I wanted them.
  6. System.ComponentModel.Composition could probably be removed if the server side abstractions were not part of the client package.

The end result, in my opinion, should look like this:

image

If the concerns of minimizing sub package dependencies and lock-stepping of package releases are addressed, then I believe that fine-grained packages are desirable to a package consumer.

Package Producer

The primary concern on the producer side is one of maintenance. Adding additional fine-grained package specifications to a single solution does have a cost, but I’d argue that it’s worth it when considering benefits to the consumer.

Where things do get difficult fast for the producer though is when the fine grain packages are independently versioned. Previously I said I doubted Raven is doing flawless semantic versioning. I doubt anyone is doing it flawlessly because there is no tooling available to enforce and you can’t rely on humans. I’ve tried the automatic CI based package updating “ripple” where Solution B that produces Package B but depends  on Package A from solution A , automatically updates itself when a new version of Package A is produced. It didn’t work reliably at all. If the producer has a lot of fine-grained solutions and they have a lot of momentum, package management quickly becomes a mess and a massive time sink.

But if the package producer is using a single solution (as is the case of RavenDB) and a concurrent  release of all fine-grained packages at the same time is performed, the cost of supporting fine grained packages is not prohibitive. This is the approach currently taken by ServiceStack.

blog comments powered by Disqus