- Reglas de negocio
- Casos de Uso / Funcionalidades
- Configuración del entorno
- Migraciones y persistencia
- Tests
- Endpoints disponibles
- Swagger y documentación
- Seed de datos
- Logs
- Eventos de Dominio
- Caching
- Arquitectura utilizada
- Estructura de capas
- Principios de diseño aplicados
- Patrones utilizados
- Angular Challenge
- Un Rental no puede ser creado si el auto está reservado en esas fechas.
- Un Rental no puede ser creado si el auto tiene servicios programados en esas fechas.
- Un Rental no puede ser cancelado si ya ha comenzado.
- Las fechas de un RentalPeriod deben cumplir que End > Start.
- El Customer y Car deben existir y ser válidos.
- Solo Rentals activos pueden ser modificados.
- Solo Rentals con fecha de inicio en el futuro pueden cancelarse.
-
Registrar alquiler: Crea un nuevo alquiler si el auto está libre (RegisterRentalCommand)
-
Modificar alquiler: Cambia fechas o auto (ModifyRentalCommand)
-
Cancelar alquiler: Solo si no ha iniciado aún (CancelRentalCommand)
-
Listar alquileres: Trae todos los rentals activos o existentes (GetAllRentalsQuery)
-
Registrar cliente: Permite registrar un nuevo cliente (RegisterCustomerCommand)
-
Listar clientes: Devuelve los clientes existentes (GetAllCustomersQuery)
-
Disponibilidad de autos: Chequea autos disponibles en cierto rango (CheckCarAvailabilityQuery)
-
Servicios próximos: Devuelve autos con servicios próximos (GetUpcomingCarServicesQuery)
-
Top más alquilados: Ranking de autos más alquilados (GetTopRentedCarsQuery)
-
Top por marca/tipo: Ranking agrupado (GetTopCarsByBrandModelTypeQuery)
-
Métricas diarias: Cantidades por día y por ubicación (GetDailyStatsQuery)
-
Login: Devuelve JWT si las credenciales son correctas (TokenCommand)
-
Me: Retorna info del usuario actual autenticado
-
Register: Crea un nuevo usuario y devuelve su token (RegisterCommand)
✅ RegisterRentalCommand fue testeado con más del 90% de cobertura (unit + integration).
Requisitos:
- .NET 9 SDK
- Visual Studio 2022+ o Rider (opcional)
Ejecutar:
cd src/PwcDotnet.WebAPI
dotnet restore
dotnet run
AzureDurableFunctions:Enable
está activado en appsettings.json
, también deberás ejecutar el proyecto PwcDotnet.AzureDurableFunctions
pudiendo poner como startup de la soluccion los dos proyectos incluso.
Si estas en visual studio asegurate por lo menos de que tu Stratup project sea minimamente PwcDotne.WebAPI (en caso de usar azuredurablefunctions local agregar el proyecto al startup de la solucion).
Por defecto, el sistema utiliza una base de datos en memoria (InMemoryDatabase
) para facilitar las pruebas rápidas y el testing sin necesidad de un servidor externo.
Si deseás usar una base de datos persistente (como SQL Server), seguí los siguientes pasos:
Editá el archivo appsettings.json
para desactivar la base en memoria:
"UseInMemoryDb": true // o false si querés usar SQL Server
Si false
(Persistir en SqlServer), agregá tu connection string en:
"UseInMemoryDb": false,
"ConnectionStrings": {
"PwcConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=PWCChallengeDb;Trusted_Connection=True;"
}
# Update de la migracion del proyecto de infrastructura
dotnet ef migrations add InitialCreate --project ../PwcDotnet.Infrastructure --startup-project .
dotnet ef database update --project ../PwcDotnet.Infrastructure --startup-project .
Asegurate de que el proyecto
PwcDotnet.WebAPI
esté seteado comoStartup Project
y que tenga acceso al archivoappsettings.json
Se implementaron pruebas unitarias y de integración con:
- xUnit
- Moq
- FluentAssertions
- WebApplicationFactory (integration)
# Ejecutar tests unitarios
dotnet test tests/PwcDotnet.UnitTests
# Ejecutar tests de integración
dotnet test tests/PwcDotnet.IntegrationTests
Tambien existen 📁TestCases de endopints en el
TestCases/PwcDotnet.WebAPI.http
file del proyecto PwcDotnet.WebAPI. Ideal para probar todos los endpoints de forma rápida.
RentalBusinessRulesTests
: Valida reglas de negocio del agregado Rental.RegisterRentalCommandHandlerTests
: Valida distintos flujos del handler (OK, auto reservado, auto en service, etc.)RentalCreatedDomainEventTests
: Verifica que se genere el evento correctamente al crear un alquiler.
- Endpoint
/rentals/register
probado end-to-end (semilla, login, request, verificación).
dotnet test --collect:"XPlat Code Coverage"
reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:coveragereport
🔎 Cobertura del caso de uso RegisterRental supera el 90%.
Metodo | Path | Descripción |
---|---|---|
POST | /auth/login |
Login del usuario y devolución de JWT |
POST | /auth/register |
Registro de nuevo usuario |
GET | /auth/me |
Devuelve datos del usuario actual |
POST | /rentals/register |
Registra un nuevo alquiler |
PUT | /rentals/modify |
Modifica un alquiler existente |
PUT | /rentals/cancel |
Cancela un alquiler |
GET | /dashboard/top-used-cars?from=...&to=...&locationId=... |
Top autos más alquilados |
GET | /dashboard/top-by-brand?from=...&to=... |
Top por marca y modelo |
GET | /dashboard/daily-stats?from=...&to=... |
Métricas diarias |
GET | /cars/availability?start=...&end=...&carType=... |
Disponibilidad de autos |
GET | /cars/upcoming-services?from=... |
Autos con servicios próximos |
GET | /customers |
Lista de clientes |
GET | /rentals |
Lista de rentals |
Disponible en:
https://localhost:{PORT}/swagger
Incluye:
- JWT Bearer Authentication
- Schema por endpoint con input/output
Se ejecuta automáticamente al correr la API. Contiene:
-
Usuario Admin:
admin@admin.com
/Admin123!
-
Customers, Rentals y Cars
-
Autos con servicios próximos
-
Se incluyen datos semilla en memoria (
SeedData.cs
en PwcDotnet.Infrastructure/Data) si activás In-Memory enappsettings.json
. -
Podés comentar la línea
SeedData.InitializeAsync()
en el Program.cs dE PwcDotnet.WebAPI si no querés usarlo.
Implementado con Serilog:
- Logs enriquecidos (threadId, traceId, etc.)
- Consola
- Archivos locales:
logs/log-*.txt
- Preparado para OpenTelemetry
🔄 RentalCreatedDomainEvent
se dispara al registrar un nuevo alquiler.
☁️ Integrado con Azure Durable Functions (AzLocal) para ejecutar side-effects -> envio de email del challenge. 📬 El email del cliente se recupera dinámicamente desde el repositorio antes de disparar la Durable Function.
"AzureDurableFunctions": {
"Enable": true,
"Url": "http://localhost:7081/api"
}
Si
Enable
está entrue
, el DomainEvent llamará al endpoint HTTPSendRentalEmailOrchestration_HttpStart
y disparará una orquestación Durable.
📐 Basado en Clean Architecture + DDD:
- Separación de responsabilidades (Separation of Concerns)
- Mantenibilidad
- Testing
- Alta cohesión y bajo acoplamiento
- Escalabilidad y modularidad
- Inversión de dependencias (Dependency Inversion Principle)
- Inversión de control (Inversion of Control Principle)
- Independencia de frameworks
src/
├── PwcDotnet.Domain -> Entidades, ValueObjects, reglas del negocio
├── PwcDotnet.Application -> Casos de uso (CQRS), validaciones, interfaces
├── PwcDotnet.Infrastructure -> EF Core, configuraciones, repositorios
├── PwcDotnet.WebAPI -> Endpoints Minimal API, Swagger, logging, DI
├── PwcDotnet.AzureDurableFunctions -> Orchestrators, activities (ej SendEmailOrchestrator and activities)
├── PwcDotnet.AngularSPA -> SPA Client
tests/
├── PwcDotnet.UnitTests -> Tests de dominio y aplicación
├── PwcDotnet.IntegrationTests -> Tests end-to-end con WebApplicationFactory
Se aplicaron dos mecanismos de cacheo para mejorar la performance del sistema:
Se implementó un middleware personalizado para simular el atributo [ResponseCache]
, ya que en Minimal APIs no está disponible por defecto.
// Extension en WebAPI.Extensions.EndpointCachingExtensions.cs
public static class EndpointCachingExtensions
{
public static RouteHandlerBuilder WithResponseCache(this RouteHandlerBuilder builder, int seconds)
{
return builder.AddEndpointFilter(async (context, next) =>
{
var httpContext = context.HttpContext;
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromSeconds(seconds)
};
return await next(context);
});
}
}
group.MapGet("/", GetAllAsync).WithResponseCache(1);
Este ejemplo aplica un cache público por 1 segundo al endpoint
/customers
.
Se utilizó IMemoryCache
para almacenar temporalmente los resultados de la query GetAllCustomersQuery
, mejorando la velocidad y reduciendo lecturas innecesarias a base de datos.
public async Task<List<CustomerDto>> Handle(GetAllCustomersQuery request, CancellationToken cancellationToken)
{
var customerDtoList = await _cache.GetOrCreateAsync("customers_cache", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3);
var customers = await _customerRepository.GetAllAsync();
return customers.Select(c => new CustomerDto
{
Id = c.Id,
FullName = c.FullName,
Email = c.Email
}).ToList();
});
return customerDtoList ?? new List<CustomerDto>();
}
Se utiliza una key
"customers_cache"
con una expiración absoluta de 3 segundos.
💡 Ambos mecanismos son independientes y complementarios:
IMemoryCache
: actúa en la capa de Aplicación.ResponseCache
: actúa sobre la respuesta HTTP (capa Web/API).
- SOLID (especialmente SRP, DIP, OCP)
- KISS (Keep It Simple)
- YAGNI (You Aren't Gonna Need It)
- DRY (Don't Repeat Yourself)
- Separation of Concerns
- Inversion of Control Principle
- Explicit Dependencies
- DDD
- DDD Patterns (Aggregates, Entities, ValueObjets, etc)
- CQRS (Command Query Responsibility Segregation)
- Repository Pattern (incluyendo un repositorio genérico)
- Dependency Injection (nativa de .NET)
- Validation Behavior (usando MediatR y FluentValidation)
- Extension Methods (para configuración)
- Unit of Work (a través del contexto de EF Core)
- Minimal APIs (.NET 8)
- Domain Events
- Mediator Pattern (MediatR)
- Exception Middleware
Sección en construcción. Aquí se documentará el frontend Angular cuando se agregue a la solución.
Gracias por leer 🚀