Basic SOLID Principles to Advanced DI and GraphQL in .Net World
This is a summary from our remarkable .Net Summer meetup held at Seven Peaks office. On that day, our experts introduced audiences to the world of .Net, starting from the basic SOLID principles in .Net and knowledge that .Net developers should have, then stepping into the most common cases of Dependency Injections, and finally using GraphQL API in .Net to share data across multiple platforms.
To begin, Giorgio Desideri, our humourous Tech Lead Cloud Solutions, will educate you on SOLID principles in .Net – one of the most iconic sets of principles ever created in the history of Software Engineering.
SOLID Principles/Patterns in .Net
The SOLID principles for using C# and .Net is not “a new thing” in software development, but many developers still don’t know about it even though it has been around for more than 40 years. Herein, Giorgio will bring it up to remind all of the developers again about this concept in order that they can avoid unsuccessful application development.
SOLID is an acronym for Single Responsibility Principle (SRP), Open closed Principle (OSP), Liskov substitution Principle (LSP), Interface Segregation Principle (ISP), and Dependency Inversion Principle (DIP).
It is the pattern built for a “good quality” development in which all concepts are interconnected. When utilizing one concept, all the others must follow suit.
S – Single Responsibility Principle (SRP)
“A class should have one and only one reason for the change” – Martin, Robert C.
It is well-known as “every module, class, function should have a responsibility over a single part of the software functionality.” This means that each class or similar structure in your code should serve a single purpose, and should have all of its members related to that purpose. If you need to change one of them, you have to change the whole tool.
O – Open Closed Principle (OCP)
“software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”
It means when you write a code, a class, a function, a module, or whatever, it should be adaptable enough to allow for future expansion but it must be secure enough to prevent modification.
L – Liskov Substitution Principle (LSP)
In 1988, Barbara Liskov introduced this concept:
“Let ϕ ( x ) be a property provable about objects x of type T.
Then ϕ ( y ) should be true for objects y of type S where S is a subtype of T.”
This principle is just an extension of the Open-Closed Principle. In practical software development, this statement says that any derived class should work in place of a parent class and act the same way without any changes.
I – Interface Segregation Principle (ISP)
“No code should be forced to depend on methods it does not use”
This claim makes the following assertion: For an interface to be useful, it should be closely linked to the code that uses it rather than the code that implements it. Rather than defining the methods based on the implementation of the class, the methods on the interface are determined by what the client code requires.
D – Dependency Inversion Principle (DIP)
“High-level modules should not import anything from low-level modules. Both should depend on abstractions.”
“Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.”
In other words, high-level modules provide complex logic in a system (application), while lower-level modules provide utility features. It is important for high-level modules to be easily reusable and immune to the effects of changes made to lower-level modules.
After getting to know SOLID principles in .Net, our Senior Backend Developer, Massimo Dradi, will save you “from a common disease” of an application’s code when its components are overly coupled. He will attempt to clarify the various Dependency Injection concepts and introduce you to the support provided by .NET Core, one of the most well-known techniques for creating more maintainable code.
What is the dependency injection?
Dependency Injection (DI) is a design pattern used to implement Inversion of Control(IoC) and Dependency Inversion principles.
The Dependency Injection pattern involves 3 types of classes:
- Client Class
The client class (dependent class) is a class which depends on the service class
- Service Class
The service class (dependency) is a class that provides service to the client class.
- Injector Class
The injector class injects the service class object into the client class.
Types of dependency injection
- Constructor Injection
The injector supplies the service (dependency) through the client class constructor.
Most widely used.
- Property Injection(aka the Setter Injection)
The injector supplies the dependency through a public property of the client class.
- Method Injection
The client class implements an interface which declares the method(s) to supply the dependency and the injector uses this interface to supply the dependency to the client class.
Why dependency injection?
We are going to talk about the Constructor Injection. So to get the client class ready for the Constructor Injection, we need to move from some implementation like in the left red box exemplified below where we knew the dependency inside the constructor to something like in the right green box where we receive the dependency directly as a parameter in the constructor.
With this simple changes, we get the big list of the advantages in using dependency injection:
- Promotes loose coupling of components
We can promote loose coupling of components because at this point we are not coupling anymore the implementation of this class to the product service class. But instead, any class that implements _productRepository, its interface will be passed and it will work fine.
- Support unit testing
Using dependency injection is the only way to support proper unit testing because if we don’t inject the repositories in this case, then we don’t have the way to mark it later. Unless we start to use some strange kind of interceptors and it’s going to be very complicated.
- Cleaner, more readable code
We can keep our code cleaner and more readable
- Obey to the Dependency inversion principle (D of the SOLID principles)
The high level modules should not depend on low level modules. Both of them should depend on abstractions, which in this case is the interface.
- Obey Inversion of Control (IoC) principle
We should transfer the creation of objects outside of the dependant class to an external class which is a container or a framework called IoC container.
Tips: What is an IoC Container?
IoC Container (a.k.a. DI Container) is a framework for implementing automatic dependency injection.
IoC Container provides easy support for the following DI lifecycle:
How to use constructor DI in .NET
The ASP.NET Core itself provides a basic built-in IoC container that is represented by IServiceProvider and IServiceCollection interfaces. It supports constructor dependency injection by default.
Then you can simply register a service,
- Service lifetime is transient.
- IProductService is the service type to resolve.
- ProductService is the implementation to provide.
Later on, resolve a service
- A parameter of the registered service type is added in the dependant class constructor.
- When the dependant class (ProductsController) is instantiated the DI container will inject an instance of the dependency (ProductsService).
Before going into the concept of Service Lifetime, it’s useful to understand what the service scope is.
Service scopes are short lived child DI containers. So everytime you create a new scope, you are basically creating a new DI container that will live for a shorter time than the Root scope. And in order to create a new scope, you have two ways to go.
One is manually using the CreateScope method exposed by the IServiceProvider interface. The other one is just letting in the web application .Net to automatically create the scope for each request that it receives.
Services Lifetime is the way to control how long the service should live before it gets disposed of and there are three ways to register a service: transient, singleton, and scoped. You are responsible for selecting the ideal lifespan for your services.
A new service instance is created each time a service is requested. If the service is disposable it will be disposed when the service scope is disposed.
Use Transient lifetime if your services…
- Has a non-shared state for the execution context.
- Used by multiple threads concurrently and it is not thread safe.
- Has a transient dependency, such as HttpClient, that has a short intended lifetime.
Only one instance of the service is created. Singleton services will not be disposed until the root scope is disposed of which usually occurs when the application exits.
Use Singleton lifetime if your services…
- Has a shared state such as cache service.
- It is stateless.
A new instance of a service is created in each scope. It will act as if it is a singleton within that scope. If the service is disposable it will be disposed when the service scope is disposed.
Use Scoped lifetime if your services…
- You want it to act as a singleton within the scope of the request.
- Database and repository services are often registered as scoped services.
- Default registration of DbContext in EntityFramework Core is also scoped.
Now we know about SOLID principles in .Net and Dependency Injection. Lastly, please have fun with Nicolas Pierson, who will talk about the basics of GraphQL in .Net – a query language for your API and a server-side runtime for executing queries using a type system you define for your data. It is an alternative to rest apis, so let’s explore GraphQL!
What is GraphQL?
“GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.”
GraphQL makes it simpler to evolve APIs over time, offers clients the power to request only the information they require, and enables strong developer tools. It also provides a thorough and comprehensible description of the data in your API.
GraphQL Type System
It’s like a system with queries. Then we basically need to define some types or some objects with entities where we will be able to run queries. These are the objects you can find in GraphQL:
- Object types – You can use it to define a type that you want to manage, get in return, or modify.
- Interfaces – it is a kind of abstraction that you have in any object-oriented language where you can abstract and reuse.
- Unions – it is like the intersection between two objects that you want to manipulate.
- Enumerations – this is the same as you can have on an API where you define each possible value and where you expect defined values in the list.
- Lists – Above are the defined objects but what you have inside, we still need Scalars which is kind of a primitive in Java or any other languages. Consequently, you will still discover the following:
- Custom scalars e.g. Date
GraphQL comes with these most used operations:
- Queries (read data)
- Mutation (write data)
- Subscription (listen for data)
List of challenges we are trying to solve
- Data requirements vary across devices and become harder when multiple users share data.
- Users want instant access to data.
- Users want to continue using their apps even with low or no connectivity.
- Building scalable data-driven apps without learning distributed systems concepts is hard.
A quick comparison between GraphQL and REST
|Real time applications||Non-Interactive (System to System)|
|Complex object hierarchy||Simple Object Hierarchy|
|Complex query||Repeated Simple queries|
|Complicated to develop||Easier to develop|
|Easier to consume by clients||More complex to consume by clients|
For GraphQL, the flexibility on how you can query and manipulate is more dynamic than a REST API. So you can have fun and play with it and just tweak the objects. Additionally, you can think of applications over a web socket that you would normally not do with other protocols.
Furthermore, it is easier to consume by the client because when you define the types, you are defining them with Scalars so when you are on your explorer or when you are on a mobile app, you can definitely define what the types are. As a result when someone consumes a GraphQL API, there will be an error straight away that says this type doesn’t match. Thus it makes the life of the client a little bit easier by the generation of the documentation so you can play with it.
However, the REST API is not dead. It is still easier to develop but a bit more complicated for the integration because we still need to generate documentation. They will retrieve all data even if they don’t want to.
In the video that follows, we discuss further obstacles and how to overcome them. You may benefit from hearing the above detailed content, beginning with SOLID principles in. Net and continuing through Dependency Injection and GraphQL, directly from the experts themselves.
Sign up today & start getting tech news.
Get the latest tech trends directly in your inbox each month. And get invited to exclusive events.