# Kanject.Core.CacheDb

A cache abstraction — `ICacheDb` — with three interchangeable providers: DynamoDB, S3 Express One Zone, and in-memory. Store strings or typed objects with an absolute expiry, read them back with a single `TryGet`, and namespace every key for per-stage isolation. Swap providers by swapping the registration call.

## Providers

- **DynamoDB** — `AddDynamoCacheDb(...)` — serverless, TTL-backed cache table. The default for Lambda services. /docs/core-cache
- **S3 Express** — `AddS3ExpressCacheDb(...)` — S3 Express One Zone directory buckets for single-AZ, ultra-low-latency reads.
- **In-memory** — `AddInMemoryCacheDb(namespace)` — same `ICacheDb` surface, in-process. For tests and single-instance workloads.

## Install

```bash
dotnet add package Kanject.Core.CacheDb.Provider.DynamoDb
# or .Provider.InMemory  (tests / single process)
# or .Provider.S3Express (S3 Express One Zone, ultra-low latency)
```

Each provider is its own package; they all implement `ICacheDb` from `Kanject.Core.CacheDb.Abstractions` (pulled in transitively).

## Register

```csharp
using Kanject.Core.CacheDb.Provider.DynamoDb.Extensions;

builder.Services.AddDynamoCacheDb(options =>
{
    options.CacheName = "orders-cache";
    options.Namespace = appSettings.Stage;   // per-stage key isolation
    options.AwsRegion = appSettings.AwsRegion;
    options.TTL       = 15;                   // default item lifetime, in minutes
});
```

The S3 Express provider takes the same shape plus `AvailabilityZoneId`; the in-memory provider takes just a namespace string. `Namespace` prefixes every key, so `dev` and `prod` can share an account without colliding.

## Use it

Inject `ICacheDb`. Build a namespaced key with `FormatCacheKey`, try-get, and on a miss compute and store with an **absolute expiry** (a `DateTime`, not a relative TTL):

```csharp
public class QuoteService(ICacheDb cache, IPricingApi pricing)
{
    public async Task<string> GetQuoteAsync(string sku)
    {
        var key = cache.FormatCacheKey("quote", sku);

        var (hit, cached) = await cache.TryGetCacheDataAsync(key);
        if (hit) return cached!;

        var quote = await pricing.QuoteAsync(sku);

        // Absolute expiry — pass the DateTime the entry should die at.
        await cache.CacheDataAsync(key, quote, DateTime.UtcNow.AddMinutes(5));
        return quote;
    }
}
```

Typed overloads serialize and deserialize any object; removal takes one or many keys:

```csharp
// Typed overloads serialize/deserialize with System.Text.Json.
await cache.CacheDataAsync("basket:" + userId, basket, DateTime.UtcNow.AddHours(1));

var (found, basket) = await cache.TryGetCacheDataAsync<Basket>("basket:" + userId);

// Invalidate one or many keys.
await cache.RemoveCachedDataAsync("basket:" + userId);
```

## What ships with it

- One `ICacheDb` interface, three providers — DynamoDB, S3 Express One Zone, in-memory
- `CacheDataAsync(key, value, DateTime? expiry)` — string and typed (`<T>`) overloads, System.Text.Json serialization
- `TryGetCacheDataAsync(key)` → `(bool exists, value)` — string and typed overloads
- `RemoveCachedDataAsync(params string[] keys)` — invalidate one or many keys
- `FormatCacheKey(params string[] keys)` + a `Namespace` prefix for per-stage / per-tenant isolation
- Lock, counter-lock, and seat-lock helpers with the same surface across providers

> **Absolute expiry, not a TTL clock:** Cache entries take a `DateTime` expiry — the moment the entry should die — rather than a relative `TimeSpan`. Compute it at write time (`DateTime.UtcNow.Add…`). The provider-level default `TTL` (minutes) applies when you pass no explicit expiry.

---
_Source: https://www.kanject.com/docs/core-cache/ · Kanject Docs_
