Skip to content

Commit

Permalink
feat: Entity caching via IDistributedCache (#393)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcarey1590 authored Dec 12, 2024
1 parent f4055b2 commit d2b549b
Show file tree
Hide file tree
Showing 39 changed files with 1,305 additions and 339 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="8.0.11" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System;
using System.Linq;
using Firebend.AutoCrud.Core.Abstractions.Builders;
using Firebend.AutoCrud.Core.Configurators;
using Firebend.AutoCrud.Core.Implementations.Caching;
using Firebend.AutoCrud.Core.Interfaces.Caching;
using Firebend.AutoCrud.Core.Interfaces.Models;
using Microsoft.Extensions.DependencyInjection;

namespace Firebend.AutoCrud.Core.Extensions.EntityBuilderExtensions;

Expand Down Expand Up @@ -95,13 +99,65 @@ public static EntityCrudBuilder<TKey, TEntity> AddCrud<TKey, TEntity>(this Entit
/// })
/// </code>
/// </example>
public static EntityCrudBuilder<TKey, TEntity> AddDomainEvents<TKey, TEntity>(this EntityCrudBuilder<TKey, TEntity> builder,
public static EntityCrudBuilder<TKey, TEntity> AddDomainEvents<TKey, TEntity>(
this EntityCrudBuilder<TKey, TEntity> builder,
Action<DomainEventsConfigurator<EntityCrudBuilder<TKey, TEntity>, TKey, TEntity>> configure)
where TKey : struct
where TEntity : class, IEntity<TKey>
{
using var domainEventsConfigurator = new DomainEventsConfigurator<EntityCrudBuilder<TKey, TEntity>, TKey, TEntity>(builder);
using var domainEventsConfigurator =
new DomainEventsConfigurator<EntityCrudBuilder<TKey, TEntity>, TKey, TEntity>(builder);
configure(domainEventsConfigurator);
return builder;
}

/// <summary>
/// Adds caching to an entity using the default cache service. Must be called after <see cref="Firebend.AutoCrud.Core.Extensions.ServiceCollectionExtensions.WithEntityCaching<TCacheOptions>"/>.
/// </summary>
/// <param name="builder"><see cref="Firebend.AutoCrud.Core.Abstractions.Builders.EntityCrudBuilder<TKey,TEntity>"/></param>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TEntity"></typeparam>
/// <returns>The calling EntityCrudBuilder</returns>
/// <exception cref="InvalidOperationException"></exception>
public static EntityCrudBuilder<TKey, TEntity> AddEntityCaching<TKey, TEntity>(
this EntityCrudBuilder<TKey, TEntity> builder)
where TKey : struct
where TEntity : class, IEntity<TKey>
{
if (builder.Services.All(x => x.ServiceType != typeof(IEntityCacheOptions)))
{
throw new InvalidOperationException(
"WithEntityCaching must be called before AddEntityCaching.");
}

builder.Services.AddScoped<IEntityCacheService<TKey, TEntity>, DefaultEntityCacheService<TKey, TEntity>>();

return builder;
}

/// <summary>
/// Adds caching to an entity using the provided cache service. Must be called after <see cref="Firebend.AutoCrud.Core.Extensions.ServiceCollectionExtensions.WithEntityCaching<TCacheOptions>"/>.
/// </summary>
/// <param name="builder"><see cref="Firebend.AutoCrud.Core.Abstractions.Builders.EntityCrudBuilder<TKey,TEntity>"/></param>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TService">Entity Cache Service type to register</typeparam>
/// <returns>The calling EntityCrudBuilder</returns>
/// <exception cref="InvalidOperationException"></exception>
public static EntityCrudBuilder<TKey, TEntity> AddEntityCaching<TKey, TEntity, TService>(
this EntityCrudBuilder<TKey, TEntity> builder)
where TKey : struct
where TEntity : class, IEntity<TKey>
where TService : class, IEntityCacheService<TKey, TEntity>
{
if (builder.Services.All(x => x.ServiceType != typeof(IEntityCacheOptions)))
{
throw new InvalidOperationException(
"WithEntityCaching must be called before AddEntityCaching.");
}

builder.Services.AddScoped<IEntityCacheService<TKey, TEntity>, TService>();

return builder;
}
}
60 changes: 60 additions & 0 deletions Firebend.AutoCrud.Core/Extensions/EntityCacheServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Firebend.AutoCrud.Core.Interfaces.Caching;
using Firebend.AutoCrud.Core.Interfaces.Models;

namespace Firebend.AutoCrud.Core.Extensions;

public static class EntityCacheServiceExtensions
{
public static async Task<TEntity> GetOrSetAsync<TKey, TEntity>(
this IEntityCacheService<TKey, TEntity> cacheService,
TKey key,
Func<Task<TEntity>> factory,
CancellationToken cancellationToken)
where TKey : struct
where TEntity : class, IEntity<TKey>
{
var entity = await cacheService.GetAsync(key, cancellationToken);

if (entity is not null)
{
return entity;
}

entity = await factory();

if (entity is not null)
{
await cacheService.SetAsync(entity, cancellationToken);
}

return entity;
}

public static async Task<List<TEntity>> GetOrSetAsync<TKey, TEntity>(
this IEntityCacheService<TKey, TEntity> cacheService,
Func<Task<List<TEntity>>> factory,
CancellationToken cancellationToken)
where TKey : struct
where TEntity : class, IEntity<TKey>
{
var collection = await cacheService.GetCollectionAsync(cancellationToken);

if (collection is not null)
{
return collection;
}

collection = await factory();

if (collection.HasValues())
{
await cacheService.SetCollectionAsync(collection, cancellationToken);
}

return collection;
}
}
34 changes: 34 additions & 0 deletions Firebend.AutoCrud.Core/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Firebend.AutoCrud.Core.Implementations.Caching;
using Firebend.AutoCrud.Core.Interfaces.Caching;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Firebend.AutoCrud.Core.Extensions;

Expand All @@ -21,4 +26,33 @@ public static void RegisterAllTypes<T>(
services.Add(new ServiceDescriptor(typeof(T), type, lifetime));
}
}

public static IServiceCollection WithEntityCaching(this IServiceCollection services, Action<EntityCacheOptions> configure = null)
{
var cacheOptions = new EntityCacheOptions();
configure?.Invoke(cacheOptions);
services.AddScoped<IEntityCacheOptions>((_) => cacheOptions);
services.TryAddSingleton<IEntityCacheSerializer, JsonEntityCacheSerializer>();
CheckDistributedCache(services);
return services;
}

public static IServiceCollection WithEntityCaching<TCacheOptions, TCacheSerializer>(this IServiceCollection services)
where TCacheOptions : class, IEntityCacheOptions
where TCacheSerializer : class, IEntityCacheSerializer
{
services.AddScoped<IEntityCacheOptions, TCacheOptions>();
services.AddSingleton<IEntityCacheSerializer, TCacheSerializer>();
CheckDistributedCache(services);
return services;
}

private static void CheckDistributedCache(IServiceCollection services)
{
if (services.All(x => x.ServiceType != typeof(IDistributedCache)))
{
throw new InvalidOperationException(
"IDistributedCache is required for entity caching. Ensure it is registered before calling AddEntityCaching.");
}
}
}
2 changes: 1 addition & 1 deletion Firebend.AutoCrud.Core/Firebend.AutoCrud.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AsyncKeyedLock" Version="7.0.1" />
<PackageReference Include="AsyncKeyedLock" Version="7.1.4" />
<PackageReference Include="Firebend.JsonPatchGenerator" Version="8.0.6" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
Expand Down
Loading

0 comments on commit d2b549b

Please sign in to comment.