NuGet has taken on more and more place in the development lifecycle by way of being a huge boon to productivity. Back before package managers were commonplace, finding the bits that do what you’re looking for was haphazard at best. Today, there’s a NuGet package to do almost everything and anything you can imagine.
But using NuGet packages liberally comes at a cost that is often overlooked. That cost comes in the form of extra complexity to maintain an application. I’ll look at some specific examples of that complexity as it applies to serverless functions in this post.
#1 Code Bloat
A serverless function, be it hosted on AWS, Azure, or anything else, should do only one thing. In theory, a function that does a single thing shouldn’t need many package dependencies. But in practice, this isn’t always the case.
A serverless function with many package dependencies will become engorged with lots of code that it doesn’t need. Take Entity Framework as an example. Its assembly is a tad over 5Mb in size. How much of that code am I likely to use if all I’m doing is reading and writing a POCO to a table? The answer, of course, is not much at all.
Large package dependencies will increase the memory consumption of your functions. And as we know, memory consumption directly affects cost in serverless, so it’s in your interest to use the lightest packages you can find. You could profile their performance using a tool like Benchmark.net, or monitor your functions before and after with an APM like Application Insights or X-Ray.
Always evaluate if there is some way to avoid taking on an additional dependency. It can be worth it, in some cases, to code the functionality we’re looking for ourselves, especially if we’re only going to be using a small subset of the functionality of the package in question.
#2 Dependency Version Conflicts
It doesn’t take much for the dreaded “Found conflicts between different versions of the same dependent assembly” warning to appear in Visual Studio.
I was trying to replicate this behaviour in my Functions 2.0 demo project and got a similar error when trying to reference a .NET Standard assembly that used the latest Newtonsoft.Json package:
Microsoft.NET.Sdk.Functions 1.0.24 requires Newtonsoft.Json (= 11.0.2) but version Newtonsoft.Json 12.0.1 was resolved.
The only way to resolve the warning is by downgrading from 12.0.1 to 11.0.2. But what if another package that I want to use requires version 12? I’ll have to find a workaround, or use another package that doesn’t depend on version 12. This problem is accentuated as you add more and more NuGet packages to a project.
#3 Inherent Technical Debt
Software will always be buggy, and NuGet packages are no exception. Some packages publish fixes regularly, requiring frequent upgrades, otherwise exposing ourselves to a bug or vulnerability in our code.
But that’s the tricky part. Updating a package means regression testing anywhere that the package is used, especially when it comes to major version upgrades. I’ve seen many bugs appear following a package upgrade over the years, and it’s one of the reasons why I believe strongly in automated acceptance tests to test an entire serverless application.
There’s also the possibility that a package used today may become deprecated tomorrow. Take the migration to .NET Core as an example. Many packages didn’t make the transition because the maintainers moved on to other things.
For example, RhinoMocks, the popular .NET Framework mocking library, was never ported to .NET Core. This poses a problem if I’m trying to migrate a .NET Framework project to .NET Core. The only choice is to rewrite the tests with another mocking library.
The same fate can happen to any NuGet package, at any time. The moral of the story: take on dependencies only after careful consideration.