Creating an Application from Scratch using .NET Core and Angular — Part 3
In this article we are going to see how to create the Interfaces and the Services, which will be responsible for the business rule of our application, in the Domain layer, and we also are going to create the Repositories using the Repository pattern in the Infrastructure layer to access and execute the operations in the database.
The Repository Pattern
This pattern it’s used in a lot of projects and brings a lot of benefits. Some advantages of this pattern are:
- Allows the isolation of the business layer (the domain layer) of the database layer (the infrastructure layer).
- Allows the change of a database in the future (if necessary) without having much impact in the system
- Allow loosely coupled between classes
- You have all the code for access the database in one place
- Make easy to implement unit test
- Provide a flexible architecture
- Easy to maintain
Here is a definition of a repository from the Microsoft documentation:
Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.
Using this pattern, we are going to have a generic class for the Repository with the basic operations (Add, Get, GetById, Remove, Update, Search, Save), and we are going to have specific classes for specific actions that we need, that goes beyond the basic operations. So let’s start creating the interfaces.
Domain Layer
In this layer, we will have the Interfaces and the Services. We are going to use the Interfaces to work with Dependency injection, that is the letter D of the SOLID principles, that is Dependency Inversion Principle (also known as IoC — Inversion of Control). I will not explain about SOLID for now because it’s not the focus of this article, but it’s good to understand what says this principle:
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
It means that we should not depend on an implementation (a concrete class) but we must depend on an abstraction (this can be an interface or any other way of abstraction). Doing that, if something change in the class, the other classes will not be affected because it depends on an abstraction.
The Services classes are where we are going to add the business rules of the application. Those classes are between the Controller and the Repository. The application layer should not have access to the infrastructure layer, so we are going to use the services classes in the domain layer as an intermediate between the controllers in the application layer and the repositories in the infrastructure layer. Also, we should not add business rules in the controller, so the controllers will call the services where we will concentrate the business rules, and the services will call the repository.
Interfaces
We need to create a folder for the Interfaces, and create all the interfaces that we are going to use:
Generic Interface
Let’s start implementing the generic interface IRepository. This interface it’s for the generic Repository class that we are going to create in the Infrastructure layer. For this class we need the signature for the CRUD (Create, Read, Update, Delete) operation, and also a method for saving the changes and for search:
In the Repository class that we are going to create in the Infrastructure project, we are going to implement this interface. You can see that in this class we have all the basic operations:
The Add method it’s for adding a new entity.
The GetAll method it’s to get all records for the entity.
The GetById method it’s to get an entity by it’s Id.
The Update method it’s to update an entity.
The Remove method it’s to delete an entity.
The Search method it’s for searching an entity passing a lambda expression to search any entity with any parameter. This predicate it’s an expression, so you can use a lambda expression to filter objects, it’s exactly the same way we use the where with linq.
The SaveChanges it’s for save the changes of the entity. It will return an int, that will be the number of lines that were affected by the save action.
The class that will implement this interface, also need to implement the interface IDisposible for release memory.
Specific interfaces
For the BookRepository class (that we are going to implement in the Infrastructure layer), we are not going to use the GetById and the GetAll from the generic class, because we want to return the category of the book in those methods. If we use the methods from the generic Repository class, it will not bring the name of the category, so we will have a specific GetAll and GetById for this class. We will also have a method that will allow returning the books filtered by a category. So the interface IBookRepository will inherit from IRepository<Book>, and we will have those specific methods:
- GetAll — this method we are going to override because we need to add the category name in the result
- GetById — this method will also be overridden to allow returning the category name
- GetBooksByCategory — this method will receive the id of a category as a parameter, and will return all books with this category id
We need the ‘new’ in the line 9 and 10, because those 2 methods will be overridden in the BookRepository class.
In the interface ICategoryRepository we do not need any specific methods, so we will just create the interface that inherits from IRepository:
In the IBookService we have the signature of the methods about the CRUD operations and also the GetBooksByCategory, which will receive a category id as a parameter and will return a list of books that have that category id:
In the ICategoryService we also have the signature of the methods about the CRUD operations:
For both classes, the method GetAll will return a list of object. The Add, the Update and the GetById methods will return the object, and the remove method will return the number of lines that were affected.
Note that for those two interfaces we also need to implement the Disposable, and the reason for that it’s because this is used for memory release.
Services
Now let’s create the ‘Services’ folder, and create the CategoryService and the BookService classes. On the Services class is where we are going to add the business rules:
For Category it is only possible to add a category if the informed name does not exist in the database yet. And it is only possible to remove a category if there are no books using the category.
This is the CategoryService class, that implements the interface ICategoryService:
Note that we are using Dependency Injection to pass the ICategoryRepository in the constructor of our class. This way the class do not depends on a concrete class, but depends on an abstraction (the interface).
In the BookService, we will implement the interface IBookService. For this class will also have the rule that will not allow to register two books with the same name. This is the BookService.cs:
Before we call the Add or Update method, we need to validate if the name is already being used. So first we will search if already exists a book with the informed name, and if does not exist, then we can call the Add or Update.
Infrastructure Layer
Let’s create now the ‘Repositories’ folder, and create the Repository, BookRepository and CategoryRepository classes:
Generic Repository
Let’s start creating the generic Repository class:
This is an abstract class, which means that this class cannot be instantiated, it can only be inherited. All specific repositories classes that we are going to create, will inherit from this main class. On this class, we need to implemented the methods from the Interface IRepository.
The Db property is protected because all classes that will inheritance from Repository, can access the Db property.
The property DbSet is used as a shortcut to execute the operations in the database.
There some methods that are virtual, and the reason for that it’s because we want to allow to do an override in other specific repository class if necessary. This is from the Microsoft official documentation:
The
virtual
keyword is used to modify a method, property, indexer, or event declaration and allow for it to be overridden in a derived class.
We implement the Dispose method because this is used to release memory in our application:
Implementing the Dispose method is primarily for releasing unmanaged resources used by your code.
In the Search method, we are using the AsNoTracking() because when we add something in the memory, the object will be tracking, so in this case that we are only reading something in the database, we can use AsNoTracking to increase performance in our application. We can see this information in the official Microsoft documentation:
No tracking queries are useful when the results are used in a read-only scenario. They’re quicker to execute because there’s no need to set up the change tracking information. If you don’t need to update the entities retrieved from the database, then a no-tracking query should be used.
Specific Repositories
We also need to create the specifics repositories classes for the CategoryRepository and the BookRepository. In the CategoryRepository class we will inherit from Repository<Category>:
In the BookRepository class we will inherit from Repository<Book> and implement the interface IBookRepository:
Now we can commit the code in GitHub. In the next part, we are going to create the Controllers in the API layer and see how we can document the API using Swagger. We also are going to using AutoMapper to mapping our entities to Dtos objects. You can access the part 4 clicking here.
This is the link for the project in GitHub:
https://github.com/henriquesd/BookStore
If you like this project, I kindly ask you to give a ⭐️ in the repository.
Thanks for reading!
References
Microsoft documentation — The Repository Pattern
Microsoft documentation — Design the infrastructure persistence layer
Microsoft documentation — Virtual
Microsoft documentation — Implement a Dispose method
Microsoft documentation — Tracking vs. No-Tracking Queries
Desenvolvedor.IO — Dominando o ASP.NET MVC Core
Repository Pattern with C# and Entity Framework — Programming with Mosh