The company I’m working at is big enough to span over multiple development teams. To be able to share a common way of working and to prevent that the wheel is invented over and over we rely on shared nuget packages.
I am author of many packages and the main contributor of those and that requires me to spend some time maintaining them. To be able to do this I need a working method that enables me to do this efficiently.
In this post I’m going to share my way of doing this, step by step.
To version control the packages git is used. Git is a de facto standard and need no further description :)
Decide on a good naming convention that will work for you. We use a prefix with our company separated by points narrowing down. For instance: CompanyA.AspNetCore.Mvc would be our own package with custom classes for ASP.NET Core MVC.
To stay consistent a project structure should be used. I base mine on this Gist by David Fowler. It is not strictly followed, but rather inspired by. For example, the test project is not separated into its own test folder, instead it is included in the src folder. This setup is done once, when I introduce additional packages I just copy the structure from another project. This structure could easily be extracted into a dotnet template.
Follow thisreference by adding your nuspec file.
Make sure you cover your packages with unit tests. That will give you confidence that your code works as you expect it to work and will also help other contributors. I’m using xUnit and in some projects AutoFixture to maximize maintainability and reduce code in the Arrange phase.
Add a README.md to the project that explains how to use the package. More on the README.md below in the section about Crafting the package. I am considering looking into Read the Docs as a complement.
The workflow that is used is Git Flow which means that we do not directly push changes to the master branch, but rather to feature branches that we integrate against the develop branch. I am not going to go into detail about Git Flow here.
When a feature is done, a pull request is opened and stakeholders are invited. And hopefully the feature is merged back to develop and ready for the next upcoming release. The git tag should reflect the version to be released. I explain my process of doing this in the section Crafting the package.
Breaking changes or not?
So, what is a breaking change?
If you alter the definition of an existing method that you are exposing to the consumer or remove properties from an object that is a breaking change. Please make sure you have a consuming test client targeting your packages so that you can verify that the changes you’ve made are correctly done.
When it’s time to update an already existing package you need to take your consumers into consideration. You need to ask yourself if it’s a breaking change or not that you are introducing. If it isn’t, then it’s fine. That change should then only alter the minor and/or patch version.
If you are about to introduce a breaking change you need to keep support for the deprecated feature at least one version before removing it. I tend to do it this way
- Add the new feature
- Deprecate the old feature by adding an ObsoleteAttribute and describe how the consumer should migrate.
- Bump the minor version and release a new version
In the next major release, remove the obsoleted feature. In this way the consumer is given some time to migrate their existing code to use the new version.
Before publishing a new package I make sure that
- The dependencies in the nuspec file is updated and that the supported target frameworks are in place
- The README.md is updated with new functionality and examples
- Versioning is correct
Dependencies in nuspec
There is a section in the nuspec file manifest that enabled to specify the dependencies that the package has. Even if packages only support one target framework they are included in a dependency group. That makes it easy in the future to add additional target frameworks.
Updating the README.md
To make sure that I update the README.md so that it is easy to follow I use Markdown Live Preview when editing it. That gives me a visual editor on how the file will be displayed. I then copy the content into my README.md and commit it.
Versioning the package
I’m using semantic versioning. To version the package the numbers must be consistent in *.csproj, .nuspec and in the git repository. For this I’m utilizing custom made PowerShell scripts.
This is executed by running it from the src folder.
.\bump-assembly-version.ps1 -Version 0.0.1
This is excuted by running it from the src folder.
.\bump-nuspec-version-ps1 -Version 0.0.1
I then commit those changes
git commit -am "Incremented project version"
Before pushing anything I make sure that my repository is tagged with the same version so that we have a tag that corresponds to the release
git tag -a 0.0.1 -m "Release 0.0.1"
(All of the above could easily be built into one single script file. I still do it separately but might consolidate in the future :))
And then push the tag
git push origin 0.0.1
And the code
There are different ways you can do this. Either locally using the nuget CLI to push the package to your feed, or let your CI service do this work.
Finally the nuget package is crafted based on the nuspec file. The package is then pushed to our internal nuget feed. For this to work seamless a service connection to our nuget feed is added in Azure DevOps. The package manager that is used is ProGet.
When this package published I usually notify all the developers on Slack that new packages are available and take any further discussions from there.
This is a process that is working for me. If you have any opinions or other ways of doing this I’d like to hear. What features should be extracted into nuget packages really depends on your company and how you share code between team. I prefer to have a consistent way of doing things and I’ve noticed that we save a lot of time in our projects if we can reuse good things that others already have built.
If you liked the post, you know what to do! 👏