Skip to content

Latest commit

 

History

History
222 lines (146 loc) · 12.2 KB

MicrosoftHybridCache.md

File metadata and controls

222 lines (146 loc) · 12.2 KB

FusionCache logo

Ⓜ️ Microsoft HybridCache Support

⚡ TL;DR (quick version)
FusionCache can ALSO be used as an implementation of the new HybridCache abstraction from Microsoft, with the added extra features of FusionCache. Oh, and it's the first production-ready implementation of HybridCache (see below).

Note

FusionCache is the FIRST 3rd party implementation of HybridCache from Microsoft. But not just that: in a strange turn of events, since at the time of this writing (Jan 2025) Microsoft has not yet released their default implementation, FusionCache is the FIRST production-ready implementation of HybridCache AT ALL, including the one by Microsoft itself. Quite bonkers 😬

With .NET 9 Microsoft introduced their own hybrid cache, called HybridCache.

This of course sparked a lot of questions about what I thought about it, and the future of FusionCache.

If you like you can read my thoughts about it, but the main take away is:

FusionCache will NOT leverage the new HybridCache, in the sense that it will not be built "on top of it", BUT it will do 2 main things:

  1. be available ALSO as an implementation of HybridCache
  2. it may take advantage of some of the new underlying bits being created FOR it

You see, the nice thing is that Microsoft introduced not just a (default) implementation (which as of this writing, Jan 2025, has not been released yet) but also a shared abstraction that anyone can implement.

🖼️ Abstractions

Ok so HybridCache is first and foremost an abstraction, and?

This may turn the HybridCache abstract class into some sort of "lingua franca" for a basic set of common features for all hybrid caches in .NET.

So FusionCache is available ALSO as an implementation of HybridCache, via an adapter class.

Ok cool, but how?

Easy peasy:

services.AddFusionCache()
  .AsHybridCache(); // MAGIC

When setting up FusionCache in our Startup.cs file, we simply add .AsHybridCache(), that's it.

Now, every time we'll ask for HybridCache via DI (taken as-is from the official Microsoft docs):

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            cancellationToken: token
        );
    }

    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

we'll be using in reality FusionCache underneath acting as HybridCache, all transparently.

Oh, and we'll still be able to get IFusionCache too at the same time, so another SomeService2 in the same app, similarly as the above example, can do this:

public class SomeService2(IFusionCache cache)
{
    private IFusionCache _cache = cache;
    
    // ...

and the SAME FusionCache instance will be used for both, directly as well as via the HybridCache adapter.

As FusionCache users this means we'll have 2 options available:

  • use FusionCache directly, as we did up until today
  • depend on the HybridCache shared abstraction by Microsoft, but use the FusionCache implementation (the adapter)

Actually, as said, we can do them both at the same time, in the same app: if there are components that depend on the HybridCache abstraction we can use the adapter for them, and if we want more power and more control in our own code we can use FusionCache directly, all while sharing the same underlying data.

Basically, we register FusionCache (eg: .AddFusionCache()), make it also available as HybridCache (eg: .AsHybridCache()), and use what we want based on the need.

Also, when using the adapter based on FusionCache, we'll have more features anyway.

Yep, more features: read on.

Important

Again, to be clear, this does NOT mean that FusionCache will now be based ON HybridCache from Microsoft, but that it will ALSO be available AS an implementation of it, via an adapter class.

🆎 Feature Comparison

Let's see which features are on the table.

For the Microsoft implementation, the features will be:

FusionCache on the other hand has more, like:

🚀 I Want Moar

Being able to use FusionCache "as" HybridCache means we'll also have the power of FusionCache itself, even when using it via the HybridCache abstraction.

This includes the resiliency of fail-safe, the speed of soft/hard timeouts and eager-refresh, the automatic synchronization of the backplane, the self-healing power of auto-recovery, the full observability of the native OpenTelemetry support and more.

Oh (x2), and we'll be even able to read and write from BOTH at the SAME TIME, fully protected from Cache Stampede! Yup, this means that when doing hybridCache.GetOrCreateAsync("foo", ...) at the same time as fusionCache.GetOrSetAsync("foo", ...), they both will do only ONE database call, at all, among the 2 of them.

Oh (x3), and since FusionCache supports both the sync and async programming model in a unified way (while HybridCache only supports the async one), this also means that Cache Stampede protection and every other feature will work perfectly well even when calling at the same time:

  • hybridCache.GetOrCreateAsync("foo", ...) (async call from the HybridCache adapter)
  • fusionCache.GetOrSet("foo", ...) (sync call from FusionCache directly)

They'll be both protected from Cache Stampede automatically, but not just separately, but also between themselves: this means that accross both the HybridCache adapter instance and the FusionCache instance, only 1 database call will be executed, total.

Nice 😬

🚀 I Said Moar!

Ok, here's something crazy to think about.

The HybridCache implementation from Microsoft currently available (it's in preview, the GA is not out yet) has some limitations, in particular:

  • NO L2 OPT-OUT: there's no way to control the use of IDistributedCache or not. If it's registered in the DI container it will be used, otherwise it will not, meaning if another component needs it, you'll be then forced to use it in HybridCache too
  • SINGLE INSTANCE: it does not support multiple named caches, there can be only one
  • NO KEYED SERVICES: since it does not support multiple caches, it means it cannot support Microsoft's own Keyed Services

Now these are the limitation of the (current) HybridCache implementation , not the abstraction: does this mean that when using FusionCache via the HybridCache adapter, we can go above and beyound those limits?

Yup 🎉

First: since the registration is the one for FusionCache, this means we have total control over what components to use (see the builder here), so we are not forced to use an L2 just because there's an IDistributedCache registered in the DI container.

Second: thanks to the Named Caches feature of FusionCache, we can register more than one, and each of them can be exposed via the adapter.

But how can we access them separately in our code?

Easy, via Keyed Services!

Instead of registering it like this:

services.AddFusionCache()
	.AsHybridCache();

and then request it via serviceProvider.GetRequiredService<HybridCache>() or via a param injection like this:

public class SomeService(HybridCache cache) {
    ...
}

we can register it like this:

services.AddFusionCache()
	.AsKeyedHybridCache("Foo");

and then request it via serviceProvider.GetRequiredKeyedService<HybridCache>("Foo") or via a param injection like this:

public class SomeService([FromKeyedServices("Foo")] HybridCache cache) {
    ...
}

And is it possible to do both at the same time? Of course, simply register one FusionCache with .AsHybridCache() and or more with .AsKeyedHybridCache(...), that's it.

Boom!

🚳 Limitations

The HybridCache API surface area is more limited: for example for each GetOrCreateAsync() call we can pass a HybridCacheEntryOptions object instead of a FusionCacheEntryOptions object.

Because of this, when using FusionCache via the HybridCache adapter we can configure all of this goodness only at startup, and not on a per-call basis: still, it's a lot of power to have available for when we need or want to depend on the Microsoft abstraction.

But there's more: the HybridCacheEntryOptions type already allows for some level of flexibility, like controlling Memory/Distributed Read/Write per-call. Therefore the FusionCache adapter automatically maps all of the options to the corresponding ones in FusionCache, and will work flawlessly.

As an example, using HybridCacheEntryFlags.DisableLocalCacheRead in the HybridCacheEntryOptions becomes SkipMemoryCacheRead in FusionCacheEntryOptions, again all automatically.

🙏 Microsoft (and Marc) and OSS

To me, this can be a good example of what it may look like when Microsoft and the OSS community have a constructive dialog.

First and foremost many thanks to the HybridCache lead @mgravell for the openness, the back and forth and the time spent reading my mega-comments.

I think this can be a really good starting point, and future endeavours by Microsoft to come up with new core components already existing in the OSS space should go even beyond, and be even more collaborative: frequent meetings between the maintainers of the main OSS packages in that space, to be all aligned and have a shared vision while respecting each other's work.

With that, Microsoft can provide - if it makes sense in each case - a basic default implementation but even more importantly a shared abstraction, which btw must be designed to allow augmentation by the OSS alternatives: in doing so Microsoft must inevitably accept strong inputs from the OSS community to do this well, and yes I know it takes time and resources, but imho it's the only way to make it work.

Then it should also give visibility to the OSS alternatives (in the main docs, samples, videos, etc), and encourage all .NET users to discover and try the alternatives: in doing so they will not lose anything, and instead in turn the .NET ecosystem as a whole will thrive.

In the past this has not always been the case, but the future may be different.

Just my 2 cents.