This is a part of a series of blog posts about design and architecture patterns.
Disclaimer!
This blog post will only cover the theory of what every software architecture needs to provide in order to be considered "useful" and reliable. More practical approaches will be shown in the next blog posts.
Introduction
Let us start with a simple premise that software solutions usually obey:
Building scalable and reliable solutions requires an architecture that solves all cross-cutting issues that can be modified or extended without affecting the underlying business logic.
My experience showed me that every solution needs at least 10 different architecture patterns (or aspects) in order to be complete:
- Authentication and authorization
- Dependency injection
- Localization
- Logging
- Auditing
- Caching
- Storage
- Error handling
- Distributed processing
- Recurring tasks / Jobs
We won't go into detail about what each of these aspects means, instead, we will focus on how to create the architecture with them in mind and not affect the business logic that uses the underlying implementation.
Architecture requirements
There are 3 different types of aspects that we can observe from the list above: implicit, explicit, and hybrid. These types of aspects have different lifetimes and because of that, they have different implementation requirements.
Implicit aspects are the ones that exist with every handler execution and we don't need to call or use them explicitly. For example, if a call fails we retry it automatically up to 3 times with a 10-second delay.
Implicit aspects are:
- Error handling
- Recurring tasks / Jobs
Explicit aspects are the ones that we must use explicitly and they don't execute any underlying logic on their own. For example, we need to cache data that a complex query returns to speed up the response time.
Explicit aspects are:
- Authentication and authorization
- Dependency injection
- Caching
- Storage
- Distributed processing
Hybrid aspects are the ones that we can use explicitly, but they are part of the implicit aspect stack as well and they do stuff on their own. For example, each time an error occurs we can log that error automatically, but we can also use the logger to log custom messages.
Hybrid aspects are:
- Auditing
- Logging
- Localization
Let us review a couple of things first:
What is a handler?
A handler is a piece of code that handles a business logic request. In service pattern a service method is a handler, in CQRS both a comment and a query have a handler, and so on...
What is the difference between a handler and a web controller action?
Controller action handles a web request by converting all web-specific parts of a request (like URL params, query params, body, cookies, files, session values...) to a business logic request or a data transfer object (a DTO) which is a plain-old-C#-object (a POCO) that is not bound to an application type, meaning it can be executed in a web app context, console app context or any other kind of app.
Plug-and-play design pattern
The plug-and-play design pattern usually describes a solution that is capable to modify its execution pipeline based on the plugins that are available. This is achieved by adding, removing, or hot-swapping plugins in runtime and a detection engine that loads the available plugins and places them as part of the execution pipeline.
This approach is usually adopted by CMS and CRM solutions.
Our goal is a little bit different, we will focus on modifying the pipeline at runtime by configuration, but also swapping (NOT hot-swapping) aspect implementations. It is not our goal to detect new pipeline plugins in runtime and add them to the pipeline.
Adapter design pattern
The adapter pattern is a software design pattern (also known as wrapper, an alternative naming shared with the decorator pattern) that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
We will use this adapter pattern to provide multiple aspect implementations that we can hot-swap as needed.
Architecture introduction
Aspect implementation
The implementation of the aspects and swapping which implementation we are using on startup. In order to achieve this, we will heavily use the adapter pattern.
Execution pipeline
The pipeline that every request goes through in which the implicit and hybrid aspects live. We will need a custom pipeline implementation in order to easily add new aspects without breaking the old ones and use the aspects at the appropriate time.
Handler context
The context in which the explicit and hybrid aspects live. We will need a custom context that will provide all available aspects and additional information that a handler can use.
In the next blog post we will create a simple AspNet project with .NET 5.0 using the CQRS architecture pattern on top of which we will build the plug-and-play architecture.
Happy coding,
DotNetGuru
Congratulations @dotnetguru! You have completed the following achievement on the Hive blockchain and have been rewarded with new badge(s):
Your next target is to reach 50 upvotes.
You can view your badges on your board and compare yourself to others in the Ranking
If you no longer want to receive notifications, reply to this comment with the word
STOP
Check out the last post from @hivebuzz:
Support the HiveBuzz project. Vote for our proposal!