> ## Documentation Index
> Fetch the complete documentation index at: https://docs.diyanet.pro/llms.txt
> Use this file to discover all available pages before exploring further.

# Hybrid Cache

> IHybridRequestCache API, kayıt sırası, stampede koruması ve spec tabanlı CachedRepository cache

## `IHybridRequestCache`

Uygulama katmanının cache arayüzü. `HybridCache`'i doğrudan inject etmek yerine bu interface kullanılır — multi-instance broadcast hook'u ve domain-friendly imza için. Tanım: `src/BuildingBlocks/BuildingBlocks.Caching/IHybridRequestCache.cs`.

```csharp theme={null}
public interface IHybridRequestCache
{
    // Cache'te varsa döner, yoksa factory'yi çağırıp doldurur (stampede-safe).
    ValueTask<T> GetOrCreateAsync<T>(
        string key,
        Func<CancellationToken, ValueTask<T>> factory,
        HybridCacheEntryOptions? options = null,
        IReadOnlyCollection<string>? tags = null,
        CancellationToken cancellationToken = default);

    // Tek key'i hem L1 hem L2'den siler (diğer instance'ların L1'ini ETKİLEMEZ).
    ValueTask RemoveAsync(string key, CancellationToken cancellationToken = default);

    // Tag'e bağlı tüm entry'leri invalidate eder + broadcast (BroadcastTagInvalidation açıksa).
    ValueTask RemoveByTagAsync(string tag, CancellationToken cancellationToken = default);
    ValueTask RemoveByTagsAsync(IReadOnlyCollection<string> tags, CancellationToken cancellationToken = default);

    // Broadcast'ten gelen istekleri uygular — sadece local, tekrar broadcast YAPMAZ (loop önleme).
    ValueTask RemoveByTagLocallyAsync(string tag, CancellationToken cancellationToken = default);
}
```

`RemoveByTagAsync` vs `RemoveByTagLocallyAsync` farkı için: [Invalidation](/caching/invalidation) ve [Multi-Instance](/caching/multi-instance).

## Stampede koruması

`HybridCache.GetOrCreateAsync` aynı key için paralel gelen istekleri tek factory çağrısında birleştirir: cache miss anında ilk istek factory'yi (DB sorgusu) çalıştırır, diğerleri sonucu bekler. Bu sayede cold cache + yüksek trafik durumunda DB'ye yüzlerce eşzamanlı sorgu gitmez. Davranış `HybridCache` infrastructure'ından gelir; ek kod gerektirmez.

## Kayıt sırası KRİTİK

`HybridCache`, L2 backend'ini (`IDistributedCache`, Redis) **kayıt anında** keşfeder. Bu yüzden Redis önce register edilmiş olmalı:

```csharp theme={null}
// Program.cs — bu sırayla olmalı
builder.Services.AddRedisCache(builder.Configuration);   // 1) IDistributedCache (Redis)
builder.Services.AddCache(builder.Configuration);        // 2) IHybridRequestCache + CacheOptions + noop broadcaster
// ...
builder.Services.AddHybridCache(options =>               // 3) HybridCache (L2'yi keşfeder)
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        LocalCacheExpiration = cacheConfig.L1.Ttl,   // Cache:L1:Ttl (1 dk)
        Expiration           = cacheConfig.L2.Ttl    // Cache:L2:Ttl (5 dk)
    };
});
```

<Warning>
  Sıra bozulursa (`AddHybridCache` önce, `AddRedisCache` sonra) `HybridCache` `IDistributedCache`'i göremez ve **L1-only mode**'a düşer — Redis hiçbir zaman dolmaz, multi-instance'da her node ayrı veri tutar. `AddCache` metodu (`BuildingBlocks.Caching/DependencyInjection.cs`) bilerek `HybridCache` register etmez; bu sorumluluk Program.cs'e bırakılır çünkü L2 backend önce hazır olmalı.
</Warning>

`AddCache` ne register eder:

```csharp theme={null}
public static IServiceCollection AddCache(this IServiceCollection services, IConfiguration configuration)
{
    services.Configure<CacheOptions>(o => configuration.GetSection("Cache").Bind(o));
    services.AddMemoryCache();
    services.AddSingleton<IHybridRequestCache, HybridRequestCache>();
    // Default: noop. Multi-instance'da Application katmanı bunu MassTransit impl'i ile değiştirir.
    services.TryAddSingleton<IRemoteTagBroadcaster, NoopRemoteTagBroadcaster>();
    return services;
}
```

## Spec tabanlı cache — `CachedRepository<T>`

`CachedRepository<T>` (`src/.../Infrastructure.EFCore/Repositories/CachedRepository.cs`), `IReadRepository<T>` üzerinde bir HybridCache decorator'üdür. Read path'i spec cache açıksa otomatik cache'ler:

```csharp theme={null}
public async Task<List<T>> ListAsync(ISpecification<T> spec, CancellationToken ct = default)
{
    if (CacheDisabled(spec))
        return await _source.ListAsync(spec, ct);   // raw EF Core

    var key = $"{spec.CacheKey}:List";
    return await _cache.GetOrCreateAsync(
        key,
        async c => await _source.ListAsync(spec, c),
        BuildOptions(spec),   // TTL: spec.WithCacheTtl ?? CacheOptions.L2.Ttl
        TagsOf(spec),         // spec.WithTags(...)
        ct);
}
```

Spec'te cache'i şu fluent zincirle açarsın (Ardalis `EnableCache` + projeye özel `WithCacheTtl`/`WithTags`):

```csharp theme={null}
public sealed class PublicFaqsPagedSpecification : Specification<Faq>
{
    public const string CacheTag = "faqs";

    public PublicFaqsPagedSpecification(PublicFaqsFilter filter)
    {
        Query.Where(f => f.DeletedAt == null);
        Query.Where(f => f.IsActive);
        // ... filtre + sıralama + paging ...
        Query.AsNoTracking();

        // EFCore-layer cache. Tüm sayfa/sort/search varyantları aynı tag altında
        // toplanır → tek RemoveByTagAsync("faqs") hepsini lazy invalidate eder.
        Query.EnableCache(
                nameof(PublicFaqsPagedSpecification),
                filter.Page, filter.PageSize, filter.SearchTerm)
            .WithCacheTtl(TimeSpan.FromMinutes(5))
            .WithTags(CacheTag);
    }
}
```

<Note>
  `WithCacheTtl` adı bilinçli seçilmiştir — Ardalis'in `WithTimeToLive`'i ile ambiguous reference olmasın diye. TTL/tag bilgisi spec instance'ına `ConditionalWeakTable` üzerinden attach edilir (`SpecificationExtensions`); spec GC olunca metadata da serbest kalır.
</Note>

`EnableCache(name, args...)` zinciri spec'in `CacheKey` + `CacheEnabled` property'lerini set eder (Ardalis 9.x). `args` cache key'in parçası olur — yani her sayfa/arama varyantı ayrı entry, ama hepsi aynı `faqs` tag'i altında.

## Cache ne zaman devre dışı?

`CachedRepository.CacheDisabled(spec)` üç koşuldan biri sağlanırsa raw EF Core'a düşer:

```csharp theme={null}
private bool CacheDisabled<TSpec>(TSpec spec) where TSpec : ISpecification<T>
    => !_options.CurrentValue.Enabled       // global Cache:Enabled = false (kill-switch)
       || !spec.CacheEnabled                 // spec EnableCache çağırmamış
       || string.IsNullOrWhiteSpace(spec.CacheKey);
```

| Koşul                         | Sonuç                                                                                             |
| ----------------------------- | ------------------------------------------------------------------------------------------------- |
| `Cache:Enabled = false`       | Tüm spec cache bypass (emergency kill-switch). Auth/UserContext/JWKS/PKCE cache'lerini etkilemez. |
| Spec `EnableCache` çağırmamış | O query cache'lenmez (örn. admin spec'leri — anlık değişiklik görünsün).                          |
| `CacheKey` boş                | Cache'lenmez.                                                                                     |

Ayrıca `AnyAsync` ve spec'siz çağrılar (`ListAsync()`, `GetByIdAsync(...)`) bilerek passthrough'tur — cache istiyorsan `CompositeSpecification` + `EnableCache` kullan.

## İlgili

<CardGroup cols={2}>
  <Card title="Invalidation" icon="trash" href="/caching/invalidation">
    Tag düşürme: domain event handler'da `RemoveByTagAsync`.
  </Card>

  <Card title="Multi-Instance Senkron" icon="network-wired" href="/caching/multi-instance">
    `RemoveByTagLocallyAsync` + echo guard.
  </Card>

  <Card title="Specification" icon="filter" href="/building-blocks/specification">
    Ardalis.Specification ve cache zinciri.
  </Card>

  <Card title="Cache Mimarisi" icon="layer-group" href="/caching/overview">
    Genel bakış ve konfigürasyon.
  </Card>
</CardGroup>
