How many times have you come across terms like: inversion of control (IoC), dependency inversion (DI - DIv), dependency injection (DI - DIj) and abstraction, a lot of times, right?
And I am sure that many times you have been practicing them without understanding in depth what is all that, so today is our day and I promise you that we are going to learn these terms in an easy way in a non-technical language for you to appropriate these concepts.
Well but to address these issues we must start at the origin of everything, the software architecture.
At the beginning when software development started everything was darkness, as something new it is normal that as the techniques and complexity of programming and algorithms advanced, problems of construction, scalability, maintenance began to be found, and back in the 60s concepts such as modularity, inheritance, encapsulation began to be touched, but it was finally in the 90s where the term architecture was used in contrast to the word design, evoking notions of abstraction and standards, then the light at the end of the tunnel began to be created.
What is software architecture?
We can define it in one word, decisions, and they are the design decisions that are made to meet the quality attributes when we have a problem to solve through software solutions, and these decisions, good or bad, model the structure at the level macro of the parts of that system and that modeling is done through software architecture patterns.
What is the patterns?
There are two types of patterns in the software world, software architecture patterns and software design patterns.
What are software architecture patterns?
They are archetypes (structures) that tell us how the parts of a software system relate/interact at the macro level, and some of there are:
Client - server, Microkernel, Dashboard, Peer to peer (P2P), Events broker pattern (Agent), Events bus pattern, Model view controller (MVC), N-Tires, Multilayer, Microservices, and others.
I leave you the task to read about them.
What are software design patterns?
They are base templates that have already been tested to solve common problems encountered in the software development process. They are classified into three groups (creative, structural, behavioral) and there are many, but here are just a few.
The creative group models the creation of concrete and abstract objects.
In the structural group, objects are modeled to form larger structures and complement their functionality.
And the behavioral group models how to interact and responsibilities in groups of objects.
Factory, Abstract factory, Strategy, Façade, Composite, Builder, Singleton, Data transfer object (DTO) and many others.
I leave you the task to read about them.
To learn about patterns, you ca go to:
Patterns
Now, what is abstraction?
In simple words abstraction can be defined as the separation of what and how, that is, separating what something does from how it does it.
So, the what is the action (Interface, Abstract class), this action receives or not input parameters and gives us a result.
The how is what is done (behavior) with those parameters and its internal objects to return the result, that is the concrete Class or implementation.
// Artifact in Application Layer
// This is the what, the compromise or contract
interface ILogProvider {
logEvent(event: EventLog): Promise<void>;
logError(error: ErrorLog): Promise<void>;
}
// Or like abstract class
abstract class LogProviderContract {
abstract logEvent(event: EventLog): Promise<void>;
abstract logError(error: ErrorLog): Promise<void>;
}
// Artifact in Adapters Layer
// And this is the how, the concrete Class or implementation
class LogProvider implements ILogProvider {
constructor(private readonly logger: Logger) {}
async logEvent(event: EventLog): Promise<void> {
return this.logger.info(event);
}
async logError(error: ErrorLog): Promise<void> {
return this.logger.error(error);
}
}
The architecture of ports and adapters is based on this concept and what we must be clear is that a port is an interface or contract which must be in the application layer while the adapter is the concrete class that implements that interface/contract (secondary adapter in this case*), and it is also good to know that there are two types of adapters, the primary or driving adapters which are entry points to your application, for example a controller, and the secondary or driven adapters that must implement a port (interface - contract) and are injected to the application layer to perform functions such as access to data and services.
So, having understood what abstraction is, we can then review the following concepts.
What is the inversion of control (IoC)?
Basically it is to delegate, to delegate control of certain aspects of an application to a third party.
This third party is usually a framework or service, and can control from the lifecycle of objects (DIj) to that of the application itself, it can also control events and invoke application actions to those events (UI), it can also control data persistence and service providers.
What is dependency inversion (DIv)?
DIv basically is abstraction and since we already know that it is abstraction, then it is time to talk about its main function, and this function is to decouple the layers of your software solution.
This is one of the SOLID (DIvP) principles and landing its compendiums to a non-technical language we can say that:
- Important classes should not depend on less important classes, both should depend on abstractions.
- The what should not depend on the how, the how should depend on the what.
What is dependency injection (DIj)?
It is about separating the construction/creation (instances) from the execution context where the object interacts.
It is to delegate the creation of objects to a third party, in this case to a dependency injection container (DIjC) with the intention of decoupling the core of our application from the outside world, that of "trivial" things.
Now, you can learn more about this topics in the next resource:
Dependency inversion and injection
Question?
Is posible to use Dependency Injection without using Dependency Inversion?
The short answer is a partial Yes, but it is not recommended because in strongly typed languages it will generate a coupling between objects of different layers and performing end to end tests in certain cases will be very complex, however, in languages like JavaScript this is very common and it is considered good practice because this language has no interfaces or abstract classes* (Specifications before ES6), and not having defined types, it is better to use an injected module through a method to import it directly into the dependent module, therefore the mock of that injected artifact will be easily emulated.
What is Clean Architecture (CA)?
It can be defined as a framework or set of best practices aimed at facilitating the construction, testability and maintenance of a software product.
The term was coined by Robert C. Martin and in this compendium he took part of other architectural concepts "Tiers and Layers, Onion, Hexagonal (Ports and Adapters)" that have as main objective the abstraction, structuring the code through layers and isolating the inner world (application and business logic) from "trivial" things like the outside world.
The entities are the domain or business logic, the use cases are the application logic that orchestrates the domain artifacts, the adapters are the entry and exit points of your application, and the external world infrastructure are the connections, services and frameworks that enable the entry and exit points to enter and send the data being processed.
In a software solution these layers look more or less like the directories in the previous image.
Clean Architecture basic rules
Coupling is only allowed between a layer and its inner neighboring layer, except for the domain layer, it can be everywhere and at best abstracted to layers more external to the adapters layer. *
An inner layer must not depend on (be coupled to) an outer layer.
* Part of this is negotiated with the technical team.
How to do Clean Architecture and not fail in the attempt?
At first doing CA can be very complicated if you don't have enough experience, but like many things in life if you have a map or a practical and illustrative guide the mission becomes much easier because it shows you step by step the details that you must take into account to achieve your purpose.
For this reason we have created a diagram and its equivalence in code to help you in the task of abstracting the layers of your software solutions.
Magical formula
In code, it looks something like this:
// Application layer
interface IChatProvider {
send(recipient: string, text: string): Promise<void>;
read(): Promise<Message[]>;
}
// Adapter layer
interface IChatClient {
send(message: Message): Promise<boolean>;
read(recipient: string): Promise<Message[]>;
}
class ChatProvider implements IChatProvider {
#address: string;
#chatClient: IChatClient;
#messages: Message[] = [];
async send(recipient: string, text: string): Promise<boolean> {
// implementation
}
async read(): Promise<Message[]> {
// implementation
}
}
// Infrastructure layer
class ChatConnection {
#connected: boolean = false;
#chatIO: ChatIO;
connect(host: string, port: number): boolean {
// implementation
}
disconnect(): boolean {
// implementation
}
}
class ChatClient implements IChatClient {
#connection: ChatConnection;
async send(message: Message): Promise<boolean> {
// implementation
}
async read(recipient: string): Promise<Message[]> {
// implementation
}
}
Well, this is all I have to share in this article and I hope I have contributed to your knowledge by explaining these concepts in simpler terms because handling them in such technical terms can be a bit complicated to understand these concepts.
If you find any inaccuracies I appreciate the feedback. :)
Now, the next step for you is learn about Clean Architecture in the next resource, good luck!!
Clean Architecture using NodeJs and TypeScript
Top comments (1)
If you need to design 3D software architecture diagram, you can try iCraft Editor : icraft.gantcloud.com/editor