🚧 Work in progress. Source code nor documentation can be used yet! 🚧
Last update 05/01/2021
Document version 1.0.0
There is an excellent book called "Adaptive Code: Agile coding with design patterns and SOLID principles (Developer Best Practices) 2nd Edition", by Gary McLean Hall.
I am using this book as the basis for this project.
The Entourage anti-pattern in simple Enlgish means that when you invite one, and only one, friend to your party, we brings his own uninvited friends. This causes undesirable dependency management. It brings unnecessary dependencies on board.
When we develop a project and add references to another project of conrcrete implementations with references to other projects of concrete implementations as well, or/and nuget packages too, therefore we have to add those references as well. In simple words, in a ASP.NET Web app project, in the bin folder you will see the EF related assemblies as implicit dependencies.
The Stairway pattern is useful when we use abstraction (interfaces or abstract classes) and dependency inversion. It is applied when the project just depends on other projects that have only interfaces. Because of the we can avoid the Entourage anti-pattern. Also, those interface only projects should not depend on other third party (e.g. nuget) references. It's not possible all the time, but it would be good if those projects contain no implementations at all.
The current structure allows the programmers to develop diffent implementations for different clients following the same methods signatures by implementing the interfaces.
- .NET version 5.0.1 SDK
- Identity server
- RabbitMQ, if RabbitMQ messagging implementation is used
- ElasticSearch, Kibana if Serilog + ElasticSearch + Kibana implementation is used
- Autofac
- Carter
- DocumentFormat.OpenXml
- Finbuckle.MultiTenant
- Fluent validation
- MassTransit
- Microsoft.Extensions.Caching.Redis
- NSubstitute
- NSubstitute.Analyzers.CSharp
- Serilog
- xunit
The template, in order to be functional and provide a solid full set of functionalities, embraces the following scenario:
"Think of yourself travelling to another city, or country for a bussiness trip. You want a list of restaraunts available, and some information about them (cost, food quality, timetable, cusine)."
At the moment:
- The codebase has 14 projects.
- 2 clients projects
- 2 unit tests projects
- 6 interfaces projects
- 4 implementations projects
- An appveyor.yml file
- It borrows elements from DDD.
- It is layer structured.
- It is tier structured.
- It embraces DI
- Carter template is used instead of ASP.NET Core WebApi template, for the Web API
This namespace is related to the agreed upon system behaviours. In the implementation classes we write the code that is related to the functional requirements. Each domain entity is assembled by three items:
- An Entity class item, that carries the domain (or business) logic
- A Record type item, that carries the data
- A Validator class item, that validates the data integrity
All 3 are immutable and any state changes should only be possible using methods. Data must be validated to ensure that we always have a valid state.
A service class is an orchestrator. A stateless object that performs actions. It includes all the objects and data, and dictates how all of them will work together. It actually implements pipelines of "input data -> transform data -> run calculations -> results -> persist and/or return results".
A repository encapsulates the logic that retrieves the data from a data source. It mediates between the domain and data mapping layers, acting like an in-memory domain object collection. The domain must be agnostic to the type of data that comprises the data source layer.
All the behaviours in this namespace are related to CRUD operations, no matter if its a SQL database, or a flat-file database.
The namespace is responsible to files retrieving, editing, saving and deleting. These files might be persisted in the hard drive or consumed by a web service. The implementation for this template, will support .txt and Microsoft Office files.
Caching is a technique of storing frequently used data in memory, or memory like infrastructure, so that, when the same data is needed next time, it would be directly retrieved from the memory instead executing more expensive statements, in order to retrieve the data from the data source.
The cached data will not be available in the following cases:
- If its lifetime expires,
- If the application releases its memory,
- If caching does not take place for some reason.
You can access items in the cache using an indexer and may control the lifetime of objects in the cache and set up links between the cached objects and their physical sources.
This template will support in-memory and Redis caching .
A message queue is a form of asynchronous service-to-service communication. Messages are stored on the queue until they are processed and deleted. Message queues can be used to decouple heavyweight processing, to buffer or batch work, and to smooth spiky workloads.
This template will support in-memory and RabbitMq messaging.
Mapper classes are in charge of mapping data from one object to another. This allows the domain to be isolated by the data structures the client app uses, and the data structures the repositories use. The isolated domain is easier to be unit tested by the programmers, and the solution structure is more scalable this ways.
Rules of thumb
- No null values allowed in the database
appveyor.yml
version: '1.0.{build}'
image: Visual Studio 2019
branches:
only:
- main
init:
# Good practise, because Windows line endings are different from Unix/Linux ones
- cmd: git config --global core.autocrlf true
#install:
# Install repo specific stuff here
before_build:
# Display .NET Core version
- cmd: dotnet --version
# Display minimal restore text
- cmd: dotnet restore
build_script:
- cmd: dotnet publish
#after_build:
# For once the build has completed
#artifacts:
# - path: '\src\bin\Debug\netcoreapp1.1\publish'
# name: WebSite
# type: WebDeployPackage
clone_depth: 1
test_script:
# restore packages for our unit tests
- cmd: dotnet restore
- cmd: dotnet test
#on_finish :
# any cleanup in here
deploy: off
main.yml
name: Build main branch
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.101
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 1.11
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Cache SonarCloud packages
uses: actions/cache@v1
with:
path: ~\sonar\cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache SonarCloud scanner
id: cache-sonar-scanner
uses: actions/cache@v1
with:
path: .\.sonar\scanner
key: ${{ runner.os }}-sonar-scanner
restore-keys: ${{ runner.os }}-sonar-scanner
- name: Install SonarCloud scanner
if: steps.cache-sonar-scanner.outputs.cache-hit != 'true'
shell: powershell
run: |
New-Item -Path .\.sonar\scanner -ItemType Directory
dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
shell: powershell
run: |
.\.sonar\scanner\dotnet-sonarscanner begin /k:"kostasgrevenitis_Codenception.StairwayPatternArchitecture" /o:"kostasgrevenitis-github" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io"
dotnet build
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
-- TODO --
- If Web Api client is used, create a controller class and the related DTO class(es)
- Create a service class
- Create an entity class, a record item and a validator class
- Create a repository class
- Create a data model class
- Create the source access class
- Add them into the Autofac IoC container, into the Startup.cs or Program.cs class
Always include
- Unit tests
- Integration tests
For each namespace we have the interfaces and the method signatures. So based on our needs, we can develop different concrete implementantion based on the desired requirements, by making sure we use the same methods signatures provided by the interfaces.
This ensures that programmers don't name the methods based on whatever they like, but they follow a common pattern. It also ensures higher code quality, better unit testing and easier code review.
Features | Description | Version |
---|---|---|
Option monad | null is used to indicate no value. The method called can't produce a value of the expected type, and therefore it returns "nothing". The programmer must continually check if the value is null. It's only a matter of time before a null reference exception crops up because the variable wasn't checked. This is where Option monad steps in. In functional languages use what's known as an option type. In F# it's called Option, in Haskell it's called Maybe. Option works in a very similar way to Nullable, except it works with all types rather than just value types. It's a struct and therefore can't be null. An instance can be created by either calling Some(value), which represents a positive "I have a value" response, or None, which is the equivalent of returning null. Also read this |
0.2.0.0 |
Exceptions | TODO | 0.3.0.0 |
To json | TODO | 0.3.0.0 |
Data source access | Implementation with Dapper | 0.4.0.0 |
Authentication/Authorization/Roles | Implementation with IdentityServer | 0.5.0.0 |
Caching management | - Implementation with InMemory cache - Implementation with Redis |
0.5.0.0 |
IO management | Microsoft Office files management with DocumentFormat.OpenXml | 0.6.0.0 |
Logging | - Implementation with Serilog to files - Implementation with Serilog, ElasticSearch, Kibana |
0.7.0.0 |
UI project | UI project built with Blazor | 0.8.0.0 |
Programmer's documentation | Implementation with DocxFx | 0.9.0.0 |
Docker/Containers | TODO | 1.0.0.0 |
Messaging management | Implementation with MassTransit | 1.1.0.0 |
Multitenant support (extra feature) | Implementation with Finbuckle.MultiTenant | 2.0.0.0 |
Semantic versioning is used