Invalid8.Core
1.0.4
dotnet add package Invalid8.Core --version 1.0.4
NuGet\Install-Package Invalid8.Core -Version 1.0.4
<PackageReference Include="Invalid8.Core" Version="1.0.4" />
<PackageVersion Include="Invalid8.Core" Version="1.0.4" />
<PackageReference Include="Invalid8.Core" />
paket add Invalid8.Core --version 1.0.4
#r "nuget: Invalid8.Core, 1.0.4"
#:package Invalid8.Core@1.0.4
#addin nuget:?package=Invalid8.Core&version=1.0.4
#tool nuget:?package=Invalid8.Core&version=1.0.4
Invalid8 Caching Library Documentation
Overview
Invalid8 is a sophisticated, high-performance caching library for .NET applications designed specifically for modern distributed systems and CQRS architectures. It provides intelligent query caching with configurable stale/expiration times, automatic cache invalidation for mutations, distributed cache synchronization via event bus, and resilient retry mechanisms.
Key Features:
- 🚀 React Query-like DX for .NET with familiar patterns
- 🌐 Distributed cache synchronization across multiple instances
- ⚡ High performance with minimal overhead
- 🛡️ Resilient design with circuit breakers and retry policies
- 📊 Comprehensive observability with metrics and logging
- 🎯 CQRS-optimized for read/write separation
Core Concepts
1. Cache Lifecycle
sequenceDiagram
participant Client
participant QueryClient
participant Cache
participant Backend
Client->>QueryClient: Request Data
QueryClient->>Cache: Check cache
alt Cache Hit & Fresh
Cache-->>QueryClient: Valid data
QueryClient-->>Client: Return cached data
else Cache Hit & Stale
Cache-->>QueryClient: Stale data
QueryClient-->>Client: Return stale data immediately
QueryClient->>Backend: Background refresh
Backend-->>QueryClient: Fresh data
QueryClient->>Cache: Update cache
else Cache Miss
QueryClient->>Backend: Fetch data
Backend-->>QueryClient: Fresh data
QueryClient->>Cache: Store data
QueryClient-->>Client: Return fresh data
end
2. Cache Invalidation
sequenceDiagram
participant Client
participant QueryClient
participant EventBus
participant Cache
participant Other Instances
Client->>QueryClient: Execute Mutation
QueryClient->>Cache: Invalidate keys
QueryClient->>EventBus: Publish invalidation
EventBus->>Other Instances: Notify all instances
Other Instances->>Their Caches: Invalidate keys
API Reference
Core Methods
QueryAsync<T>(string[] key, Func<Task<T>> queryFunc, QueryOptions? options = null)
Purpose: Execute a query with caching support
Parameters:
key
: Array representing the cache key (e.g.,["users", "id123"]
)queryFunc
: Function to fetch data if not in cacheoptions
: Cache configuration (stale time, cache time, etc.)
Flow:
- Checks cache first
- Returns cached data if valid
- Fetches fresh data if cache miss/stale
- Updates cache in background if stale
MutateAsync<T>(Func<Task<T>> mutationFunc, MutationOptions? options = null)
Purpose: Execute a mutation with automatic cache invalidation
Parameters:
mutationFunc
: Mutation operationoptions
: Invalidation configuration
Flow:
- Executes mutation
- Invalidates specified cache keys
- Publishes invalidation events to distributed cache
SetQueryDataAsync<T>(string[] key, T data, CacheEntryOptions? options = null)
Purpose: Manually update cache data (optimistic updates)
UpdateQueryDataAsync<T>(string[] key, Func<T?, T> updateFunction, CacheEntryOptions? options = null)
Purpose: Transform existing cache data
Configuration Options
QueryOptions
Property | Type | Default | Description |
---|---|---|---|
StaleTime | TimeSpan? | 5 min | Time before data is considered stale |
CacheTime | TimeSpan? | 30 min | Time before data expires |
RetryCount | int | 3 | Number of retry attempts |
RetryDelay | TimeSpan | 1 sec | Delay between retries |
EnableBackgroundRefetch | bool | true | Whether to refresh stale data in background |
Tags | string[] | Empty | Tags for categorical invalidation |
MutationOptions
Property | Type | Default | Description |
---|---|---|---|
InvalidateQueries | string[][] | Empty | Array of keys to invalidate |
PublishEvent | bool | true | Whether to publish invalidation events |
RetryCount | int | 3 | Number of retry attempts |
Timeout | TimeSpan? | null | Operation timeout |
OptimisticMutationOptions<T>
Property | Type | Default | Description |
---|---|---|---|
QueryKeys | string[][] | Required | Keys to update optimistically |
OptimisticData | Func<T?> | Required | Function to compute optimistic data |
OnSuccess | Func<T, T> | null | Transform result after success |
RollbackOnError | bool | true | Whether to rollback on error |
OnError | Action<Exception> | null | Custom error handler |
CQRS Implementation Guide
Query Side Optimization
public class GetUserQueryHandler : IQueryHandler<GetUserQuery, UserDto>
{
private readonly IQueryClient _queryClient;
public async Task<UserDto> Handle(GetUserQuery query, CancellationToken ct)
{
return await _queryClient.QueryAsync(
key: new[] { "users", query.UserId.ToString() },
queryFunc: () => _userRepository.GetUserAsync(query.UserId),
options: new QueryOptions
{
StaleTime = TimeSpan.FromMinutes(5),
CacheTime = TimeSpan.FromHours(1),
Tags = new[] { "users", $"user:{query.UserId}" }
});
}
}
Command Side with Automatic Invalidation
public class UpdateUserCommandHandler : ICommandHandler<UpdateUserCommand>
{
private readonly IQueryClient _queryClient;
public async Task Handle(UpdateUserCommand command, CancellationToken ct)
{
await _queryClient.MutateAsync(
mutationFunc: () => _userRepository.UpdateUserAsync(command.User),
options: new MutationOptions
{
InvalidateQueries = new[]
{
new[] { "users", command.User.Id.ToString() },
new[] { "users", "list" },
new[] { "dashboard", "stats" }
}
});
}
}
Optimistic Updates for Better UX
public async Task<Todo> CompleteTodoAsync(int todoId)
{
return await _queryClient.MutateAsync(
mutationFunc: () => _api.CompleteTodoAsync(todoId),
options: new OptimisticMutationOptions<Todo>
{
QueryKeys = new[] { new[] { "todos", todoId.ToString() } },
OptimisticData: () => new Todo { Id = todoId, Completed = true, Title = "Loading..." },
OnSuccess: result => result with { UpdatedAt = DateTime.UtcNow },
RollbackOnError: true
});
}
Distributed Cache Synchronization
// Service A (updates data)
await _queryClient.MutateAsync(() => _serviceA.UpdateOrderAsync(order),
new MutationOptions
{
InvalidateQueries = new[] { new[] { "orders", order.Id.ToString() } },
PublishEvent = true
});
// Service B (automatically receives invalidation)
await _eventProvider.SubscribeToInvalidationsAsync(async invalidationEvent =>
{
foreach (var key in invalidationEvent.Keys)
{
if (key[0] == "orders")
{
await _analyticsService.ClearOrderCache(key[1]);
}
}
});
Setup Guide
1. Basic In-Memory Setup (Development)
// Program.cs
builder.Services.AddInvalid8()
.AddInvalid8WithInMemoryCache();
2. Redis Production Setup
// Program.cs
builder.Services.AddInvalid8()
.AddInvalid8WithRedisCache(options =>
options.Configuration = builder.Configuration.GetConnectionString("Redis"));
3. Configuration Options
// appsettings.json
{
"Invalid8": {
"DefaultStaleTime": "00:05:00",
"DefaultCacheTime": "00:30:00",
"EnableBackgroundRefresh": true,
"MaxRetryAttempts": 3
},
"ConnectionStrings": {
"Redis": "localhost:6379,abortConnect=false"
}
}
Advanced Features
1. Background Refresh
When data becomes stale but isn't expired:
- Returns stale data immediately
- Asynchronously refreshes data in background
- Updates cache for subsequent requests
2. Resilient Retries
Automatic retry mechanism for:
- Cache operations
- Query executions
- Event publishing
- Circuit breaker pattern integration
3. Distributed Invalidation
sequenceDiagram
participant InstanceA
participant EventBus
participant InstanceB
participant InstanceC
InstanceA->>EventBus: Publish invalidation
EventBus->>InstanceB: Receive event
EventBus->>InstanceC: Receive event
InstanceB->>InstanceB: Local invalidation
InstanceC->>InstanceC: Local invalidation
4. Tag-Based Invalidation
// Cache with tags
await _queryClient.SetQueryDataAsync(
key: new[] { "users", "123" },
value: user,
options: new CacheEntryOptions
{
Tags = new[] { "department:finance", "region:us" }
});
// Invalidate by tag
await _cacheProvider.InvalidateByTagAsync("department:finance");
Best Practices
1. Key Design
- Use consistent key structures (e.g.,
["entity-type", "id"]
) - For lists, use prefix keys (e.g.,
["users", "list"]
) - Include domain context in keys (e.g.,
["tenant:123", "users", "456"]
)
2. Timeout Configuration
// Set appropriate timeouts based on data volatility
new QueryOptions
{
StaleTime = TimeSpan.FromSeconds(30), // Frequently changing data
CacheTime = TimeSpan.FromMinutes(5) // Keep in cache longer
}
new QueryOptions
{
StaleTime = TimeSpan.FromMinutes(60), // Stable reference data
CacheTime = TimeSpan.FromHours(24) // Long cache duration
}
3. Monitoring and Metrics
// Track cache performance
public class CacheMetrics
{
public void RecordCacheHit(string[] key) => _metrics.Increment("cache.hits");
public void RecordCacheMiss(string[] key) => _metrics.Increment("cache.misses");
public void RecordLatency(string[] key, TimeSpan latency) => _metrics.Timing("cache.latency", latency);
}
// Integrate with application insights
services.AddSingleton<ICacheTelemetry, ApplicationInsightsTelemetry>();
Performance Characteristics
Operation | Average Latency | Notes |
---|---|---|
Cache Hit | 1-5ms | Depends on cache provider |
Cache Miss | Query latency + 5-10ms | Includes cache population |
Mutation | Query latency + 10-20ms | Includes invalidation overhead |
Distributed Invalidation | 50-100ms | Network latency dependent |
Troubleshooting
Symptom | Possible Cause | Solution |
---|---|---|
Stale data returned | Background refresh failed | Check logs for refresh errors |
Cache not invalidating | Event bus issues | Verify event provider connectivity |
High memory usage | Cache size too large | Implement eviction policies |
Distributed cache out of sync | Network partitions | Check event bus connectivity |
Migration Guide
From Manual Caching
// BEFORE: Manual cache management
public async Task<User> GetUserAsync(int id)
{
var cacheKey = $"user:{id}";
var cached = await _cache.GetAsync<User>(cacheKey);
if (cached != null) return cached;
var user = await _db.Users.FindAsync(id);
await _cache.SetAsync(cacheKey, user, TimeSpan.FromMinutes(30));
return user;
}
// AFTER: Invalid8 automated caching
public async Task<User> GetUserAsync(int id)
{
return await _queryClient.QueryAsync(
new[] { "users", id.ToString() },
() => _db.Users.FindAsync(id));
}
Examples
Basic Usage
// Simple query with caching
var user = await _queryClient.QueryAsync(
new[] { "users", userId.ToString() },
() => _userService.GetUserAsync(userId));
// Mutation with automatic invalidation
await _queryClient.MutateAsync(
() => _userService.UpdateUserAsync(user),
new MutationOptions
{
InvalidateQueries = new[] { new[] { "users", user.Id.ToString() } }
});
Advanced Scenario
// Optimistic update with rollback
var result = await _queryClient.MutateAsync(
() => _api.CompleteTodoAsync(todoId),
new OptimisticMutationOptions<Todo>
{
QueryKeys = new[] { new[] { "todos", todoId.ToString() } },
OptimisticData: () => new Todo { Id = todoId, Completed = true },
OnSuccess: updatedTodo => updatedTodo with { UpdatedAt = DateTime.UtcNow },
RollbackOnError: true
});
Support
- Documentation: GitHub Wiki
- Issues: GitHub Issues
- Discussions: GitHub Discussions
License
MIT License - feel free to use in commercial projects.
Invalid8 - Intelligent Caching for Modern .NET Applications
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net8.0
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.0-preview.7.25380.108)
-
net9.0
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.0-preview.7.25380.108)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Invalid8.Core:
Package | Downloads |
---|---|
Invalid8
A sophisticated caching library for .NET applications with React Query-like developer experience. Provides intelligent query caching, automatic cache invalidation, distributed cache synchronization, and production-ready resilience patterns. |
|
Invalid8.Providers.Base
Base provider implementations and reusable components for Invalid8 caching library. Contains abstract base classes for cache and event providers. |
GitHub repositories
This package is not used by any popular GitHub repositories.