Tyto.Rpc.Caching
0.0.1-alpha.100
dotnet add package Tyto.Rpc.Caching --version 0.0.1-alpha.100
NuGet\Install-Package Tyto.Rpc.Caching -Version 0.0.1-alpha.100
<PackageReference Include="Tyto.Rpc.Caching" Version="0.0.1-alpha.100" />
<PackageVersion Include="Tyto.Rpc.Caching" Version="0.0.1-alpha.100" />
<PackageReference Include="Tyto.Rpc.Caching" />
paket add Tyto.Rpc.Caching --version 0.0.1-alpha.100
#r "nuget: Tyto.Rpc.Caching, 0.0.1-alpha.100"
#:package Tyto.Rpc.Caching@0.0.1-alpha.100
#addin nuget:?package=Tyto.Rpc.Caching&version=0.0.1-alpha.100&prerelease
#tool nuget:?package=Tyto.Rpc.Caching&version=0.0.1-alpha.100&prerelease
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
IRpcClientunder 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.Cachinglocking 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 | Versions 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. |
-
net10.0
- Tyto.Caching.Abstractions (>= 0.0.1-alpha.100)
- Tyto.Rpc.Abstractions (>= 0.0.1-alpha.100)
- Tyto.Rpc.Client (>= 0.0.1-alpha.100)
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 |