Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 6 additions & 12 deletions sample/Vivarni.Example.API/Controllers/GuestMessagesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,21 @@ namespace Vivarni.Example.API.Controllers;

[Route("api")]
[ApiController]
public class GuestMessagesController : ControllerBase
public class GuestMessagesController(IGenericRepository<GuestMessage> repositoryBase) : ControllerBase
{
private readonly IGenericRepository<GuestMessage> _guestMessageRepository;

public GuestMessagesController(IGenericRepository<GuestMessage> repositoryBase)
{
_guestMessageRepository = repositoryBase;
}

[HttpGet("guestmessages")]
public async Task<IActionResult> GetAllGuestMessages(CancellationToken cancellation)
{
var messages = await _guestMessageRepository.ListAsync(cancellation);
var messages = await repositoryBase.ListAsync(cancellation);
var vms = messages.Select(message => new GuestMessageReadModel(message.Id, message.Message, message.CreatedBy, message.CreationDate));
return Ok(vms);
}

[HttpGet("guestmessages/{id}")]
public async Task<IActionResult> GetGuestMessageById(Guid id, CancellationToken cancellation)
{
var message = await _guestMessageRepository.GetByIdAsync<Guid>(id, cancellation);
var message = await repositoryBase.GetByIdAsync<Guid>(id, cancellation);
if (message == null)
return NotFound();

Expand All @@ -39,18 +33,18 @@ public async Task<IActionResult> GetGuestMessageById(Guid id, CancellationToken
public async Task<IActionResult> PostGuestMessageWithDomainEvents([FromBody] GuestMessageWriteModel wm, CancellationToken cancellation)
{
var message = GuestMessage.GuestMessageCreate(wm.GuestMessage, wm.CreatedByUser);
var vm = await _guestMessageRepository.AddAsync(message, cancellation);
var vm = await repositoryBase.AddAsync(message, cancellation);
return Ok(vm);
}

[HttpDelete("guestmessages")]
public async Task<IActionResult> DeleteGuestMessageById(Guid id, CancellationToken cancellation)
{
var message = await _guestMessageRepository.GetByIdAsync<Guid>(id, cancellation);
var message = await repositoryBase.GetByIdAsync<Guid>(id, cancellation);
if (message == null)
return NotFound();

await _guestMessageRepository.DeleteAsync(message, cancellation);
await repositoryBase.DeleteAsync(message, cancellation);
return Ok();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@ namespace Vivarni.Example.API.Controllers;

[Route("api")]
[ApiController]
public class GuestMessagesCounterController : ControllerBase
public class GuestMessagesCounterController(IGenericRepository<GuestMessagesCounter> counterRepository) : ControllerBase
{
private readonly IGenericRepository<GuestMessagesCounter> _counterRepository;
public GuestMessagesCounterController(IGenericRepository<GuestMessagesCounter> counterRepository)
{
_counterRepository = counterRepository;
}

[HttpGet("guestmessagescounter")]
public async Task<IActionResult> GetCounters(CancellationToken cancellation)
{
var counters = await _counterRepository.ListAsync(cancellation);
var counters = await counterRepository.ListAsync(cancellation);
var counter = counters.SingleOrDefault();
if (counter == null)
return Ok("The domain event hasn't been called yet. Create a new message for the datamigration to take place");
Expand Down
2 changes: 1 addition & 1 deletion src/Vivarni.DDD.Core/Vivarni.DDD.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<Description>Fundamental building blocks for creating the domain layer in a DDD software design approach.</Description>
<PackageTags>ddd;domain driven design;specification;aggregate</PackageTags>
<LangVersion>11.0</LangVersion>
<LangVersion>13.0</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,8 @@ public interface IDomainEventBrokerService
}

/// <inheritdoc cref="IDomainEventBrokerService"/>
public class DomainEventBrokerService : IDomainEventBrokerService
public class DomainEventBrokerService(Dictionary<Type, IReadOnlyCollection<IDomainEventHandler>> handlers, IServiceProvider serviceProvider) : IDomainEventBrokerService
{
private readonly Dictionary<Type, IReadOnlyCollection<IDomainEventHandler>> _handlers;
private readonly IServiceProvider _serviceProvider;

/// <summary>
/// Creates an instance of this class.
/// </summary>
public DomainEventBrokerService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_handlers = new Dictionary<Type, IReadOnlyCollection<IDomainEventHandler>>();
}

/// <inheritdoc/>
public async Task PublishEventsAsync(IDomainEvent[] events, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -66,13 +54,13 @@ private async Task PublishAsync<TEvent>(TEvent domainEvent, CancellationToken ca
/// <returns>A collection of event handler instances.</returns>
private IReadOnlyCollection<TEventHandler> ResolveEventHandlers<TEventHandler>()
{
if (!_handlers.ContainsKey(typeof(TEventHandler)))
if (!handlers.ContainsKey(typeof(TEventHandler)))
{
var handlers = (IReadOnlyCollection<IDomainEventHandler>)_serviceProvider.GetServices<TEventHandler>();
_handlers[typeof(TEventHandler)] = handlers;
var services = (IReadOnlyCollection<IDomainEventHandler>)serviceProvider.GetServices<TEventHandler>();
handlers[typeof(TEventHandler)] = services;
}

return (IReadOnlyCollection<TEventHandler>)_handlers[typeof(TEventHandler)];
return (IReadOnlyCollection<TEventHandler>)handlers[typeof(TEventHandler)];
}
}
}
83 changes: 35 additions & 48 deletions src/Vivarni.DDD.Infrastructure/Repositories/GenericRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,49 +16,36 @@
namespace Vivarni.DDD.Infrastructure.Repositories
{
/// <inheritdoc cref="IGenericRepository{T}"/>
public class GenericRepository<T> : IGenericRepository<T>
/// <summary>
/// We use the standard Microsoft logger in order to be portable between solutions which
/// may use other logging frameworks than Serilog. All logs are routed to serilog anyways,
/// so no worries there ;-)
/// </summary>
public class GenericRepository<T>(ILogger<GenericRepository<T>> logger, ICachingProvider cacheProvider, DbContext ctx) : IGenericRepository<T>

Check warning on line 24 in src/Vivarni.DDD.Infrastructure/Repositories/GenericRepository.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'ctx' is unread.

Check warning on line 24 in src/Vivarni.DDD.Infrastructure/Repositories/GenericRepository.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'ctx' is unread.
where T : class, IAggregateRoot
{
private readonly DbContext _ctx;
private readonly DbContext ctx;

Check warning on line 27 in src/Vivarni.DDD.Infrastructure/Repositories/GenericRepository.cs

View workflow job for this annotation

GitHub Actions / build

Field 'GenericRepository<T>.ctx' is never assigned to, and will always have its default value null

Check warning on line 27 in src/Vivarni.DDD.Infrastructure/Repositories/GenericRepository.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field 'ctx' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 27 in src/Vivarni.DDD.Infrastructure/Repositories/GenericRepository.cs

View workflow job for this annotation

GitHub Actions / build

Field 'GenericRepository<T>.ctx' is never assigned to, and will always have its default value null

Check warning on line 27 in src/Vivarni.DDD.Infrastructure/Repositories/GenericRepository.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field 'ctx' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

private const string LOG_MSG_DFLT =
"{GenericRepositoryMethod} execution for {GenericRepositoryType} took {GenericRepositoryMilliseconds} ms";

/// <summary>
/// We use the standard Microsoft logger in order to be portable between solutions which
/// may use other logging frameworks than Serilog. All logs are routed to serilog anyways,
/// so no worries there ;-)
/// </summary>
private readonly ILogger<GenericRepository<T>> _logger;
private readonly ICachingProvider _cacheProvider;

/// <summary>
/// Creates a new instance of this class.
/// </summary>
public GenericRepository(DbContext ctx, ILogger<GenericRepository<T>> logger, ICachingProvider cachingProvider)
{
_ctx = ctx;
_logger = logger;
_cacheProvider = cachingProvider;
}

private void Log(ISpecification<T> spec, string methodName, long milliseconds, bool? cacheHit)
{
var specName = spec.GetType().ToString();
var messageTemplate =
"{GenericRepositoryMethod} execution for {GenericRepositoryType} took {GenericRepositoryMilliseconds} ms using specification {GenericRepositorySpecification} " +
"with CacheEnabled {GenericRepositoryCacheEnabled} and CacheHit {GenericRepositoryCacheHit}";

_logger.LogInformation(messageTemplate, methodName, typeof(T), milliseconds, specName, spec.CacheEnabled, cacheHit);
logger.LogInformation(messageTemplate, methodName, typeof(T), milliseconds, specName, spec.CacheEnabled, cacheHit);
}

/// <inheritdoc/>
public virtual async Task<T?> GetByIdAsync<TId>(TId id, CancellationToken cancellationToken = default)
where TId : notnull
{
var sw = Stopwatch.StartNew();
var result = await _ctx.Set<T>().FindAsync(new object[] { id }, cancellationToken);
_logger.LogInformation(LOG_MSG_DFLT + "Id={GenericQueryId}", nameof(GetByIdAsync), typeof(T), sw.ElapsedMilliseconds, id);
var result = await ctx.Set<T>().FindAsync(new object[] { id }, cancellationToken);
logger.LogInformation(LOG_MSG_DFLT + "Id={GenericQueryId}", nameof(GetByIdAsync), typeof(T), sw.ElapsedMilliseconds, id);

return result;
}
Expand All @@ -67,8 +54,8 @@
public async Task<IReadOnlyList<T>> ListAsync(CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();
var result = await _ctx.Set<T>().ToListAsync(cancellationToken);
_logger.LogInformation(LOG_MSG_DFLT, nameof(ListAsync), typeof(T), sw.ElapsedMilliseconds);
var result = await ctx.Set<T>().ToListAsync(cancellationToken);
logger.LogInformation(LOG_MSG_DFLT, nameof(ListAsync), typeof(T), sw.ElapsedMilliseconds);

return result;
}
Expand All @@ -86,7 +73,7 @@
var ttl = spec.GetCacheTTL();
var forceRefresh = spec.HasForcedCacheRefreshFlag();
cacheHit = true;
result = await _cacheProvider.GetAsync(spec.CacheKey!, async () =>
result = await cacheProvider.GetAsync(spec.CacheKey!, async () =>
{
cacheHit = false;
return await specificationResult.ToListAsync(cancellationToken);
Expand Down Expand Up @@ -124,7 +111,7 @@
var ttl = spec.GetCacheTTL();
var forceRefresh = spec.HasForcedCacheRefreshFlag();
cacheHit = true;
result = await _cacheProvider.GetAsync(spec.CacheKey!, async () =>
result = await cacheProvider.GetAsync(spec.CacheKey!, async () =>
{
cacheHit = false;
return await specificationResult.CountAsync(cancellationToken);
Expand All @@ -143,9 +130,9 @@
public virtual async Task<T> AddAsync(T entity, CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();
await _ctx.Set<T>().AddAsync(entity, cancellationToken);
await _ctx.SaveChangesAsync(cancellationToken);
_logger.LogInformation(LOG_MSG_DFLT, nameof(AddAsync), typeof(T), sw.ElapsedMilliseconds);
await ctx.Set<T>().AddAsync(entity, cancellationToken);
await ctx.SaveChangesAsync(cancellationToken);
logger.LogInformation(LOG_MSG_DFLT, nameof(AddAsync), typeof(T), sw.ElapsedMilliseconds);

return entity;
}
Expand All @@ -154,17 +141,17 @@
public virtual async Task UpdateAsync(T entity, CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();
await _ctx.SaveChangesAsync(cancellationToken);
_logger.LogInformation(LOG_MSG_DFLT, nameof(UpdateAsync), typeof(T), sw.ElapsedMilliseconds);
await ctx.SaveChangesAsync(cancellationToken);
logger.LogInformation(LOG_MSG_DFLT, nameof(UpdateAsync), typeof(T), sw.ElapsedMilliseconds);
}

/// <inheritdoc/>
public virtual async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();
_ctx.Set<T>().Remove(entity);
await _ctx.SaveChangesAsync(cancellationToken);
_logger.LogInformation(LOG_MSG_DFLT, nameof(DeleteAsync), typeof(T), sw.ElapsedMilliseconds);
ctx.Set<T>().Remove(entity);
await ctx.SaveChangesAsync(cancellationToken);
logger.LogInformation(LOG_MSG_DFLT, nameof(DeleteAsync), typeof(T), sw.ElapsedMilliseconds);
}

/// <inheritdoc/>
Expand All @@ -180,7 +167,7 @@
var ttl = spec.GetCacheTTL();
var forceRefresh = spec.HasForcedCacheRefreshFlag();
cacheHit = true;
result = await _cacheProvider.GetAsync(spec.CacheKey!, async () =>
result = await cacheProvider.GetAsync(spec.CacheKey!, async () =>
{
cacheHit = false;
return await specificationResult.FirstAsync(cancellationToken);
Expand Down Expand Up @@ -208,7 +195,7 @@
var ttl = spec.GetCacheTTL();
var forceRefresh = spec.HasForcedCacheRefreshFlag();
cacheHit = true;
result = await _cacheProvider.GetAsync(spec.CacheKey!, async () =>
result = await cacheProvider.GetAsync(spec.CacheKey!, async () =>
{
cacheHit |= false;
return await specificationResult.FirstOrDefaultAsync(cancellationToken);
Expand Down Expand Up @@ -236,7 +223,7 @@
var ttl = spec.GetCacheTTL();
var forceRefresh = spec.HasForcedCacheRefreshFlag();
cacheHit = true;
result = await _cacheProvider.GetAsync(spec.CacheKey!, async () =>
result = await cacheProvider.GetAsync(spec.CacheKey!, async () =>
{
cacheHit = false;
return await specificationResult.SingleAsync(cancellationToken);
Expand Down Expand Up @@ -264,7 +251,7 @@
var ttl = spec.GetCacheTTL();
var forceRefresh = spec.HasForcedCacheRefreshFlag();
cacheHit = true;
result = await _cacheProvider.GetAsync(spec.CacheKey!, async () =>
result = await cacheProvider.GetAsync(spec.CacheKey!, async () =>
{
cacheHit = false;
return await specificationResult.SingleOrDefaultAsync(cancellationToken);
Expand All @@ -283,16 +270,16 @@
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
var evaluator = SpecificationEvaluator.Default;
return evaluator.GetQuery(_ctx.Set<T>().AsQueryable(), spec);
return evaluator.GetQuery(ctx.Set<T>().AsQueryable(), spec);
}

/// <inheritdoc/>
public async Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();
await _ctx.Set<T>().AddRangeAsync(entities, cancellationToken);
await _ctx.SaveChangesAsync(cancellationToken);
_logger.LogInformation(LOG_MSG_DFLT, nameof(AddRangeAsync), typeof(T), sw.ElapsedMilliseconds);
await ctx.Set<T>().AddRangeAsync(entities, cancellationToken);
await ctx.SaveChangesAsync(cancellationToken);
logger.LogInformation(LOG_MSG_DFLT, nameof(AddRangeAsync), typeof(T), sw.ElapsedMilliseconds);

return entities;
}
Expand All @@ -301,17 +288,17 @@
public async Task UpdateRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();
await _ctx.SaveChangesAsync(cancellationToken);
_logger.LogInformation(LOG_MSG_DFLT, nameof(UpdateRangeAsync), typeof(T), sw.ElapsedMilliseconds);
await ctx.SaveChangesAsync(cancellationToken);
logger.LogInformation(LOG_MSG_DFLT, nameof(UpdateRangeAsync), typeof(T), sw.ElapsedMilliseconds);
}

/// <inheritdoc/>
public async Task DeleteRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();
_ctx.Set<T>().RemoveRange(entities);
await _ctx.SaveChangesAsync(cancellationToken);
_logger.LogInformation(LOG_MSG_DFLT, nameof(DeleteRangeAsync), typeof(T), sw.ElapsedMilliseconds);
ctx.Set<T>().RemoveRange(entities);
await ctx.SaveChangesAsync(cancellationToken);
logger.LogInformation(LOG_MSG_DFLT, nameof(DeleteRangeAsync), typeof(T), sw.ElapsedMilliseconds);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<Description>Fundamental building blocks for creating the domain layer in a DDD software design approach.</Description>
<PackageTags>ddd;domain driven design;specification;aggregate</PackageTags>
<LangVersion>8.0</LangVersion>
<LangVersion>13.0</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Loading