Tyto.Rpc.Caching 0.0.1-alpha.100

This is a prerelease version of Tyto.Rpc.Caching.
dotnet add package Tyto.Rpc.Caching --version 0.0.1-alpha.100
                    
NuGet\Install-Package Tyto.Rpc.Caching -Version 0.0.1-alpha.100
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Tyto.Rpc.Caching" Version="0.0.1-alpha.100" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Tyto.Rpc.Caching" Version="0.0.1-alpha.100" />
                    
Directory.Packages.props
<PackageReference Include="Tyto.Rpc.Caching" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Tyto.Rpc.Caching --version 0.0.1-alpha.100
                    
#r "nuget: Tyto.Rpc.Caching, 0.0.1-alpha.100"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Tyto.Rpc.Caching@0.0.1-alpha.100
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Tyto.Rpc.Caching&version=0.0.1-alpha.100&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Tyto.Rpc.Caching&version=0.0.1-alpha.100&prerelease
                    
Install as a Cake Tool

Tyto.Rpc.Caching

A high-performance, transparent client-side caching decorator for Tyto RPC. It seamlessly integrates Tyto.Rpc with Tyto.Caching without polluting message contracts.


🌟 Key Architectural Principles

  • Zero Contract Pollution: Request DTOs (IRpcRequest<TResponse>) remain 100% pure contracts. No infrastructure interfaces (ICacheable), annotations, or key generators are enforced on models.
  • Transparent Decorator: Decorates IRpcClient under the hood. Uncached requests execute with zero allocation and zero overhead.
  • L1 Fast-Path Bypass: Utilizes synchronous in-memory L1 cache (TryGet) before allocating async state machines or distributed locks.
  • Thundering Herd / Stampede Protection: Leverages Tyto.Caching locking to ensure concurrent identical requests execute only one remote RPC call while safely propagating transient failures.
  • Model-Based Error Caching (Negative Caching): Cache domain errors (such as NotFoundError) explicitly to prevent repeated expensive remote calls.
  • Full Expiration Support: Hard absolute expiration (TTL), sliding expiration, and Stale-While-Revalidate (SWR).

📦 Installation & Setup

Register distributed caching and configure your RPC client:

// 1. Configure Distributed Caching Providers (Memory, Redis, Backplane)
services.AddDistributedCaching(caching => {
    caching.AddInMemoryProvider("LocalMem");
    caching.AddRedisProvider("GlobalRedis", "localhost:6379");
});

// 2. Configure RPC Client with Caching Policies
services.AddRpc(rpc => {
    rpc.AddClient(client => {
        client.UseHttp(http => {
            http.ConnectTo("CatalogService", new Uri("https://api.catalog.local"))
                .Handles<GetCategoryByIdRequest>()
                .Handles<ValidateAppAccessRpcRequest>();
        })
        .UseCaching(caching => {
            // Policy 1: Standard RPC Caching with Sub-Key and SWR
            caching.For<GetCategoryByIdRequest, CategoryDto>("CatalogProfile", policy => {
                policy.WithKey(req => $"category:{req.CategoryId}")
                      .WithTtl(TimeSpan.FromMinutes(15))
                      .WithStaleWhileRevalidate(TimeSpan.FromMinutes(2))
                      .OnlyIf(req => !req.BypassCache);
            });

            // Policy 2: Negative Caching for Domain Errors
            caching.For<ValidateAppAccessRpcRequest, ValidateAppAccessRpcResponse>("AppAccessProfile", policy => {
                policy.WithKey(req => $"{req.AppId}:{req.OrganizationId}:{req.ClientId}")
                      .WithTtl(TimeSpan.FromMinutes(30))
                      // Caches NotFoundError responses as negative hits
                      .CacheErrors<NotFoundError>();
            });
        });
    });
});

⚡ Shorthand Single-Request Configuration

For simple setups with a single request type, pass the profile name directly:

client.UseCaching<GetCategoryByIdRequest, CategoryDto>("CatalogProfile", policy => {
    policy.WithKey(req => $"category:{req.CategoryId}")
          .WithTtl(TimeSpan.FromMinutes(10));
});

🔑 Cache Key Semantics & Invalidation

All cache keys are standardized through Tyto.Caching's centralized key generator:

Key Format = "tyto:{ProfileName}:{TypeName}:{SubKey}"

1. Dynamic Key Generation (WithKey)

For parameterized requests, extract a unique sub-key:

policy.WithKey(req => $"category:{req.CategoryId}");
  • Physical Key in Redis/Memory: tyto:CatalogProfile:CategoryDto:category:123
  • Manual Invalidation in Code:
    public class CategoryService(ICache<string, CategoryDto> cache) {
        public async Task EvictCategoryAsync(int categoryId) {
            // Removes from L1, L2 and broadcasts via Backplane
            await cache.InvalidateAsync($"category:{categoryId}");
        }
    }
    

2. Parameterless Requests (Default Fallback)

If .WithKey(...) is omitted, Tyto defaults to using the request type's name (nameof(TRequest)):

caching.For<GetCurrenciesRpcRequest, CurrencyListDto>("StaticProfile", policy => {
    policy.WithTtl(TimeSpan.FromHours(1));
});
  • Physical Key in Redis/Memory: tyto:StaticProfile:CurrencyListDto:GetCurrenciesRpcRequest
  • Manual Invalidation:
    await cache.InvalidateAsync(nameof(GetCurrenciesRpcRequest));
    

🛡️ Model-Based Error Caching (CacheErrors)

Instead of caching untyped null values, define explicit domain error rules:

// 1. By Error Type
policy.CacheErrors<NotFoundError>();

// 2. By Predicate / Error Code
policy.CacheErrors(err => err.Code == "TENANT_SUSPENDED" || err is NotFoundError);

When an RPC call returns a matching RpcError, the result is cached as a negative hit. Transient errors (InternalServerError, TimeoutError, network exceptions) are never cached and safely propagate to the caller.


🚀 Usage in Application Code

The consumer code remains 100% unaware of caching:

public class CategoryService(IRpcClient rpcClient) {
    public async Task<CategoryDto?> GetAsync(int id, CancellationToken ct) {
        // 1st Call -> Executes HTTP/gRPC remote call & populates L1/L2 cache.
        // Subsequent Calls -> Served instantly from L1 (Memory) or L2 (Redis)!
        RpcResult<CategoryDto> result = await rpcClient.CallAsync(new GetCategoryByIdRequest(id), ct);

        return result.Value;
    }
}

📄 License

This project is licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.1-alpha.100 35 8/24/2026
0.0.1-alpha.99 49 8/20/2026
0.0.1-alpha.98 49 8/18/2026
0.0.1-alpha.97 45 8/18/2026
0.0.1-alpha.96 53 8/18/2026
0.0.1-alpha.95 58 8/17/2026