2013-11-07

What you really need to know when authoring NuGet packages

I have noticed that most articles on how to author NuGet packages focus on the practical part; which commands to run and how to setup your own NuGet server. All these articles also fail to point out the most important information in the NuGet guidelines.

But before I tell you what you really need to know let's look at a really bad example. A few months ago I was consuming a NuGet package that was a client to a service I needed to use. When you installed the package you got four things: Two assemblies, one  configuration file and another NuGet package that was a dependency. That might not sound to bad, but it was.

First of all the second Nuget package that was a dependency was an emulator for the service. The motivation was that you needed the emulator to test your code. That was in my opinion a partially false statement. In my unit tests I fakes the dependency by faking the interfaces it provided. In integration testing I actually used the real service. I could have used the emulator for integration testing but it made more sense to use the real thing for me in this case. Also, it made no sense to add the emulator to my production code where I needed the client NuGet package.

Then we have the two assemblies. One was a shared assembly between the client and the service and the other was the client library itself. Turned out that the code in the client assembly was needed in one place, but the shared assembly contained a few constants I needed in a few more places. Naturally I could have created my own copies of the constants rather than installing the NuGet package in multiple places (I'll have a separate article on that), but especially for some unit tests I really wanted to reuse the shared assembly only.

And speaking about my unit test assemblies. The configuration file added by the NuGet package was added to all my unit test assemblies even though I didn't need them at all. I did need the client assembly so that i could fake the interfaces, but the configuration file was not needed.

All this meant that I had a bunch of stuff in a a lot of assemblies that was not really needed. Either I just had it there and ignored it or I had to remove it each time I updated the package. Neither option was very appealing to me.

So how should the NuGet author have done in this case? Well the recommendation is clear; in most cases there should be one NuGet package for each assembly. And this is how I would have structured the NuGet packages to accommodate everybody's needs assuming the service name was Foo and from the company Acme.
  • Acme.Foo.Core - This is the shared assembly package. It only contains the shared assembly.
  • Acme.Foo.Client - This package contains the client assembly only and is dependent on Acme.Foo.Core.
  • Acme.Foo.Client.Config - This package contains the client configuration and is dependent on Acme.Foo.Client.
  • Acme.Foo.Emulator - This package contains the emulator for the Foo service and a client config to talk to the emulator. This package depends on the Acme.Foo.Core.
With this approach you can see that depending on your needs to can bring in what you need rather than having one NuGet package containing everything. So if you are authoring NuGet packages; make no assumption on how people will use your packages but instead let people decide that themselves. But you can make it easy for them with some smart package dependency setup.

6 comments:

  1. Great example. I like the proposed solution with one exception. I don't see the value in separating the config from the client. Don't you always need the config file for it?
    Seems like the config means nothing by itself and the client always needs it.

    ReplyDelete
    Replies
    1. (hmmm... Seems my first post got lost...)

      Note that the configuration file would almost certainly need to be modified by the user (if not, there's no reason to make it a separate file). If it were part of Acme.Foo.Core, when it came time to update that package, Nuget would overwrite the user's customized config file, with a new copy of the default configuration.

      But by putting the config in a separate package, the file will be copied just once, and not every time the core package is updated.

      Delete
  2. I realize I failed to point out one thing. In my example (taken from real life) there was actually a case where I needed the Acme.Foo.Client in my unit test assemblies but I did not need the config file. Hence the config file is a separate package. Does that make sense Kareem?

    ReplyDelete
  3. Excellent article.
    Along those lines, I'd like to ask you, what's your preference when you have a dependency and you need to ship it in your package? Say you have a solution with ProjectInternalUtils and MyProduct. MyProduct uses ProjectInternalUtils, so you need to "nuget pack -IncludeReferencedProjects". Would you rather create a nuspec on ProjectInternalUtils so it goes as a package dependency, or just leave it as a dll?

    ReplyDelete
  4. @JulioUna; "It Depends" :-)
    Since it sounds like "MyProduct" in your example is something you would publish as a NuGet package, yes I would prefer the ProjectInternalUtils as a package dependency. Because you don't know if somebody else would like to use that in some other project or as in my example. I don't think you can ever go wrong with that approach. The obvious upside is that you can update one without the other. Downside is that you want to make sure different versions of the packages work together as expected.
    Bundling the two together can still make sense if the internal stuff is something nobody needs access to in order to for example test MyProduct. It simplifies your testing since you always knows which version is used together. And updating the whole package for a bug fix in one might not be a big deal. But you are definitely limiting the flexibility for others that way. It is not uncommon that people come up with usages you did not predict so keeping separate packages is always nicer to others I think.

    ReplyDelete
    Replies
    1. I was thinking in the case of "I really think this is internal and no one would need to consume it directly", but you're right also in that chances are someone actually might need it, at which point I'm better off with the package approach. Thanks for your answer!

      Delete