Building microservices through Event Driven Architecture part4: repositories
This tutorial is the 4th part of a series : Building microservices through Event Driven Architecture
During this journey, I will implement the command side of Repositories. Repositories belong to Interface Adapters of Uncle Bob Clean Architecture
In this step, I will start implementing the command side of the Infrastructure, I will focus on how to persist data.
And I will use EntityFrameworkCore and SQL Server.
Because LogCorner.EduSync.Speech.Application.UseCases use a ISpeechRepository as dependency, I think that the most obvious way is to start by implementing ISpeechRepository , and to continue by implementing its dependencies, etc… But since I know where I am going, I will proceed as follows:
- UnitOfWork : I will use UnitOfWork pattern, it will help me to treat AggregateRoot as a unit for the purpose of data changes.
- Repository : I will use repository pattern, AggregateRoots are the only objects my code loads from the repository
Let us create a Generic Repository IRepository and apply an AggregateRoot contraints.
My IRepository will look like this :
T is an AggregateRoot and TIdentifier is the type of AggregateRoot identifier : int, Guid, etc…
ISpeechRepository implements IRepository<Speech, Guid> where Speech is an AggregateRoot and Guid is the type of Speech.Id
It is not possible to create a Repository for an non AggregateRoot Entity : MediaFile for example
IUnitOfWork.Commit persist (save or update) the entire Aggregate (AggregateRoot and related Entities)
let us start by testing IUnitOfWork, it will result in the implementation of UnitOfWork. The latter will need a class that inherits from DbContext. (DataBaseContext in my case)
TEST 1 : When saving IUnitOfWork.Commit Should Save Aggregate Root and DbContext.SaveChanges called only once
Implementation of UnitOfWork
Let us create a DataBaseContext class that inherits from DbContext
The final implementation of UnitOfWork first test
TEST 2 : When disposing unitOfWork.Dispose should be called only once
Let us implement UnitOfWork.Dispose()
At this stage, the solution compiles, all the tests pass and the code coverage of LogCorner.EduSync.Speech.Infrastructure is 100%
TEST 3 : Verify that CreateAsync can be called on Repository and should trigger dbset.AddAsync
Repository can only be instanciated with an AggregateRoot, so let us create an class that inherits from AggregateRoot<Guid> for testing purpose.
The final implementation of Repository will look like this
TEST 4 : Verify that CreateAsync can be called on SpeechRepository and fire Repository.CreateAsync only once
the goal of this test is to implement SpeechRepository, so I verify that when SpeechRepository.CreateAsync is called then Repository.CreateAsync is called only once
Here is the final implementation of SpeechRepository
The implementation of this section differ according to the ORM used (EF, NHibernate, or others). For example we can create classes specific to the repository (SpeechDao) and apply a mapping between SpeechDao and Speech.
SpeechDao can be seen as a duplicate class (properties ) of the Speech class of the domain.
But knowing that EF allows me to do more simple, without creating repository specific classes and then apply mapping between duplicate classes, by providing an IEntityTypeConfiguration interface.
I can use it to point domain objects to database tables without applying additional mapping between SpeechDao and Speech like this :
we can note here, how ValueObjects are managed
The last thing you need to know is that EFCore needs a parameterless constructor because it uses reflection to do its thing.
If you do not want to update domain classes and introdues private parameterless contructors, you should create repository specific classes and then apply mapping between duplicate classes. And this repository specific classes should have parameterless contructors.
I design the sql server database using SSDT, it will help on my devops pipeline
Configure LogCorner.EduSync.Speech.Presentation to target the sql database
And finally, finish the configuration of the dependency injection
The entire application can now be tested using postman (use the RegisterSpeech or develop branch)
Code source is available here : RegisterSpeechRepository