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.
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);}
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.
Sıra bozulursa (AddHybridCache önce, AddRedisCache sonra) HybridCacheIDistributedCache’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ı.
AddCache ne register eder:
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;}
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:
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):
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); }}
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.
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.