This is a simple portfolio project that I develop in my spare time (and which is still ongoing) that aims to provide a REST API built in .NET, to manage music-related content, which exposes interactions between models representing artists, genres and musical productions, as well as their tracks.
The intention of this project is not to present an application rich in business rules, but to expose a series of good practices, conventions and patterns that I am aware of.
Before you start, keep in mind that this project was built using .NET 7 Standard Term Support, so its SDK and CLI are required to run this application through the .NET cross-platform development environment.
Clone the repository:
git clone https://github.com/rodolfo-brandao/music-library
Navigate to the /src
directory:
cd music-library/src
Restore, build and test the application:
dotnet restore \
&& dotnet build --no-restore \
&& dotnet test --no-build --verbosity normal
Since the main data schema of this API is relational, the SQLite database was chosen for this purpose.
Once the application setup is done, now it will be necessary to create its database. In practical terms, this will consist of a .db
file located in the MusicLibrary.Data
layer.
Note that this application was developed using the Code-First approach, and for that, we will need the EF Core Tools.
Install the latest version through the .NET CLI:
dotnet tool update --global dotnet-ef
Now let's create the migration containing all the DDL scope.
To do so, in the src
directory, simply run:
dotnet ef migrations add InitialCreate \
--project MusicLibrary.Data \
--startup-project MusicLibrary.Presentation
Through this command we are indicating to the
dotnet ef
tool to take the entity mapping configurations located inMusicLibray.Data/Mappings
and generate the migration files containing the DDL scheme.
After that, you will notice that a folder called Migrations
has been created inside MusicLibrary.Data
, it contains all the setup automatically generated by the dotnet ef
tool.
Now let's apply the defined migration. To do so, let's navigate to the src/MusicLibrary.Data
.
Create a new folder called "SQLite":
mkdir SQLite
Now we go back to the /src
directory
cd ..
And create the .db
file mentioned previously:
dotnet ef database update \
--project MusicLibrary.Data \
--startup-project MusicLibrary.Presentation
This command tells the
dotnet ef
tool to use the newly created migration and apply it to the database connection configured in theMusicLibrary.Presentation
layer.
After following all the steps, we can now run our project locally.
Still in the src
directory, run:
dotnet run --project MusicLibrary.Presentation/MusicLibrary.Presentation.csproj
With this, the application's Swagger will be available via HTTPS at localhost:7233
(here).
- .NET 7 Standard Term Support
- Domain-driven Design
- Authentication & JWT
- Mediator
- CQRS
- Repository
- Unit of Work
- Null Objects
- Swagger
- Code-First w/ Fluent API
- Unit & Integration Tests
The detailed list of all packages used in this project can be accessed here.
When it comes to the exposed layer of our application, which is called Presentation, the first thing we notice in this architecture is the fact that all HTTP requests are segregated into 2 categories:
- Commands
- Queries
Where,
- For requests that consist of commands, the insertion, update and deletion actions can be performed within the database
- As for the requests that deal with queries, only the reading action will be performed by the database
Bearing in mind that both actions can occur both in the SQLite and Redis database.
Moving on to the second component of our architecture, which communicates directly with the Presentation layer, we have a layer called Application.
This is responsible for applying the backend business definitions of our API through handlers that implement the Mediator pattern. Where, for this purpose, the validation of the data coming from the commands/queries is carried out.
In cases where these validations fail, generally speaking, the API will return responses containing the HTTP status code 400 Bad Request
along with the respective message of the cause of the failure.
For successful cases, the API will return responses containing the appropriate HTTP status code, within the range 200–299, which may also have the JSON object of the respective resource.
From this moment on, we will be talking about the layer responsible for data communication and management, being directly connected to SQLite and Redis databases. This layer is simply named Data.
In practical terms, we will find here the concrete implementations of the components responsible for establishing the connection with the data source, writing and reading in the databases, committing the actions performed in them and also managing the application's security features. Such components are:
- Entity repositories (for commands/queries actions)
- Unit of Work (to commit command actions)
- Security Service (to manage users and access tokens)
These are used within the handlers contained in the Application layer to enable business rules, thus resulting in a dependency on this layer.
Finally, consisting of the most "deep" part of our API, we have the so-called Core layer. Where here we define our domain from the entities conceived through the need of the project.
Such entities are:
- Artist
- Genre (musical)
- Production
- Track
- User
Where, for each one, we have their null state representations:
- NullArtist
- NullGenre
- NullProcution
- NullTrack
- NullUser
In addition to specifying the contracts of the concrete components of the Data layer. These being:
- IRepository{T} (base implementation)
- IArtistRepository
- IGenreRepository
- IProductionRepository
- ITrackRepository
- IUnitOfWork
- ISecurityService
This project's CI pipeline triggers SonarCloud analytics for every push or pull request created in the main
and develop
branches.
Such analysis brings surveys on bugs, code smells, vulnerabilities, security hotspots, code coverage and duplicate lines of code.
To view it, go here.
The development and maintenance of the project contained in this repository has been my sole responsibility, Rodolfo Brandão, the owner.