Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/JA-123 Logging in BusinessLogic services and DbRepository #118

Merged
merged 14 commits into from
Jul 7, 2024
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AutoFixture;
using Microsoft.Extensions.Logging;
using Moq;
using TutorLizard.BusinessLogic.Interfaces.Data.Repositories;
using TutorLizard.BusinessLogic.Models;
Expand All @@ -12,10 +13,13 @@ public abstract class BrowseServiceTestsBase : TestsWithInMemoryDbBase
protected Fixture Fixture = new();
protected Mock<IDbRepository<Ad>> MockAdRepository = new();
protected Mock<IDbRepository<ScheduleItem>> MockScheduleItemRepository = new();
protected Mock<ILogger<BrowseService>> MockLogger = new();

protected BrowseServiceTestsBase() : base()
{
BrowseService = new(MockAdRepository.Object, MockScheduleItemRepository.Object);
BrowseService = new(MockAdRepository.Object,
MockScheduleItemRepository.Object,
MockLogger.Object);
}

protected void SetupMockGetAllAds(List<Ad> ads)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AutoFixture;
using Microsoft.Extensions.Logging;
using Moq;
using TutorLizard.BusinessLogic.Interfaces.Data.Repositories;
using TutorLizard.BusinessLogic.Models;
Expand All @@ -14,14 +15,15 @@ public class StudentServiceTestBase : TestsWithInMemoryDbBase
protected Mock<IDbRepository<AdRequest>> MockAdRequestRepository = new();
protected Mock<IDbRepository<ScheduleItem>> MockScheduleItemRepository = new();
protected Mock<IDbRepository<ScheduleItemRequest>> MockScheduleItemRequestRepository = new();

protected Mock<ILogger<StudentService>> MockLogger = new();

protected StudentServiceTestBase() : base()
{
StudentService = new StudentService(MockAdRepository.Object,
MockAdRequestRepository.Object,
MockScheduleItemRepository.Object,
MockScheduleItemRequestRepository.Object);
MockAdRequestRepository.Object,
MockScheduleItemRepository.Object,
MockScheduleItemRequestRepository.Object,
MockLogger.Object);
}

protected void SetupMockGetScheduleItemById(ScheduleItem? scheduleItem)
Expand All @@ -38,18 +40,5 @@ protected void SetupMockGetAllScheduleItems(List<ScheduleItem> scheduleItems)
.Setup(x => x.GetAll())
.Returns(scheduleItemsInDb);
}

protected IQueryable<TEntity> AddEntitiesToInMemoryDb<TEntity>(List<TEntity> entities)
where TEntity : class
{
DbContext
.Set<TEntity>()
.AddRange(entities);
DbContext.SaveChanges();

return DbContext
.Set<TEntity>()
.AsQueryable();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using TutorLizard.BusinessLogic.Extensions;
using TutorLizard.BusinessLogic.Interfaces.Data.Repositories;

namespace TutorLizard.BusinessLogic.Data.Repositories.DataBase;
Expand All @@ -7,10 +9,13 @@ public class DbRepository<TEntity, UDbContext> : IDbRepository<TEntity>
where UDbContext : DbContext
{
private readonly UDbContext _dbContext;
private readonly ILogger<DbRepository<TEntity, UDbContext>> _logger;

public DbRepository(UDbContext dbContext)
public DbRepository(UDbContext dbContext,
ILogger<DbRepository<TEntity, UDbContext>> logger)
{
_dbContext = dbContext;
_logger = logger;
}

public IQueryable<TEntity> GetAll()
Expand All @@ -21,8 +26,20 @@ public IQueryable<TEntity> GetAll()

public async Task<TEntity?> GetById<VId>(VId id)
{
return await _dbContext.Set<TEntity>()
TEntity? entity = await _dbContext
.Set<TEntity>()
.FindAsync(id);

if (entity is null)
{
_logger.LogEntityNotFound(nameof(GetById), id);
}
else
{
_logger.LogEntityFound(nameof(GetById), id);
}

return entity;
}

public async Task<TEntity> Create(TEntity entity)
Expand All @@ -31,31 +48,55 @@ public async Task<TEntity> Create(TEntity entity)
.Set<TEntity>()
.Add(entity);
await _dbContext.SaveChangesAsync();

_logger.LogEntityCreated(nameof(Create), GetEntityId(entity));

return entity;
}

public async Task<TEntity?> Update<VId>(VId id, Action<TEntity> updateAction)
{
TEntity? toUpdate = await GetById(id);
if (toUpdate is null)
{
_logger.LogEntityNotUpdated(nameof(Update), id);
return null;
}

updateAction.Invoke(toUpdate);
await _dbContext.SaveChangesAsync();

_logger.LogEntityUpdated(nameof(Update), id);

return toUpdate;
}

public async Task<TEntity?> Delete<VId>(VId id)
{
TEntity? toDelete = await GetById(id);
if (toDelete is null)
{
_logger.LogEntityNotDeleted(nameof(Delete), id);
return null;
}

_dbContext.Set<TEntity>()
.Remove(toDelete);
await _dbContext.SaveChangesAsync();

_logger.LogEntityDeleted(nameof(Delete), id);

return toDelete;
}

private object? GetEntityId(TEntity entity)
{
var entry = _dbContext.Entry(entity);
var id = entry.Metadata.FindPrimaryKey()?
.Properties
.Select(p => entry.Property(p.Name).CurrentValue)
.FirstOrDefault();

return id;
}
}
3 changes: 1 addition & 2 deletions TutorLizard.BusinessLogic/Extensions/DtoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ public static UserDto ToDto(this User user)
user.Name,
user.UserType,
user.Email,
user.DateCreated,
user.GoogleId);
user.DateCreated);
}
71 changes: 71 additions & 0 deletions TutorLizard.BusinessLogic/Extensions/LoggingExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Microsoft.Extensions.Logging;
using TutorLizard.BusinessLogic.Interfaces.Data.Repositories;

namespace TutorLizard.BusinessLogic.Extensions;
internal static class LoggingExtensions
{
internal static IDisposable? BeginMethodCallScope<TService, TRequest>(this ILogger<TService> logger, string methodName, TRequest request, bool destructureRequest = true)
{
string scopeMessageFormat = $"Service: {{Service}} Method: {{ServiceMethod}} Request: {(destructureRequest ? "{@Request}" : "{Request}")}";
var scope = logger.BeginScope(scopeMessageFormat, typeof(TService).Name, methodName, request);

string logMessageFormat = $"[Service method call] Service: {{Service}} Method: {{ServiceMethod}} Request: {(destructureRequest ? "{@Request}" : "{Request}")}";
logger.LogInformation(logMessageFormat, typeof(TService).Name, methodName, request);

return scope;
}

internal static void LogReturningResponse<TService, TResponse>(this ILogger<TService> logger, TResponse response, bool destructureResponse = true)
{
string logMessageFormat = $"[Returning response] Response: {(destructureResponse ? "{@Response}" : "{Response}")}";
logger.LogInformation(logMessageFormat, response);
}

internal static void LogEntityCreated<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} created successfully with Id: {EntityId}", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityNotCreated<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName)
where TEntity : class
{
logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} not created", methodName, typeof(TEntity).Name);
}

internal static void LogEntityUpdated<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} updated successfully", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityNotUpdated<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} not updated", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityDeleted<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} deleted successfully", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityNotDeleted<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} not deleted", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityFound<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} found", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityNotFound<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} not found", methodName, typeof(TEntity).Name, id);
}
}
16 changes: 15 additions & 1 deletion TutorLizard.BusinessLogic/Services/BrowseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,28 @@
using TutorLizard.Shared.Models.DTOs;
using TutorLizard.Shared.Models.DTOs.Requests;
using TutorLizard.Shared.Models.DTOs.Responses;
using Microsoft.Extensions.Logging;
using TutorLizard.BusinessLogic.Extensions;

namespace TutorLizard.BusinessLogic.Services;
public class BrowseService : IBrowseService
{
private readonly IDbRepository<Ad> _adRepository;
private readonly IDbRepository<ScheduleItem> _scheduleItemRepository;
private readonly ILogger<BrowseService> _logger;

public BrowseService(IDbRepository<Ad> adRepository,
IDbRepository<ScheduleItem> _scheduleItemRepository)
IDbRepository<ScheduleItem> _scheduleItemRepository,
ILogger<BrowseService> logger)
{
_adRepository = adRepository;
this._scheduleItemRepository = _scheduleItemRepository;
_logger = logger;
}
public async Task<GetBrowseAdsPageResponse> GetBrowseAdsPage(GetBrowseAdsPageRequest request)
{
using var scope = _logger.BeginMethodCallScope(nameof(GetBrowseAdsPage), request);

if (request.PageSize < 1 || request.PageNumber < 1)
{
return new()
Expand Down Expand Up @@ -64,11 +71,14 @@ public async Task<GetBrowseAdsPageResponse> GetBrowseAdsPage(GetBrowseAdsPageReq
SearchCriteria = request.SearchCriteria
};

_logger.LogReturningResponse(response);
return response;
}

public async Task<GetAdDetailsResponse?> GetAdDetails(GetAdDetailsRequest request)
{
using var scope = _logger.BeginMethodCallScope(nameof(GetAdDetails), request);

GetAdDetailsResponse? response = await _adRepository.GetAll()
.Where(a => a.Id == request.AdId)
.Select(a => new GetAdDetailsResponse
Expand All @@ -92,11 +102,14 @@ public async Task<GetBrowseAdsPageResponse> GetBrowseAdsPage(GetBrowseAdsPageReq
})
.FirstOrDefaultAsync();

_logger.LogReturningResponse(response);
return response;
}

public async Task<GetUsersScheduleResponse> GetUsersSchedule(GetUsersScheduleRequest request)
{
using var scope = _logger.BeginMethodCallScope(nameof(GetUsersSchedule), request);

List<TutorsScheduleItemSummaryDto> tutorsSchedule = await _scheduleItemRepository.GetAll()
.Where(i => i.Ad.TutorId == request.UserId)
.Select(i => new TutorsScheduleItemSummaryDto()
Expand Down Expand Up @@ -136,6 +149,7 @@ public async Task<GetUsersScheduleResponse> GetUsersSchedule(GetUsersScheduleReq
StudentsSchedule = studentsSchedule
};

_logger.LogReturningResponse(response);
return response;
}
private IQueryable<Ad> ApplySearchCriteria(IQueryable<Ad> ads, AdSearchCriteriaDto searchCriteria)
Expand Down
13 changes: 11 additions & 2 deletions TutorLizard.BusinessLogic/Services/CategoryService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using TutorLizard.BusinessLogic.Extensions;
using TutorLizard.BusinessLogic.Interfaces.Data.Repositories;
using TutorLizard.BusinessLogic.Interfaces.Services;
Expand All @@ -11,23 +12,31 @@ namespace TutorLizard.BusinessLogic.Services;
public class CategoryService : ICategoryService
{
private readonly IDbRepository<Category> _categoryRepository;
private readonly ILogger<CategoryService> _logger;

public CategoryService(IDbRepository<Category> categoryRepository)
public CategoryService(IDbRepository<Category> categoryRepository,
ILogger<CategoryService> logger)
{
_categoryRepository = categoryRepository;
_logger = logger;
}

public async Task<GetCategoriesResponse> GetCategories(GetCategoriesRequest request)
{
using var scope = _logger.BeginMethodCallScope(nameof(GetCategories), request);

List<CategoryDto> categories = await _categoryRepository
.GetAll()
.Select(category => category.ToDto())
.ToListAsync();

return new GetCategoriesResponse()
GetCategoriesResponse response = new()
{
Success = true,
Categories = categories
};

_logger.LogReturningResponse(response);
return response;
}
}
Loading
Loading