Fantastic Book Manager

Implementation of the user story following the requirements:

  • .NET C#
  • Web API
  • Clean Architecture
  • Test-Driven Development (TDD) methodologies
  • Without using the entity framework
  • Create a user, login as the user, and ensure that the user information is stored in the database
  • Data layer & Business logic layer

Summary

User Story

"As a user, I want to be able to manage a collection of books, including adding new books, updating book details, removing books, and viewing a list of all my books. Additionally, I want to have the ability to create an account, log in, and ensure that my book collection is private to me."

Book Management

Users can perform CRUD operations (Create, Read, Update, Delete) on a collection of books.

User Authentication

Users can create an account, which includes user registration with email and password.

Users can log in to access their book collection.

User authentication ensures that only authorized users can access and modify their book collection.

Privacy and Authorization

The user story implies that each user's book collection is private to them, meaning users can only see and modify their own books.

Authorization checks should be in place to ensure that users can only manipulate their own data.

List of Books

Users should be able to view a list of all their books, with book title, author, and genre.

Technologies

Getting Started

Fantastic Book Manager uses docker, so after cloning the repository just go to the src folder and run the command docker-compose up --build, then you will be able to access:

Identity API - https://localhost:14006/swagger
Book API - https://localhost:13006/swagger

You can also open the BookManager.Services.sln solution using Visual Studio, if the docker-compose project is not your "by default startup project", right click on the docker-compose node and select the "Set as Startup Project" menu option, then you will be able to build and run the whole solution into Docker by simple hitting F5.

Taken decisions

Entity Framework

One of the requirements was to not use the Entity Framework, so Dapper was used, which has performance as one of its main features and allows us to write queries in raw SQL, but some tool would still be needed to manage changes to the database, so DbUp was chosen, allowing us to write changes to the database in SQL Scripts and DbUp tracks which SQL scripts have already been executed and executes the change scripts necessary to update our database.

User management / Security

As requested, some form of user authentication was necessary, for this we could follow three different ways:

  1. Use an external authentication tool such as Firebase Authentication
  2. Writing this entire user creation and authentication part manually
  3. Use a well-known tool that has login functionality

Alternative (1) is a good one, but there is a requirement regarding user information being saved in database, so it could be seen as a requirement violation.

Alternative (2) would be like trying to reinvent the wheel and we would certainly run into problems such as:

  • Time Constraints: Creating our own authentication system from scratch is a time-consuming process
  • Expertise: Familiarity with authentication and security best practices is important
  • Scalability and maintenance: Long-term maintenance and scalability of our application
  • Security: Creating our own system would require a deep understanding of security principles to ensure our system is as secure as possible.

Alternative (3) which was chosen because we understood that it would be the best for this scenario, using a tool already known and widely used by the community and which already provides us with resources such as login functionality, user management, passwords, profile data, claims, tokens, email confirmation, and more.

Using NetDevPack.Identity

To implement the Identity API I chose to use the NetDevPack.Identity which is a library that already adds several basic implementations of ASP.NET Identity such as JWT, Claims, Validation and other facilities, this helped save time and make the Identity API extremely simple.

Separation of Book and Identity API

The Book API and the Identity API were separated because thinking about the design of a distributed system or even an application that goes to production, it makes sense to have an application responsible for managing the entire login and access part, imagining the possibility of this growing and have to take care of claims, roles, user management, etc.

Command–query separation (CQS)

The controller-service-repository pattern is widely used and well known, in Clean Architecture we could use this pattern without any problems and the service would be responsible for the Business Logic Layer, however we chose to work with commands, this way we have the Use Cases much more obvious: CreateBookCommand, DeleteBookCommand.

Adopting the CQS (command-query separation) principle also makes it much easier to start working with events, which can be extremely useful in scale scenarios and distributed systems.

By separating the commands from the queries we have very well defined rules where it is clear that the commands will make changes to our database and that the queries will only read information without causing any changes, this allows a much more smoodie adoption of the CQRS, allowing queries to search for information from another database, which can be extremely useful in a scale scenario.

Validations

The commands are clear intentions to change our system that receive information directly from our users, bringing the possibility of containing invalid information, for this reason each command has a validation class that follows the CommandNameValidator pattern (CreateBookCommandValidator), the validation classes are responsible for validate inputed data which are simpler validations and we are using FluentValidation for this.

Domain-driven design (DDD) brings some interesting patterns, one of them is that our business entities must always be valid, this basically means that any change that occurs making our business model invalid must be blocked, so even not using DDD we are following this pattern to ensure that there is no change that makes our business model invalid, to do that we adopted GuardClauses which checks for invalid inputs up front and immediately failing if any are found.

Error handling

Unwanted behavior can occur in our application at any time, such as the user trying to enter incorrect data or trying to perform an invalid operation. This means that our application has to validate these behaviors as described in the validations topic, but there is an important point: If this validation is not successful, how can I return this information to the user? We adopted IErrorHandler, which is a service responsible for receiving all operation errors. It is a common interface for our application layer and also for the presentation layer, allowing us to send a command and then verify the success of this operation through it.

Unit tests

As described in the architecture overview part, we have 4 layers and we are performing unit tests for all of them.

Domain

Business entities are responsible for changing their own information, these changes are being tested using unit tests.

Application

Using unit tests to test command validations, ensuring that they are validating the command correctly, in addition to properly testing the command handler, ensuring that everything is working as it should in addition to serving as documentation for the code.

Infrastructure

The infrastructure layer is basically responsible for accessing the data (in our current scenario), we also have unit tests for each method that accesses the database, this ensures that the SQL database queries are working as they should, so the SQL database query is really hitting the database, however we are using the concept of transaction, this means that all data written during the tests is removed at the end.

Presentation

The presentation layer, which in our case has our REST API, is very simple, but the controller methods are responsible for calling correct commands and verifying the success of the operation, to guarantee that we are also using unit tests.

Architecture

We adopted unit tests in all layers of our application, in addition, written using NetArchTest.Rules to enforce architectural rules in unit tests.

Architecture overview

The Book API was developed using clean architecture principles.

Clean Architecture

Domain Layer

This layer is represented in the image as Entities, in the system we call it Book.Domain, it will contain our business entities and other items that are the core of the system such as enums, exceptions, types and other items specific to the domain layer.

Application Layer

This layer is represented in the image as Use cases, in the system we call it Book.Application, it will contain the behaviors of our system, there we can find our commands that are ways to well describe these behaviors. It is dependent on the domain layer, but has no dependencies on any other layer or project. This layer defines interfaces that are implemented by outside layers. For example, if the application needs to access the database, a new interface would be added to application and an implementation would be created within infrastructure.

Infrastructure Layer

In the system we call it Book.Infrastructure, it responsible for accessing external resources such as file systems, data stores, and so on. This layer contains details, concrete implementations for repositories. The decoupling between the application layer and the infrastructure layer is what allows solution structures to change and/or add specific implementations as the project requirements change.

Presentation Layer

In the system we call it Book.API, this layer essentially contains the I/O components of the system could be a mobile application, a web application, in our case it is a REST API. This layer depends on both the Application and Infrastructure layers, however, the dependency on Infrastructure is only to support dependency injection. Therefore only Program.cs should reference Infrastructure.