Backend
As mentioned in the domain section of the docs, we are powering the backend with .NET Core 9. It will be organized in a Modular Monolith manner, powered through Redis as its backing service.
On top of that we will have it orchestrated with Docker (docker-compose), all living in its own repository.
Static Code Analyzer and Coding Conventions
A common issue when working in teams is to enforce consistent coding styles whilst also maintaining a high level of code quality.
One tool we have at our disposal is an .editorconfig
file. Inside of this file you
can setup coding styling rules you want to be enforced for a whole solution. (This is
if you save the file as a Solution Item). Now, if you are using Rider
things might
be a bit different, however its support for the .editorconfig
file is great, it will
highlight if you are setting some rule that’s not supported and even give you suggestions
to be available values, this is all fed through documentation and up to date syntax:
Unlike the Frontend, we should not be
comitting |
However, besides just having this conventions file, we can install NuGet packages as well:
-
StyleCop.Analyzers
-
SonarAnalyzer.CSharp
Having both of the packages installed might seem illogical, but there’s a clear separation of concerns for both of these packages.
StyleCop.Analyzers
This NuGet package focuses on C# coding style and formatting rules (e.g., spacing, naming conventions, indentation). Based on Microsoft’s C# coding guidelines it helps maintaining a consistent code style across the team.
Example Rule: Requires XML documentation comments for public members.
SonarAnalyzer.CSharp
This static code analyzer focuses on code quality, maintainability, and security. Meaning that it will provide static analysis to detect issues like code smells, bugs, and vulnerabilities.
It is often paired with SonarQube or SonarCloud for continous code quality monitoring, however these services are paid, so bear that in mind. (An extra layer into the process with direct budgeting impact).
Example Rule: Detects potential null reference exceptions or inefficient LINQ queries.
Code Styling and Code Quality
We will use both of the tools to enforce both style and a deeper static code analysis, the aim of the project (and the team) is to have consistent formatting alongside quality/security insights.
It’s true that this robustness should be aimed at large-scale projects, however, even in small teams and environments this can be setup and set a good precedent for further projects that are built with the foresight of making the dev process mroe agile yet with more quality.
Packages and Rules Setup
At the root of the solution we have and .editorconfig
file, this file will be picked
up by the IDE and will start enforcing all the rules that are stated there as we
go through code. By reading documentation laid out at Static Code Analyzer and Coding Conventions
we can extend and fine-tune the rules to our liking.
With that in mind, the .editorconfig
file should be understood as different layers to
setup warnings and errors we might want to enforce on the whole codebase, this can have
multiple type of rules turned off/on. At the beggining of the file we have general usage
rules setout (indent style, tab_width, pascal_casing, others). And by the end we can
have different sections for different analyzers' rules:
-
StyleCop.Analyzers (prefix: SA)
-
SonarAnalyzers.CSharp (prefix: S)
-
NET.CodeAnalyzers (prefix: CA)
-
Visual Studio.Analyzers (prefix: IDE)
As the project is being developed, there might be rules or things we do not like, so we can configure them to our liking at this file and the specific rule number.
With this in mind, the second step of how to start enforcing code quality, is to change the warnings that might start showing up with the analyzers as errors. (Why?) Because we want for any coding convention infraction to be treated as an error and stop the build from happening, this can even go further into CI/CD pipelines; the moment someone pushes a change that breaks coding guidelines, the CI/CD pipeline will fail immediately.
The way to enforce this would take place normally at a .csproj
file:
<PropertyGroup>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>All</AnalysisMode>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> (1)
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors> (2)
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> (3)
</PropertyGroup>
1 | The moment some IDE analyzer picks up on a rule infraction, it will no longer just show as a warning, but as an error. |
2 | And the moment a static analyzer picks up on some warning, it will immediately be treated as an error. |
3 | Lastly, we want for the build to fail the moment some code smell makes its way into the codebase. |
And this is fine, but depending on the size of the project (solution) having to configure this in every single project is cumbersome rather than useful. Luckily we have extensions on the framework and IDE levels that help centralize these types of cross-cutting concerns.
Hence, there’s a Directory.Build.props
file at the root of the solution, this is an xml
file that will take care of
setting up the enforcement of specific settings at a solution level. Think of it
as a super-layer that gets applied to all solution’s projects. When it comes to our
code quality concerns, the same settings that would be applied to a
csproj file will now be stated there.
One really important setting we need to take into consideration at the |
NuGet Packages
As it was mentioned before, we will be installing StyleCop.Analyzers alongside SonarAnalyzer.CSharp, that’s pretty straightforward, however, since we want for this to be centralized, we will have to configure them in a specific way so that they get installed in all projects:
<ItemGroup>
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.6.0.109712"/>
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556"/>
</ItemGroup>
The versions of the packages will vary as time moves on.
For further information in regards to how packages are being centrally managed, you can head down to Directory.Packages.props.
Shaping rules to one’s liking
And so, once we have installed the static analyzers and setup and wired a project
to be using centralized build props alongside centralized package managing. We will
definitely start getting errors immediately. Some rules might overlap between analyzers
and perhaps some rules that they enforce might not be to our liking, and so we can start
customizing those rules to our liking. The .editorconfig
file will be the place to
setup all these things.
As mentioned in Static Code Analyzer and Coding Conventions, we will add specific rules for the code style of the project, but it’s also in here that we can add overrides to ignore some analyzer errors:
# StyleCop.Analyzers (SA...)
dotnet_diagnostic.SA1649.severity = none
dotnet_diagnostic.SA1600.severity = none
dotnet_diagnostic.SA1400.severity = none
dotnet_diagnostic.SA1101.severity = none
dotnet_diagnostic.SA1633.severity = none
dotnet_diagnostic.SA0001.severity = none
# SonarAnalyzers.CSharp (S...)
dotnet_diagnostic.S3903.severity = none
# NET.CodeAnalyzers (CA...)
dotnet_diagnostic.CA2007.severity = none
dotnet_diagnostic.CA5394.severity = none
# Visual Studio.Analyzers (IDE...)
By the end, we can separate the different rules we are ignoring by which analyzer is it that it’s generating the rule, this also keeps it documented for other developers to see the rules that were skipped. To read more on them and the reasoning behind them you can google each rule ID and read up on the specific documentation for them.
It’s also worth noting that some IDE’s (such as Rider) might detect the settings
in .editorconfig
as invalid or with warnings. This is expected and in the end the IDE
will stil pick up on the overrides as it should, by running the project we should be
getting errors as expected and overrides for specific ones should also be taken
into consideration.
Whilst these rules might become a bit annoying at first, reading up on the reason for them to exist in the first place is a great opportunity to deepen the developers knowledge, and it should always be leveraged in a way that makes the code the developer makes of higher quality. In the end, it should be discussed and documented with facts and logic when a rule will end up being ignored. Once should always aim at using as much as he can from tools and recommendations, but also think for themselves. |
Another thing worthy of note is the fact that sometimes we might end up trying to
save files With BOM by default (at least on Windows). We can even configure our
.editorconfig
to not let pass files that have this setting applied to them. (this
is enforced by the charset = utf-8
setting). If we get an error on "file format"
or "unexpected character", we might have to save the file that’s failing the check
without BOM.
CORS
Cross-Origin Resource Sharing is a mechanism to safely bypass the same-origin policy, that is, it allows a web page to access restricted resources from a server on a different domain than the domain that served the web page. By default HTTP is configured to only allow "same-origin" requests, in an effort to add more security, CORS allows for the developer to configure what type of interactions will be allowed and from which domains.
In our specific use case we will configure CORS (in prod/demo) to have specific policies that would only allow specific shapes of requests. However, for dev purposes we have configured the CORS middleware to accept any request:
if (builder.Environment.IsDevelopment())
{
builder.Services.AddCors(options => (1)
{
options.AddPolicy( (2)
"AllowAll", (3)
policy => policy
.AllowAnyOrigin() (4)
.AllowAnyMethod()
.AllowAnyHeader());
});
}
// ...more code
if (builder.Environment.IsDevelopment())
{
app.UseCors("AllowAll"); (5)
}
1 | As you can see on the builder side of the app we can configure CORS with the
AddCors() extension method. |
2 | To the parameter of the delegate that the extension method receives we can use another
extension method called AddPolicy() . |
3 | We can add multiple policies that can abstract a specific domain or set of rules we might want to add to the CORS middleware, we use names to differentiate them. |
4 | And through a delegate on the extension method we leverage the fluent pattern to start the composition of the rules that the policy will add to the middleware. |
5 | And when configuring the HTTP pipeline we have to use the UseCors() extension
method and call the respective policy so that it’s applied to the pipeline. |