Skip to content

Instantly share code, notes, and snippets.

@IEvangelist
Last active April 29, 2023 00:23
Show Gist options
  • Save IEvangelist/43832e769fb742bcfb2c20d8446a9cd2 to your computer and use it in GitHub Desktop.
Save IEvangelist/43832e769fb742bcfb2c20d8446a9cd2 to your computer and use it in GitHub Desktop.
Proposed new APIs for the `IDistributedCache` interface.
public interface IDistributedCache
{
T? GetOrCreate(string key, Func<DistributedCacheEntryOptions, T> factory)
{
var bytes = Get(key);
if (bytes is { Length: > 0 })
{
var payload = Encoding.UTF8.GetString(bytes);
return JsonSerializer.Deserialize<T>(payload);
}
var options = new DistributedCacheEntryOptions();
var value = factory(options);
Set(key, value, options);
return value;
}
byte[]? Get(string key);
async Task<T?> GetOrCreateAsync(
string key,
Func<DistributedCacheEntryOptions, Task<T>> factory,
CancellationToken token = default(CancellationToken))
{
var bytes = await GetAsync(key, token);
if (bytes is { Length: > 0 })
{
var payload = Encoding.UTF8.GetString(bytes);
return JsonSerializer.Deserialize<T>(payload);
}
var options = new DistributedCacheEntryOptions();
var value = await factory(options);
await SetAsync(key, value, options, token);
return value;
}
Task<byte[]?> GetAsync(string key, CancellationToken token = default(CancellationToken));
void Set(string key, byte[] value, DistributedCacheEntryOptions options);
Task SetAsync(
string key,
byte[] value,
DistributedCacheEntryOptions options,
CancellationToken token = default(CancellationToken));
void Refresh(string key);
Task RefreshAsync(string key, CancellationToken token = default(CancellationToken));
void Remove(string key);
Task RemoveAsync(string key, CancellationToken token = default(CancellationToken));
}
@mbrdev
Copy link

mbrdev commented Apr 28, 2023

When I implement methods like this on IMemoryCache I use a SemaphoreSlim when adding an item to the cache, would this need something similar?

@mgravell
Copy link

Thoughts:

  1. serialization choice is hugely subjective; I would be very cautious asserting JSON, or even any specific JSON serializer, as a choice
  2. making serialization pluggable is ... fun; honestly, I think it would be better to handle serialization at a layer above - perhaps abstracting away the storage layer (that just talks bytes) from the functional layer (with services such as local cache store, perhaps serialization, etc)
  3. if we are looking to revisit the API, byte[] is simply a bad choice - we should prefer ReadOnlyMemory<byte> or ReadOnlySequence<byte> - likewise, for writing we should probably think in terms of IBufferWriter<byte>
  4. we should think "cache invalidation"; for example, the .NET 7 output cache API supports "tags" as a concept allowing cross-key cache invalidation; whatever logic gave rise to that requirement presumably should be considered an over-arching consideration in caching
  5. ditto "etag"; in particular, I'm thinking in terms of output cache here; currently, to implement etag, we need to fetch the local cache locally; this allows us to short-circuit the app-to-client connection, but still forces us to exercise the storage-to-app connection; ideally, we should be able to have a "here's my etag; if it is a match, don't bother even fetching things locally - we're going to short-circuit another way"
  6. I'd also want to think ahead to multi-layer caching, in particular thinking in terms of things like server-assisted-caching in the redis sense, and other such mechanisms

The topic of cache and revising has come up from multiple directions in the last few weeks; I probably have quite a few opinions to add there (from the combined perspectives of: aspnet cache, storage, serialization, multi-tier, etc); please feel free to rope me in to conversations. Likewise, let me know if I can loop you in with any of the other parallel discussions (or talk to Glenn)

@IEvangelist
Copy link
Author

When I implement methods like this on IMemoryCache I use a SemaphoreSlim when adding an item to the cache, would this need something similar?

Hi @mbrdev - potentially, that's not a bad idea. At that point, we wouldn't be able to do the default interface implementation approach. Unless the default is a simpler one like proposed here, then current implementations could optionally implement this, overriding the default - and provide those types of mechanisms.

@IEvangelist
Copy link
Author

IEvangelist commented Apr 28, 2023

Thoughts:

  1. serialization choice is hugely subjective; I would be very cautious asserting JSON, or even any specific JSON serializer, as a choice
  2. making serialization pluggable is ... fun; honestly, I think it would be better to handle serialization at a layer above - perhaps abstracting away the storage layer (that just talks bytes) from the functional layer (with services such as local cache store, perhaps serialization, etc)
  3. if we are looking to revisit the API, byte[] is simply a bad choice - we should prefer ReadOnlyMemory<byte> or ReadOnlySequence<byte> - likewise, for writing we should probably think in terms of IBufferWriter<byte>
  4. we should think "cache invalidation"; for example, the .NET 7 output cache API supports "tags" as a concept allowing cross-key cache invalidation; whatever logic gave rise to that requirement presumably should be considered an over-arching consideration in caching
  5. ditto "etag"; in particular, I'm thinking in terms of output cache here; currently, to implement etag, we need to fetch the local cache locally; this allows us to short-circuit the app-to-client connection, but still forces us to exercise the storage-to-app connection; ideally, we should be able to have a "here's my etag; if it is a match, don't bother even fetching things locally - we're going to short-circuit another way"
  6. I'd also want to think ahead to multi-layer caching, in particular thinking in terms of things like server-assisted-caching in the redis sense, and other such mechanisms

The topic of cache and revising has come up from multiple directions in the last few weeks; I probably have quite a few opinions to add there (from the combined perspectives of: aspnet cache, storage, serialization, multi-tier, etc); please feel free to rope me in to conversations. Likewise, let me know if I can loop you in with any of the other parallel discussions (or talk to Glenn)

Hi @mgravell,

serialization choice is hugely subjective; I would be very cautious asserting JSON, or even any specific JSON serializer, as a choice

Absolutely! I agree, but as a default, why not use the System.Text.Json? To your point this could be "pluggable", but as a starting point, it seems like a reasonable default.

if we are looking to revisit the API, byte[] is simply a bad choice - we should prefer ReadOnlyMemory<byte> or ReadOnlySequence<byte> - likewise, for writing we should probably think in terms of IBufferWriter<byte>

The byte[] is one of the things that got me looking at this API in the first place, it could benefit from ReadOnlyMemory<byte> or ReadOnlySequence<byte>, but thinking about the consumer standpoint, the convenience of generics and custom strong-types (POCOs and the like) are really appealing.

we should think "cache invalidation"; for example, the .NET 7 output cache API supports "tags" as a concept allowing cross-key cache invalidation; whatever logic gave rise to that requirement presumably should be considered an over-arching consideration in caching

I do like what has been implemented for output caching with ASP.NET Core, and I agree that "cache invalidation" should also exist in this API.

I'd love to get more involved with some of these discussions, even if only to be a fly on the wall and listen/learn. Thanks for thinking of me in that regard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment