The backend is an ASP.net core 2.2 application exposing a RESTful API and persisting data in a database.
There are a few concepts that have been put into place, which you should be aware of, before starting to code in this project.
The backend is structured according to onion architecture. There is the main ASP.net core application Sppd.TeamTuner
, which uses the functionality defined in Sppd.TeamTuner.Core
and implemented in Sppd.TeamTuner.Infrastructure.*
.
To register the implementations, all classes implementing IStartupRegistrator will be instantiated (they need a constructor without parameters), ConfigureServices
and Configure
will then be called from the respective method in Startup.
The ASP.net core application, which is the entry point, bootstrapping the application and exposing the RESTful API.
Every other assembly references this one. It defines the entities belonging to the application, along with interfaces for functionality like repositories or services. But it does not contain any implementations
All infrastructure projects contain implementations, which are being registered by the project itself.
Entity Framework implementation of the data access. SQL and Sqlite are supported. It can be configured in appsettings.json. Additionally, there is a InMemory implementation, which can be used for tests.
Imports cards from the Feinwaru API. Thanks a lot for letting me use the API!
Registers Hangfire and defines the jobs; currently only the job importing cards from Feinwaru.
Unit, Integration or API tests
- In the API layer (
Sppd.TeamTuner
), the controllers will always use services, but never repositories (even if it is technically possible). - In the Business layer (
Sppd.TeamTuner.Infrastructure.*
), the services will not use other services, which are using repositories, but use repositories instead. This is to avoid circular dependencies. - In the data access layer (
Sppd.TeamTuner.Infrastructure.DataAccess.*
), the repositories will never exposeIQueryable
, but always materialized lists or single objects. - A repository will never persist data, this must be handled in the business layer through a unit of work.
Let's take updating a user as an example.
- User changes some properties in the frontend and saves.
- Frontend calls the API with the updated data.
- The backend controller receives a DTO (Data Transfer Object) as method parameter.
public async Task<ActionResult<UserResponseDto>> UpdateUser([FromBody] UserUpdateRequestDto userRequestDto)
- The controller checks if the user is authorized to update the entity.
await AuthorizeAsync(AuthorizationConstants.Policies.CAN_UPDATE_USER, userRequestDto.Id);
- The DTO is being converted to an entity (using automapper).
_mapper.Map<TeamTunerUser>(userRequestDto);
- The controller calls a update method on a service.
var updatedUser = await _userService.UpdateAsync(user, userRequestDto.PropertiesToUpdate);
- The service calls an update method on the repository.
Repository.Update(storedEntity);
- The service calls SaveChanges on the unit of work.
await UnitOfWork.CommitAsync();
- The controller converts the updated user entity to a UserDto (using automapper) and returns it to the caller.
return Ok(_mapper.Map<UserResponseDto>(updatedUser));