Go That Way
Microservices are built the same way as any other piece of software: one line of code at a time. But where that code lives, and how it is organized, is an essential part of the life-cycle.
As an architectural pattern, “microservice” is very much about organizing an application into small, manageable pieces. It can be tempting to stick to the (all of 25 year-long) “traditional” monolith style of codebase management and place all of the microservices for an application in a single repository. This inevitably leads to multiple solution files, some of which are deeply nested in the repository and will never be found by new developers.
This post is part of a series on building a RESTful Web-API Job microservice using .NET Core. The approach, design, and tools used herein are all based on my personal experience and should not be considered prescriptive. Some developers might like the approach, some might have different (and even better!) ideas. I’m open to suggestion, so feel free to hit the comments section on each post.
A good rule of thumb is that if a set of projects can be collected in their own, separate solution, then that solution belongs in its own repository. Having multiple solutions in a single repo is confusing, especially to new developers. Figuring out what to build and in what order requires extensive documentation – documentation that never exists because established development teams just don’t have the time to write down minor details like build dependencies.
However, organizing solutions into different repositories – each with its own build system that produces a set of artifacts to publish or deploy – is intrinsically easier to understand. By separating the application’s concerns into singularly-focused (Single Responsibility Principle) microservices in different repositories, it makes the code smaller and easier to maintain. This also makes it substantially easier to use different languages, platforms, and tools for different microservices. A single monolithic repository spanning many tools, languages, and platforms would be difficult to navigate and understand. Development teams may hesitate to adopt a broad set of technologies under these conditions. Through separation, however, it becomes easier to pick the right tools for a particular piece of functionality.
So this microservice will have its very own repository. It’s generally a good, safe practice to name a repository after its primary artifact. This microservice will be implemented using the hexagonal software architecture pattern, meaning that the business logic will exist in its own central, isolated library within the solution. This is largely to keep the business logic out of the “front end”.
That is, applications, especially microservices, should not be bound to a particular presentation layer by intermingling critical business logic layers with UI or protocol-specific API layers. This also allows the front-end to vary, or evolve over time: it could start as a SOAP service, become a RESTful HTTP Web API, and eventually be formalized into a custom high-performance TCP protocol. By keeping the business logic out of the presentation (UI/API) layer, the two can vary independently.
Thus, the repository will have the same name as the business logic library, which will also be the primary build artifact for the repository. For simplicity, it will be called Blueshift.Jobs. The repository is available on Github for readers to follow along with (or even contribute to!) the commit history. Clone the repository:
PS D:\Dev\Blueshift> git clone https://github.com/BlueshiftSoftware/Blueshift.Jobs.git
Now that the repo is made and cloned, the next step is to structure the code base. This is a .NET Core solution, so it will rely heavily on the dotnet cli, which can be downloaded from here. For .NET solutions, generally the solution name is the same as the repo (and, consequently, the primary build artifact). The dotnet cli does this automatically when an output name is not specified. The dotnet cli defaults to the name of the current (or specified) directory. It just so happens that the root of a repo is the best place to put a solution, so new developers can easily find it:
PS D:\Dev\Blueshift\Blueshift.Jobs> dotnet new sln
With Blueshift.Jobs.sln created, now it’s time to add some projects. The .NET Core open source projects on GitHub have settled into a pattern of placing artifact projects in a src folder and their tests in a parallel test folder. This makes it much easier to navigate the repositories, with publishable source projects in one directory tree and tests in a separate, equivalently structured tree:
PS D:\Dev\Blueshift\Blueshift.Jobs> dotnet new classlib -o src\Blueshift.Jobs
PS D:\Dev\Blueshift\Blueshift.Jobs> dotnet new xunit -o test\Blueshift.Jobs.Tests
PS D:\Dev\Blueshift\Blueshift.Jobs> dotnet sln add .\src\Blueshift.Jobs\
PS D:\Dev\Blueshift\Blueshift.Jobs> dotnet sln add .\test\Blueshift.Jobs.Tests\
PS D:\Dev\Blueshift\Blueshift.Jobs> dotnet add .\test\Blueshift.Jobs.Tests\ reference .\src\Blueshift.Jobs\
And with that, the basic code structure is set up! It has a classlib project with the repo’s primary build artifact, and a test project that references that classlib. However, there are a few critical components missing from the repo. Namely, some nice housekeeping items:
\.gitattributes
\.editorconfig
\build.ps1
build.ps1 is perhaps the most important piece for this stage. It will give the repo quick build, test, and publish capabilities which will allow the adoption of CI/CD methodologies early on in the development lifecycle. There will be more on this in a later post. This script requires some environment variables to be present for NuGet publishing, which will come into play when finalizing the release configuration.
For now, there’s just one more set of files to add to help organize and tidy up the repo:
\Directory.Build.props
\Directory.Build.targets
\Packages.props
\src\Directory.Build.props
\src\Directory.Build.targets
\test\Directory.Build.props
\test\Directory.Build.targets
These files provide an easy and convenient way to centralize package management for the entire repo. MSBuild looks up the directory hierarchy in search of Directory.Build.xxx files, which are automatically included. By design, only the first one first up the tree is included, so they have to explicitly inherit any files above them.
The root Directory.Build.props file sets up common MSBuild variables that can be used by all of the projects in the repo. Meanwhile, Directory.Build.targets file adds Microsoft.Build.CentralPackageVersions, which uses the package versions specified in Packages.props to ensure that all of the projects in the repo use the same versions. This alleviates a significant amount of dependency micromanagement! Read more about this at the project site.
The Directory.Build.xxx files under src and test will respectively set up common dependencies for those folders. For now, only test will have anything of note: xunit and Moq dependencies to start.
After all of this, running build.ps1 works:

This is as good as any place to stop and commit. The code compiles, the tests run (even if they don’t do anything), and the build artifacts are generated. This is an awesome start to the project! For Agile teams using Scrum or Kanban, this is a great small initial story for starting a new project in a new repo. It takes just a few minutes and creates a working environment in which team members can immediately start collaborating.
Next step: a first-pass at the Jobs business logic!
You must be logged in to post a comment.