-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial concept for Hyperbee.Pipeline.Caching
- Loading branch information
1 parent
c2c2a68
commit 4d93cb8
Showing
9 changed files
with
584 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
src/Hyperbee.Pipline.Caching/Hyperbee.Pipeline.Caching.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<IsPackable>true</IsPackable> | ||
|
||
<Authors>Stillpoint Software, Inc.</Authors> | ||
<PackageReadmeFile>README.md</PackageReadmeFile> | ||
<PackageTags>pipeline;caching</PackageTags> | ||
<PackageIcon>icon.png</PackageIcon> | ||
<PackageProjectUrl>https://github.com/Stillpoint-Software/Hyperbee.Pipeline/</PackageProjectUrl> | ||
<PackageReleaseNotes>https://github.com/Stillpoint-Software/hyperbee.pipeline/releases/latest</PackageReleaseNotes> | ||
<TargetFrameworks>net8.0</TargetFrameworks> | ||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | ||
<Copyright>Stillpoint Software, Inc.</Copyright> | ||
<Title>Hyperbee Piplines Caching</Title> | ||
<Description>Caching for Hyperbee.Pipelines async pipelines</Description> | ||
<RepositoryUrl>https://github.com/Stillpoint-Software/Hyperbee.Pipeline</RepositoryUrl> | ||
<RepositoryType>git</RepositoryType> | ||
<PackageReleaseNotes>https://github.com/Stillpoint-Software/hyperbee.pipeline/releases/latest</PackageReleaseNotes> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Hyperbee.Pipeline\Hyperbee.Pipeline.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Include="..\..\assets\icon.png" Pack="true" Visible="false" PackagePath="/" /> | ||
<None Include="..\..\LICENSE"> | ||
<Pack>True</Pack> | ||
<PackagePath>\</PackagePath> | ||
</None> | ||
<None Include="README.md"> | ||
<Pack>True</Pack> | ||
<PackagePath>\</PackagePath> | ||
</None> | ||
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="8.0.0"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
136 changes: 136 additions & 0 deletions
136
src/Hyperbee.Pipline.Caching/PipelineCacheExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
using Hyperbee.Pipeline.Extensions.Implementation; | ||
using Microsoft.Extensions.Caching.Memory; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Hyperbee.Pipeline.Caching; | ||
|
||
public static class PipelineCacheExtensions | ||
{ | ||
public static IPipelineBuilder<TInput, TNext> PipeCache<TInput, TOutput, TNext>( | ||
this IPipelineBuilder<TInput, TOutput> builder, | ||
Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder<TOutput, TNext>> nestedBuilder, | ||
Func<TOutput, PipelineMemoryCacheOptions, PipelineMemoryCacheOptions> optionsFunc = null ) | ||
{ | ||
ArgumentNullException.ThrowIfNull( nestedBuilder ); | ||
|
||
var block = PipelineFactory.Start<TOutput>(); | ||
var function = nestedBuilder( block ).GetPipelineFunction(); | ||
|
||
return builder.PipeCacheAsync( function.Function, optionsFunc ); | ||
} | ||
|
||
public static IPipelineBuilder<TInput, TNext> PipeCacheAsync<TInput, TOutput, TNext>( | ||
this IPipelineBuilder<TInput, TOutput> builder, | ||
Func<IPipelineStartBuilder<TOutput, TOutput>, IPipelineBuilder<TOutput, TNext>> nestedBuilder, | ||
Func<TOutput, PipelineMemoryCacheOptions, PipelineMemoryCacheOptions> optionsFunc = null ) | ||
{ | ||
ArgumentNullException.ThrowIfNull( nestedBuilder ); | ||
|
||
var block = PipelineFactory.Start<TOutput>(); | ||
var function = nestedBuilder( block ).GetPipelineFunction(); | ||
|
||
return builder.PipeCacheAsync( function.Function, optionsFunc ); | ||
} | ||
|
||
public static IPipelineBuilder<TInput, TNext> PipeCache<TInput, TOutput, TNext>( | ||
this IPipelineBuilder<TInput, TOutput> builder, | ||
Function<TOutput, TNext> next, | ||
Func<TOutput, PipelineMemoryCacheOptions, PipelineMemoryCacheOptions> optionsFunc = null ) | ||
{ | ||
|
||
// default to using input as key | ||
optionsFunc ??= ( output, options ) => | ||
{ | ||
options.Key = output; | ||
return options; | ||
}; | ||
|
||
|
||
return builder.Pipe( ( context, argument ) => | ||
{ | ||
var cache = context | ||
.ServiceProvider | ||
.GetService<IMemoryCache>(); | ||
|
||
if ( cache == null ) | ||
{ | ||
context.Logger?.LogWarning( "Cache not configured." ); | ||
return next( context, argument ); | ||
} | ||
|
||
var defaultCacheOption = context | ||
.ServiceProvider | ||
.GetService<IOptions<PipelineMemoryCacheOptions>>(); | ||
|
||
var cacheOption = optionsFunc( argument, defaultCacheOption?.Value ?? new PipelineMemoryCacheOptions() ); | ||
|
||
if ( cacheOption?.Key != null ) | ||
{ | ||
return cache.GetOrCreate( cacheOption.Key, entry => | ||
{ | ||
context.Logger?.LogDebug( "Creating cache entry for {Key} not configured", cacheOption.Key ); | ||
entry.SetOptions( cacheOption ); | ||
return next( context, argument ); | ||
} ) ?? default; | ||
} | ||
|
||
context.Logger?.LogError( "Cache entries must have a valid key." ); | ||
context.Exception = new InvalidOperationException( "Cache entries must have a valid key." ); | ||
context.CancelAfter(); | ||
return default; | ||
|
||
} ); | ||
} | ||
|
||
public static IPipelineBuilder<TInput, TNext> PipeCacheAsync<TInput, TOutput, TNext>( | ||
this IPipelineBuilder<TInput, TOutput> builder, | ||
FunctionAsync<TOutput, TNext> next, | ||
Func<TOutput, PipelineMemoryCacheOptions, PipelineMemoryCacheOptions> optionsFunc = null ) | ||
{ | ||
|
||
// default to using input as key | ||
optionsFunc ??= ( output, options ) => | ||
{ | ||
options.Key = output; | ||
return options; | ||
}; | ||
|
||
|
||
return builder.PipeAsync( async ( context, argument ) => | ||
{ | ||
var cache = context | ||
.ServiceProvider | ||
.GetService<IMemoryCache>(); | ||
|
||
if ( cache == null ) | ||
{ | ||
context.Logger?.LogWarning( "Cache not configured." ); | ||
return await next( context, argument ); | ||
} | ||
|
||
var defaultCacheOption = context | ||
.ServiceProvider | ||
.GetService<IOptions<PipelineMemoryCacheOptions>>(); | ||
|
||
var cacheOption = optionsFunc( argument, defaultCacheOption?.Value ?? new PipelineMemoryCacheOptions() ); | ||
|
||
if ( cacheOption?.Key != null ) | ||
{ | ||
return await cache.GetOrCreateAsync( cacheOption.Key, entry => | ||
{ | ||
context.Logger?.LogDebug( "Creating cache entry for {Key} not configured", cacheOption.Key ); | ||
entry.SetOptions( cacheOption ); | ||
return next( context, argument ); | ||
} ) ?? default; | ||
} | ||
|
||
context.Logger?.LogError( "Cache entries must have a valid key." ); | ||
context.Exception = new InvalidOperationException( "Cache entries must have a valid key." ); | ||
context.CancelAfter(); | ||
return default; | ||
|
||
} ); | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/Hyperbee.Pipline.Caching/PipelineMemoryCacheOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using Microsoft.Extensions.Caching.Memory; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Hyperbee.Pipeline.Caching; | ||
|
||
public class PipelineMemoryCacheOptions : MemoryCacheEntryOptions, IOptions<PipelineMemoryCacheOptions> | ||
{ | ||
public object Key { get; set; } | ||
public PipelineMemoryCacheOptions Value => this; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Hyperbee.Pipeline.Caching | ||
|
||
The `Hyperbee.Pipeline.Caching` library is a set of extentsions to `Hyperbee.Pipeline` that adds support for caching within a pipeline using `Microsoft.Extensions.Caching` libraries. | ||
|
||
## Examples | ||
For simple pipelines the previous step's return value can be used as the key: | ||
|
||
```csharp | ||
// Takes a string and returns a number | ||
var command = PipelineFactory | ||
.Start<string>() | ||
.PipeCacheAsync( CharacterCountAsync ) | ||
.Build(); | ||
|
||
var result = await command( factory.Create( logger ), "test" ); | ||
|
||
Assert.AreEqual( 4, result ); | ||
``` | ||
|
||
Or for more complex the options callback can be used to customize how the results will be cached. | ||
|
||
```csharp | ||
// Takes a string and returns a number | ||
var command = PipelineFactory | ||
.Start<string>() | ||
.PipeCacheAsync( CharacterCountAsync, | ||
( input, options ) => | ||
{ | ||
options.Key = $"custom/{input}"; | ||
options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours( 1 ); | ||
return options; | ||
} ) | ||
.Build(); | ||
|
||
var result = await command( factory.Create( logger ), "test" ); | ||
|
||
Assert.AreEqual( 4, result ); | ||
``` | ||
|
||
When a set of steps should cache the overload that takes a builder can be used. In this case the inital input will be used at the key and the final step of the nested pipeline will be cached | ||
|
||
```csharp | ||
// Takes a string and returns a number | ||
var command = PipelineFactory | ||
.Start<string>() | ||
.PipeCacheAsync( b => b | ||
.PipeAsync( CharacterCountAsync ) | ||
.Pipe( (ctx, arg) => arg + 100 )) | ||
.Build(); | ||
|
||
var result = await command( factory.Create( logger ), "test" ); | ||
|
||
Assert.AreEqual( 104, result ); | ||
``` | ||
|
||
## Dependacy Injection | ||
|
||
Because this uses the existing DI built into pipelines, caching can be configured with an existing cache: | ||
|
||
```csharp | ||
// Add Memory Cache | ||
services.AddMemoryCache(); | ||
|
||
// Share with the pipelines | ||
services.AddPipeline( includeAllServices: true ); | ||
``` | ||
|
||
Or defined seperately as part of the container use for the pipelines: | ||
|
||
```csharp | ||
services.AddPipeline( (factoryServices, rootProvider) => | ||
{ | ||
factoryServices.AddMemoryCache(); | ||
factoryServices.AddPipelineDefaultCacheSettings( absoluteExpirationRelativeToNow: TimeSpan.FromHours( 1 ) ) | ||
} ); | ||
``` |
26 changes: 26 additions & 0 deletions
26
src/Hyperbee.Pipline.Caching/ServiceCollectionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using Hyperbee.Pipeline.Caching; | ||
using Microsoft.Extensions.Caching.Memory; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
// ReSharper disable CheckNamespace | ||
namespace Hyperbee.Pipeline; | ||
|
||
public static class ServiceCollectionExtensions | ||
{ | ||
public static IServiceCollection AddPipelineDefaultCacheSettings( | ||
this IServiceCollection services, | ||
DateTimeOffset? absoluteExpiration = null, | ||
TimeSpan? absoluteExpirationRelativeToNow = null, | ||
CacheItemPriority priority = CacheItemPriority.Normal, | ||
PostEvictionCallbackRegistration callbackRegistration = null | ||
) | ||
{ | ||
return services.AddTransient( ( _ ) => new PipelineMemoryCacheOptions | ||
{ | ||
AbsoluteExpiration = absoluteExpiration, | ||
AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow, | ||
Priority = priority, | ||
PostEvictionCallbacks = { callbackRegistration } | ||
} ); | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
test/Hyperbee.PipelineCaching.Tests/Hyperbee.Pipeline.Caching.Tests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
|
||
<IsPackable>false</IsPackable> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="coverlet.collector" Version="6.0.0" /> | ||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> | ||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" /> | ||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" /> | ||
<PackageReference Include="NSubstitute" Version="5.1.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\src\Hyperbee.Pipeline\Hyperbee.Pipeline.csproj" /> | ||
<ProjectReference Include="..\..\src\Hyperbee.Pipline.Caching\Hyperbee.Pipeline.Caching.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Oops, something went wrong.