C# template service

Hello everyone, I'm a developer from Bimeister and I want to talk about the C# template service that we created to unify our applications and reduce the time developers spend on basic application configuration.

We started as a small company with one main service that performed business logic. Time passed, the number of developers and the number of teams increased. We were moving to a microservice architecture, and then the problem of code unification arose. Each team saw the application structure differently, which could lead to a zoo of different types of services.

To solve this problem, Microsoft has created the ability to create your own application templates for .net. It is a regular C# project that can be packaged into a nuget package.

First of all, we need to decide on the application architecture, what approach, what patterns we will use. For inspiration, we looked at an example from Microsoft https://github.com/dotnet/eShop.

Structure

According to a clean architecture, domain logic should be the center. It must be called by the application layer, and the infrastructure must provide connectivity to external systems and storage. We presented each of these layers as a separate project in our solution.

Microservice.Api → Microservice.Infrastacture → Microservice.Application → Microservice.Domain

Microservice.Domain

The core of our application with business logic. It should contain a minimum of nuget packages and not depend on any other library from our list. It should contain aggregates, entities, value objects, or other domain models, depending on how you organize the logic. Interfaces such as domain repositories will also be located here. They will be implemented by other layers. To create these domain entities you need base classes, the example from ShopOnContainers is suitable for this. https://github.com/dotnet/eShop/tree/main/src/Ordering.Domain/SeedWork. If you develop using aggregates, entities and value objects, then you will get what you need.

Microservice.Application

An application layer that will prepare data for the domain layer and organize the processing of commands and requests. We have been successfully using the Mediatr package as the basis of this layer for 5 years. https://github.com/jbogard/MediatR. Try to make this layer without unnecessary dependencies; A sign of a well-written application layer is that it can be run in both a console application and in webapi, and can also work with any type of storage.

Microservice.Infrastructure

This layer is needed to work with the database, in our case it was Postgres. We connect the necessary EntityFramework libraries to the project and create implementations of the repository interfaces, which are located on the internal layers.

Microservice.Api

Webapi, which contains the controllers and runs the entire application. From controllers we call commands and requests from the application layer. And at startup, it adds all the necessary entities to the DI container; for this, each application layer had an extension for IServiceCollection. Also in this layer we connected our libraries for logging, tracing and health checks.

As a result, our service looks like this:

The service described here is simpler than our real one, because in this article I only want to describe the structure as a whole.

Recommendations

Add a detailed description indicating why each layer is needed and how to use the underlying entities of your application. It would be helpful to add an example query that would use the underlying entities so that the developer has an example of use. It is better to let the unnecessary things be removed by the developers themselves than for questions to constantly arise, or for tools to be used for other purposes. However, you shouldn’t do everything too strictly and leave no room for maneuver; it’s still not possible to create a universal template for everything; let the developers still have the opportunity to correct, delete and not use what they consider necessary.

It is better to wrap external libraries (such as Mediatr) with your own code in order to be able to painlessly update/change the library without having to redo the entire code.

Also, don’t try to stuff everything there, including settings for CI/CD and Git. They are often configured by the DevOps team, and searching for these settings inside a template service will be inconvenient. Your template is used to create a .net assembly and nothing more.

Pre-created tools in the template also need to be refined over time, so it is better to move the code that will be updated frequently into nuget packages so that those who have already created their own applications from our template will also receive updates.

Package formation

We now have a general structure for the application. It's time to put it in a nuget package and publish it as an application template. Information about the template is stored in a separate file, create a template folder and put solutions and projects there. At the templates folder level, create a .template.config folder and put the template.json file there with the following contents

{
  "$schema": "<https://json.schemastore.org/template>",
  "author": "Marat Kaloev",
  "classifications": ["Web", "Template", "Api", ".NET 8"],
  "identity": "Microservice",
  "name": "Bimeister WebApi",
  "shortName": "bimeister-webapi",
  "tags": { "language": "C#" },
  "sourceName": "Microservice"
}

You can read more about the contents of this file here. Note that when generating a project from your template, the name will be inserted into the project name, replacing the value of the “sourceName” property. I created an example with the Microservice prefix, so I entered it there.

Now we need to create a nuget package from this assembly.

In the root folder we create a file nuget.csproj to package our application into a package. The main difference from the usual csroj for nuget is that the PackageType attribute has the Template value, and we also need to indicate that we exclude the solution file (*.sln) from the template.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <PackageType>Template</PackageType>
    <PackageVersion>1.0</PackageVersion>
    <PackageId>Bimeister.Microservice.Template</PackageId>
    <Title>Bimeister Microservice Template</Title>
    <Authors>Marat Kaloev</Authors>
    <Description>Template for bimeister applications.</Description>
    <PackageTags>C#;webapi;.net8</PackageTags>
    <TargetFramework>net8.0</TargetFramework>
    <IncludeContentInPack>true</IncludeContentInPack>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <ContentTargetFolders>content</ContentTargetFolders>
  </PropertyGroup>
  <ItemGroup>
    <Content Include="template\\**\\*" Exclude="template\\**\\bin\\**;template\\**\\obj\\**"/>
    <Compile Remove="**\\*"/>
  </ItemGroup>
</Project>

Now you can use the dotnet pack command to build the package and publish it to the repository. In order to add it to the list of available templates, you need to install it by specifying the PackageId of the package – dotnet new install Bimeister.Microservice.Template (don't forget to connect your repository).

Usage

To standardize the settings of the new repository, our DevOps team created a repository with the necessary files and folder structure. Now, when creating your own repository, the developer needs to copy the repository from DevOps, create a template for our application in the required folder and add the necessary dependencies. After this, the developer will only have to remove the unnecessary stuff (if necessary), and you can immediately begin development.

Results

Our template service saved a lot of time and standardized our applications. So now there are no special problems when switching between the development of different services. It also greatly simplifies the entry of newcomers into the team – you can study application examples and start development faster. Of course, you have to maintain this template, otherwise over time it will become irrelevant, so do not forget to get feedback and update dependencies.

Links for deeper learning

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *