> ## 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.

# Cache Invalidation

> Bir kaydı güncelleyince cache nasıl düşer: domain event → RemoveByTagAsync → çoklu-instance broadcast. Yeni cache'lenebilir query nasıl eklenir.

Cache, **spec seviyesinde** etkinleştirilir ve **tag bazlı** invalidate edilir. Bir aggregate güncellendiğinde ilgili domain event bir handler'ı tetikler; handler `IHybridRequestCache.RemoveByTagAsync(tag)` çağırır. HybridCache hem L1 (in-memory) hem L2 (Redis) girdilerini düşürür; çoklu-instance kurulumda ayrıca diğer node'lara `CacheInvalidationIntegrationEvent` broadcast edilir. Bu sayede güncelleme bir node'da yapılsa da tüm node'ların L1'i temizlenir.

<Info>
  İlgili dosyalar:
  `Application/DomainEventHandlers/Faq{Updated,Created,Deleted,Reordered}/InvalidateFaqCacheDomainEventHandler.cs` ·
  `Application/SeedWork/Caching/CacheTags.cs` ·
  `Application/IntegrationEventHandlers/CacheInvalidation/CacheInvalidationIntegrationEventHandler.cs` ·
  `Domain/AggregatesModel/FaqAggregate/Specifications/PublicFaqsPagedSpecification.cs`
</Info>

## Çalışma prensibi (FAQ örneği)

Public FAQ listesi sık çağrılır, değişimi nettir; bu yüzden cache'i **hardcoded açıktır**. Spec, sorguyu tag'ler:

```csharp theme={null}
// PublicFaqsPagedSpecification
public const string CacheTag = "faqs";

Query.EnableCache(
        nameof(PublicFaqsPagedSpecification),
        filter.Page, filter.PageSize, filter.SearchTerm)
     .WithCacheTtl(TimeSpan.FromMinutes(5))
     .WithTags(CacheTag);
```

Tüm sayfa/sıralama/arama varyantları aynı `faqs` tag'i altında toplanır. Tek bir `RemoveByTagAsync("faqs")` hepsini lazy invalidate eder. Tag string'i tek yerde sabitlenir ki spec ile handler typo'suz paylaşsın:

```csharp theme={null}
// CacheTags
public static class CacheTags
{
    public const string Faqs = PublicFaqsPagedSpecification.CacheTag;
}
```

## Sequence — güncelleme + çoklu-instance broadcast

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant ADM as Admin
    participant N1 as API Node #1
    participant DB as PostgreSQL
    participant HC as HybridCache (L1+L2 Redis)
    participant MQ as RabbitMQ
    participant N2 as API Node #2

    ADM->>N1: PUT /api/admin/faqs/{id}
    N1->>N1: Command handler → faq.Update(...) → FaqUpdatedDomainEvent
    N1->>DB: SaveEntitiesAsync (SaveChanges)
    DB->>N1: domain event dispatch
    N1->>N1: InvalidateFaqCacheDomainEventHandler
    N1->>HC: RemoveByTagAsync("faqs")
    HC->>HC: L1 (local) + L2 (Redis) düşür
    HC->>MQ: CacheInvalidationIntegrationEvent { tags:["faqs"], SourceNodeId: N1 }
    ADM-->>N1: 200
    MQ->>N2: Consume
    N2->>N2: CacheInvalidationIntegrationEventHandler
    N2->>N2: SourceNodeId == self ? skip (echo) : RemoveByTagLocallyAsync("faqs")
    Note over N1,N2: Sonraki public FAQ read → DB → L2 + L1 repopulate
```

## Adım adım — invalidate

<Steps>
  <Step title="Aggregate güncellenir, event yayılır">
    Komut handler aggregate metodunu çağırır; metot içeride `AddDomainEvent(new FaqUpdatedDomainEvent(faqId, isActive))` biriktirir. `SaveEntitiesAsync` sonrası event dispatch edilir. Event yalın bir veri taşıyıcısıdır:

    ```csharp theme={null}
    public class FaqUpdatedDomainEvent : DomainEvent, INotification
    {
        public Guid FaqId    { get; }
        public bool IsActive { get; }
        public FaqUpdatedDomainEvent(Guid faqId, bool isActive) { FaqId = faqId; IsActive = isActive; }
    }
    ```
  </Step>

  <Step title="Handler tag'i düşürür">
    `InvalidateFaqCacheDomainEventHandler` event'i dinler ve tag'i temizler. Hata cache invalidasyonunu fail-safe yapar — loglanır ama isteği bozmaz:

    ```csharp theme={null}
    public sealed class InvalidateFaqCacheDomainEventHandler
        : INotificationHandler<FaqUpdatedDomainEvent>
    {
        private readonly IHybridRequestCache _cache;
        // ctor ...

        public async Task Handle(FaqUpdatedDomainEvent e, CancellationToken ct)
        {
            try
            {
                await _cache.RemoveByTagAsync(CacheTags.Faqs, ct);
                _logger.LogDebug("[CacheInvalidate] FaqUpdated → {Tag} dropped (faqId={FaqId})", CacheTags.Faqs, e.FaqId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "[CacheInvalidate] FaqUpdated failed: faqId={FaqId}", e.FaqId);
            }
        }
    }
    ```

    <Note>
      Aynı tag için ayrı handler dosyaları vardır: `FaqCreated`, `FaqUpdated`, `FaqDeleted`, `FaqReordered`. Hepsi `Faqs` tag'ini düşürür. Konvansiyon: handler'lar `Application/DomainEventHandlers/<EventName>/InvalidateFaqCacheDomainEventHandler.cs` altında, tek class tek dosya.
    </Note>
  </Step>

  <Step title="Çoklu-instance broadcast">
    `RemoveByTagAsync` lokal L1 + L2'yi düşürür ve `CacheInvalidationIntegrationEvent`'i (`Cache:BroadcastTagInvalidation` açıksa) yayar. Diğer node'lar bunu tüketip **yalnızca kendi L1**'lerini temizler. Echo döngüsü `SourceNodeId` ile engellenir:

    ```csharp theme={null}
    public async Task Handle(CacheInvalidationIntegrationEvent @event)
    {
        if (@event.SourceNodeId == EventBusRemoteTagBroadcaster.NodeId)
            return; // kendi yayınımız — atla (echo guard)

        foreach (var tag in @event.Tags)
            await _cache.RemoveByTagLocallyAsync(tag);  // sadece local L1, tekrar broadcast YOK
    }
    ```

    `RemoveByTagLocallyAsync` tekrar broadcast yapmaz; bu da sonsuz döngüyü önler. Detay: [Çoklu-instance cache](/caching/multi-instance).
  </Step>
</Steps>

<Warning>
  Kayıt sırası kritiktir: `AddRedisCache` (IDistributedCache) **önce**, sonra `AddCache`, sonra `AddHybridCache`. Aksi halde L2 (Redis) devre dışı kalır ve cache yalnızca L1-only çalışır — node'lar arası tutarsızlık oluşur. Bkz. [HybridCache](/caching/hybrid-cache).
</Warning>

## Yeni bir cache'lenebilir query eklemek

Yeni bir public/sık-okunan liste için cache açma sırası:

<Steps>
  <Step title="Spec'te cache'i etkinleştir">
    İlgili `Specification<T>` içinde sorguyu tag'leyin:

    ```csharp theme={null}
    public sealed class PublicCentersPagedSpecification : Specification<Center>
    {
        public const string CacheTag = "centers";

        public PublicCentersPagedSpecification(PublicCentersFilter filter)
        {
            Query.Where(c => c.DeletedAt == null && c.IsActive);
            // ... where / orderby / paging ...
            Query.AsNoTracking();
            Query.EnableCache(nameof(PublicCentersPagedSpecification), filter.Page, filter.PageSize, filter.SearchTerm)
                 .WithCacheTtl(TimeSpan.FromMinutes(5))
                 .WithTags(CacheTag);
        }
    }
    ```
  </Step>

  <Step title="Tag'i CacheTags'e ekle">
    ```csharp theme={null}
    public static class CacheTags
    {
        public const string Faqs    = PublicFaqsPagedSpecification.CacheTag;
        public const string Centers = PublicCentersPagedSpecification.CacheTag; // yeni
    }
    ```

    Tek kaynak: spec'teki `const` string'i referans alın, elle string yazmayın.
  </Step>

  <Step title="Domain event(ler)i yay">
    `Center` aggregate'inin değiştiren metodlarında ilgili domain event'i (`CenterUpdatedDomainEvent` vb.) `AddDomainEvent` ile yayın. Bkz. [Yeni Domain Event](/playbooks/new-domain-event).
  </Step>

  <Step title="Invalidation handler'ı yaz">
    `Application/DomainEventHandlers/CenterUpdated/InvalidateCenterCacheDomainEventHandler.cs`:

    ```csharp theme={null}
    public sealed class InvalidateCenterCacheDomainEventHandler
        : INotificationHandler<CenterUpdatedDomainEvent>
    {
        private readonly IHybridRequestCache _cache;
        public async Task Handle(CenterUpdatedDomainEvent e, CancellationToken ct)
            => await _cache.RemoveByTagAsync(CacheTags.Centers, ct);
    }
    ```

    Read query bir sonraki çağrıda DB'den taze yüklenip yeniden cache'lenir.
  </Step>
</Steps>

<Note>
  Şu an Bağış/Etkinlik public listelerinde cache **bilinçli olarak yoktur** — değişiklikler anında görünsün diye. Ölçek gerektiğinde aynı reçete (spec'te `EnableCache + WithTags`, `CacheTags`'e tag, invalidation handler) uygulanır.
</Note>

## İlgili

<CardGroup cols={2}>
  <Card title="HybridCache" icon="layer-group" href="/caching/hybrid-cache">
    L1/L2 yapısı ve DI kayıt sırası.
  </Card>

  <Card title="Tag Invalidation" icon="tags" href="/caching/invalidation">
    Tag mekaniği derinlemesine.
  </Card>

  <Card title="Çoklu-instance" icon="server" href="/caching/multi-instance">
    Broadcast ve echo guard.
  </Card>

  <Card title="Yeni Domain Event" icon="diagram-project" href="/playbooks/new-domain-event">
    Invalidation'ı tetikleyen event'i ekleme.
  </Card>
</CardGroup>
