-
-
Save IEvangelist/43832e769fb742bcfb2c20d8446a9cd2 to your computer and use it in GitHub Desktop.
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)); | |
} |
Thoughts:
- serialization choice is hugely subjective; I would be very cautious asserting JSON, or even any specific JSON serializer, as a choice
- 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)
- if we are looking to revisit the API,
byte[]
is simply a bad choice - we should preferReadOnlyMemory<byte>
orReadOnlySequence<byte>
- likewise, for writing we should probably think in terms ofIBufferWriter<byte>
- 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
- 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"
- 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 preferReadOnlyMemory<byte>
orReadOnlySequence<byte>
- likewise, for writing we should probably think in terms ofIBufferWriter<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.
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.