Ocelot ETag Caching

Predpokladám, že na Slovensku / v Čechách nie je moc ľudí čo využívajú Ocelot API Gateway, ale keď už teda mám napísaný post, tak ho hodím aj sem 😊.

Knižnica Kros.Ocelot.ETagCaching prináša do Ocelot API Gatewaya ETag kešovanie. ETag kešovanie je mechanizmus, ktorý umožňuje klientom overiť, či sú údaje uložené v keši stále aktuálne, bez toho, aby museli celý zdroj znovu stiahnuť. Ak sa údaje nezmenili, server odpovie so stavom 304 Not Modified a vyzve klienta aby použil údaje z keše, čím sa ušetrí bandwidth a zníži zaťaženie servera.

Prečo používať ETag kešovanie?

ETag kešovanie je obzvlášť užitočné v scenároch, v ktorých sa údaje menia zriedkavo, ale sú často požadované. Optimalizuje využitie siete a zlepšuje čas odozvy. Použitím ETag si klient môže zabezpečiť, že bude mať vždy k dispozícii najnovšiu verziu údajov.

Ako to funguje?

Pri ukladaní do keše sa používajú dve hlavičky HTTP:

  • ETag: Jedinečný identifikátor, zvyčajne guid alebo hash.
  • cache-control: Dáva klientovi pokyn, že odpoveď sa môže ukladať do keše. Pre ukladanie do keše by mala byť nastavená na hodnotu private.

Keď klient dostane odpoveď s týmito hlavičkami, uloží údaje a hodnotu ETag do keše. Pri ďalších požiadavkách klient odošle hlavičku If-None-Match s hodnotou ETag. Server potom porovná ETag s aktuálnou verziou údajov. Ak sa zhodujú, server vráti stav 304 Not Modified, čo znamená, že klient by mal použiť údaje uložené v medzipamäti.

Implementácia v Ocelot

Knižnica Kros.Ocelot.ETagCaching sa využíva Ocelot middlewares. Sama zabezpečí generovanie, ukladanie a overovanie ETag. Tu je návod, ako začať:

Konfigurácia Ocelot

Nakonfiguruj smerovania v súbore ocelot.json a špecifikuj politiky pre kešovanie:

{
    "Routes": [
        {
            "Key": "getAllProducts",
            "DownstreamPathTemplate": "/api/products/",
            "UpstreamPathTemplate": "/products/",
            "CachePolicy": "getAllProducts" // 👈 Cache policy key
        },
        {
            "Key": "getProduct",
            "DownstreamPathTemplate": "/api/products/{id}",
            "UpstreamPathTemplate": "/products/{id}",
            "CachePolicy": "getProduct" // 👈 Cache policy key
        },
        {
            "Key": "deleteProduct",
            "DownstreamPathTemplate": "/api/products/{id}",
            "UpstreamPathTemplate": "/products/{id}",
            "InvalidateCachePolicy": "invalidateProductCachePolicy"
        }
    ]
}

Program.cs Configuration

Nakonfiguruj pravidlá Program.cs:

// 👇 Define policies
builder.Services.AddOcelotETagCaching((c) =>
{
    //  👇 Simple policy with expiration and tag templates
    c.AddPolicy("getAllProducts", p =>
    {
        p.Expire(TimeSpan.FromMinutes(5));
        p.TagTemplates("products:{tenantId}", "all", "tenantAll:{tenantId}");
    });

    // 👇 Policy with custom cache key, ETag generator, and cache control
    c.AddPolicy("getProduct", p =>
    {
        p.Expire(TimeSpan.FromMinutes(5));
        p.TagTemplates("product:{tenantId}:{id}", "tenant:{tenantId}:all", "all");
        p.CacheKey(context => context.Request.Headers.GetValues("id").FirstOrDefault());
        p.ETag(context => new($""{Guid.NewGuid()}""));
        p.CacheControl(new() { Public = false });
    });
});

A pridaj middleware.

app.UseOcelot(c => { // 👇 Pridať middleware caching ETag c.AddETagCaching(); }).Wait();

Tag templates a invalidovanie keše

Tag templates sa používajú na generovanie tagov na základe parametrov požiadavky, čo uľahčuje invalidovanie konkrétnych položiek keše. Napríklad pre cestu /api/{tenantId}/products/{id} a tag template product:{tenantId}:{id} bude tag product:1:2.

Automatické invalidovanie keše

Definujte policy pre invalidáciu v ocelot.json:

{
    "Key": "deleteProduct",
    "UpstreamHttpMethod": [ "Delete" ],
    "DownstreamPathTemplate": "/api/products/{id}",
    "UpstreamPathTemplate": "/products/{id}",
    "InvalidateCachePolicy": "invalidateProductCachePolicy" 
    // 👆 Invalidate cache policy key
}

A nakonfiguruj ich v súbore Program.cs:

builder.Services.AddOcelotETagCaching(conf =>
{
    // 👇 Define invalidate cache policy
    conf.AddInvalidatePolicy("invalidateProductCachePolicy", builder =>
    {
        // 👇 Define tag templates to invalidate
        builder.TagTemplates("product:{tenantId}", "product:{tenantId}:{id}");
    });
});

Manuálne invalidovanie

Ručne môžeš zneplatniť záznamy keše pomocou IOutputCacheStore:

public class ProductsService {
    private readonly IOutputCacheStore _outputCacheStore;

    public ProductsService(IOutputCacheStore outputCacheStore)
    {
        _outputCacheStore = outputCacheStore;
    }

    public async Task DeleteProduct(int tenantId, int id)
    {
        await _outputCacheStore.InvalidateAsync(
            $"product:{tenantId}", $"product:{tenantId}:{id}");
        // 👆 Invalidate cache entries by tags            
    }
}

Redis Integration

V predvolenom nastavení knižnica používa InMemoryCacheStore, ale môžeš ju nakonfigurovať tak, aby používala Redis na distribuované ukladanie do keše:

builder.Services.AddStackExchangeRedisOutputCache(options =>
{
    options.Configuration 
        = builder.Configuration.GetConnectionString("MyRedisConStr");
    options.InstanceName = "SampleInstance";
});

Sources