{"id":15011538,"url":"https://github.com/pandatecham/be-lib-distributed-cache","last_synced_at":"2025-07-02T08:38:56.592Z","repository":{"id":242890876,"uuid":"810757616","full_name":"PandaTechAM/be-lib-distributed-cache","owner":"PandaTechAM","description":"A robust caching solution for .NET applications using Redis, with support for flexible cache configuration, tagging, and health checks.","archived":false,"fork":false,"pushed_at":"2025-02-28T06:43:33.000Z","size":243,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"development","last_synced_at":"2025-03-25T23:23:08.495Z","etag":null,"topics":["cache","distributed-lock","dotnet","library","nuget","package","pandatech","redis"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PandaTechAM.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-05T09:51:31.000Z","updated_at":"2025-02-26T06:47:16.000Z","dependencies_parsed_at":"2024-06-13T01:58:05.922Z","dependency_job_id":"ecd479ea-259d-442b-9b01-9f5a92ecd85a","html_url":"https://github.com/PandaTechAM/be-lib-distributed-cache","commit_stats":{"total_commits":13,"total_committers":2,"mean_commits":6.5,"dds":0.07692307692307687,"last_synced_commit":"0d9b1eb68492eb27c67b1f91c11c72d23b54a112"},"previous_names":["pandatecham/be-lib-cache-service"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PandaTechAM%2Fbe-lib-distributed-cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PandaTechAM%2Fbe-lib-distributed-cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PandaTechAM%2Fbe-lib-distributed-cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PandaTechAM%2Fbe-lib-distributed-cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PandaTechAM","download_url":"https://codeload.github.com/PandaTechAM/be-lib-distributed-cache/tar.gz/refs/heads/development","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248514214,"owners_count":21116899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","distributed-lock","dotnet","library","nuget","package","pandatech","redis"],"created_at":"2024-09-24T19:41:13.487Z","updated_at":"2025-04-12T03:43:38.145Z","avatar_url":"https://github.com/PandaTechAM.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pandatech.DistributedCache\n\n**Pandatech.DistributedCache** is a lightweight .NET library that leverages `StackExchange.Redis` for distributed\ncaching.\nBuilt on top of `StackExchange.Redis.Extensions.AspNetCore` and `StackExchange.Redis.Extensions.MsgPack`, it offers a\nstraightforward solution for typed caching, distributed locking, rate limiting, and stampede protection—all through the\n`Microsoft.Extensions.Caching.Abstractions` NuGet package's `HybridCache` abstract class.\n\n\u003e Note: As of January 29, 2025, `HybridCache` is still in preview, and Microsoft does not provide an official\n\u003e implementation. The only known library with a `HybridCache` implementation is `FusionCache`, which uses a two-level (\n\u003e L1 +\n\u003e L2) caching model. While that approach can yield high performance, it also adds complexity—particularly in distributed\n\u003e environments. Many scenarios do not require such complexity, which is why Pandatech.DistributedCache avoids\n\u003e maintaining\n\u003e an L1 cache. Consequently, certain `HybridCacheEntryFlags` (e.g., disabling local cache writes) are effectively\n\u003e ignored in\n\u003e this library. You may set them, but they have no effect here.\n\nOverall, `Pandatech.DistributedCache` weighs in at fewer than 500 lines of code, making it easy to understand, extend,\nand\nmaintain.\n\n## Features\n\n- **Typed Cache Service:**\n  Offers strongly typed caching using MessagePack serialization under the hood.\n- **Distributed Locking:**\n  Provides safe concurrency control with Redis-based locks.\n- **Distributed Rate Limiting:**\n  Allows you to apply business logic–driven rate limits on operations (e.g., sending SMS or email).\n- **Stampede Protection:**\n  Prevents a cache stampede by synchronizing concurrent `GetOrCreateAsync` calls on the same key.\n- **HybridCache Integration:**\n  Implements the preview `HybridCache` abstraction from Microsoft, ensuring a future-friendly approach if you choose to\n  migrate to another HybridCache-based library in the future.\n- **HybridCache Extension:**\n  Provides extra methods for `HybridCache` to simplify common cache operations.\n- **Health Check Integration:**\n  Automatically registers a Redis health check (using `AspNetCore.HealthChecks.Redis`) for seamless readiness and\n  liveness checks.\n\n## Installation\n\nInstall the package from NuGet:\n\n```bash\ndotnet add package Pandatech.DistributedCache\n```\n\n## Usage\n\n### 1. Configuration\n\nIn your `Program.cs`, configure the distributed cache:\n\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.AddDistributedCache(options =\u003e\n{\n    options.RedisConnectionString = \"your_redis_connection_string\"; //No default value and required\n    options.ChannelPrefix = \"your_channel_prefix\"; //Default is null\n    options.ConnectRetry = 15; //Default is 10\n    options.ConnectTimeout = TimeSpan.FromSeconds(10); //Default is 10 seconds\n    options.SyncTimeout = TimeSpan.FromSeconds(5); //Default is 5 seconds\n    options.DistributedLockDuration = TimeSpan.FromSeconds(30); //Default is 8 seconds\n    options.DefaultExpiration = TimeSpan.FromMinutes(5); //Default is 15 minutes\n});\n\nvar app = builder.Build();\n```\n\nWhen `AddDistributedCache` is called:\n\n- A Redis connection is established with the specified parameters.\n- A Redis health check is automatically registered with a 3-second timeout, ensuring that your application can properly\n  monitor Redis availability.\n\n### 2. Cached Entity Preparation\n\nCreate a model class to store in the cache. Decorate it with `[MessagePackObject]` so it can be serialized and\ndeserialized with MessagePack:\n\n```csharp\n[MessagePackObject]\npublic class TestCacheEntity : ICacheEntity\n{\n    [Key(0)] public string Name { get; set; } = \"Bob\";\n    [Key(1)] public int Age { get; set; } = 15;\n    [Key(2)] public DateTime CreatedAt { get; set; } = DateTime.Now;\n}\n```\n\n### 3. Injecting HybridCache\n\nUse dependency injection to retrieve an instance of `HybridCache` and perform cache operations:\n\n```csharp\npublic class CacheTestsService(HybridCache hybridCache)\n{\n   public async Task GetFromCache(CancellationToken token = default)\n   {\n      var call1 = await hybridCache.GetOrCreateAsync\u003cTestCacheEntity\u003e(\"test\",\n         async _ =\u003e await GetFromPostgres(token),\n         new HybridCacheEntryOptions\n         {\n            Expiration = TimeSpan.FromMinutes(5),\n         },\n         [\"test\"],\n         token);\n\n     \n\n      var call2 = await hybridCache.GetOrCreateAsync\u003cTestCacheEntity\u003e(\"test\",\n         async _ =\u003e await GetFromPostgres(token),\n         new HybridCacheEntryOptions\n         {\n            Expiration = TimeSpan.FromMinutes(5),\n         },\n         [\"test\"],\n         token);\n\n      var call3 = await hybridCache.GetOrCreateAsync\u003cTestCacheEntity\u003e(\"test2\",\n         async _ =\u003e await GetFromPostgres(token),\n         new HybridCacheEntryOptions\n         {\n            Expiration = TimeSpan.FromMinutes(5),\n         },\n         [\"vazgen\"],\n         token);\n\n    \n\n      var call4 = await hybridCache.GetOrCreateAsync\u003cTestCacheEntity\u003e(\"test3\",\n         async _ =\u003e await GetFromPostgres(token),\n         new HybridCacheEntryOptions\n         {\n            Expiration = TimeSpan.FromMinutes(5),\n         },\n         [\"test\", \"vazgen\"],\n         token);\n      \n   }\n   \n   public async Task TestExistence(CancellationToken token = default)\n   {\n      var call1Check = await hybridCache.ExistsAsync\u003cTestCacheEntity\u003e(\"test\", token);\n      Console.WriteLine($\"Call1: {call1Check}\");\n      var call2Check = await hybridCache.ExistsAsync\u003cTestCacheEntity\u003e(\"test\", token);\n      Console.WriteLine($\"Call2: {call2Check}\");\n      var call3Check = await hybridCache.ExistsAsync\u003cTestCacheEntity\u003e(\"test2\", token);\n      Console.WriteLine($\"Call3: {call3Check}\");\n      var call4Check = await hybridCache.ExistsAsync\u003cTestCacheEntity\u003e(\"test3\", token);\n      Console.WriteLine($\"Call4: {call4Check}\");\n   }\n\n   public async Task DeleteCache(CancellationToken token = default)\n   {\n      await hybridCache.RemoveByTagAsync(\"test\", token);\n   }\n\n   public async Task\u003cTestCacheEntity\u003e GetFromPostgres(CancellationToken token)\n   {\n      Console.WriteLine(\"Hey, I'm Fetching from postgres\");\n      await Task.Delay(500, token);\n      return new TestCacheEntity();\n   }\n}\n```\n\n### 4. Rate Limiting\n\nPandatech.DistributedCache also supports rate limiting via `IRateLimitService`.\n\n**Example Rate Limit Configuration**\nUse an enum to track different action types, and create a shared configuration:\n\n```csharp\npublic enum ActionType //your business logic actions\n{\n    SmsForTfa = 1,\n    EmailForTfa = 2\n}\n\npublic static class RateLimitingConfigurations //your shared rate limiting configuration\n{\n    public static RateLimitConfiguration GetSmsConfig()\n    {\n        return new RateLimitConfiguration\n        {\n            ActionType = (int)ActionType.SmsForTfa,\n            MaxAttempts = 2,\n            TimeToLive = TimeSpan.FromSeconds(10)\n        };\n    }\n}\n```\n\n**Applying Rate Limiting**\n\n```csharp\nusing DistributedCache.Dtos;\nusing DistributedCache.Services.Interfaces;\n\npublic class SendSmsService(IRateLimitService rateLimitService)\n{\n    public async Task\u003cRateLimitState\u003e SendSms(CancellationToken cancellationToken = default)\n    {\n        var phoneNumber = \"1234567890\";\n        var rateLimitConfiguration = RateLimitingConfigurations.GetSmsConfig().SetIdentifiers(phoneNumber);\n\n        return await rateLimitService.RateLimitAsync(rateLimitConfiguration, cancellationToken);\n    }\n}\n```\n\n### 5. Distributed Locking\n\nDistributed locks can be used for concurrency control. The core interface:\n\n```csharp\npublic interface IDistributedLockService\n{\n   Task\u003cbool\u003e AcquireLockAsync(string resourceKey, string lockToken);\n   Task\u003cbool\u003e HasLockAsync(string resourceKey);\n   Task WaitUntilLockIsReleasedAsync(string resourceKey, CancellationToken cancellationToken);\n   Task ReleaseLockAsync(string resourceKey, string lockToken);\n}\n```\n\nIn practice, you inject `IDistributedLockService` into your service or use the default RedisLockService, acquire a lock\nfor a specific resource, and release it once your operation completes. This ensures that only one caller can modify a\ngiven resource at a time.\n\n### 6. Health Check Integration\n\n`Pandatech.DistributedCache` automatically adds a Redis health check to your application through the\n`AddDistributedCache`\nmethod. By default, the check uses a 3-second timeout to validate Redis connectivity. This helps with\ncontainer-orchestrated environments (Kubernetes, Docker, etc.) to ensure your service is only considered healthy when\nRedis is accessible.\n\n### 7. HybridCache Extensions\n\nPandatech.DistributedCache provides a few helpful extension methods for the `HybridCache` abstraction. These extensions\nare also compatible with other `HybridCache` implementations you might use in the future, allowing for a smoother\nmigration path if you switch providers.\n\n1. `GetOrDefaultAsync\u003cTValue\u003e` Retrieves an item from the cache using the specified key. If the item is not found, it\n   returns the provided default\n   value instead of creating a real cache entry:\n   ```csharp\n    var cachedValue = await hybridCache.GetOrDefaultAsync(\"someKey\", defaultValue, cancellationToken);\n    ```\n   This is especially useful when you simply want a fallback value without messing up with factory methods.\n2. `TryGetAsync\u003cTValue\u003e`\n   Attempts to retrieve an item from the cache. If it exists, returns (true, value), otherwise (false, default).\n   ```csharp\n    var (exists, value) = await hybridCache.TryGetAsync\u003cYourModel\u003e(\"someKey\", cancellationToken);\n    if (exists)\n    {\n    // use the 'value'\n    }\n    else\n    {\n    // handle the 'not found' case\n    }\n    ```\n   This method is a cleaner alternative to a typical “check then get” pattern, eliminating extra round-trips to Redis.\n3. `ExistsAsync\u003cTValue\u003e`\n   Quickly checks if an item exists in the cache without returning its value:\n   ```csharp\n    var recordExists = await hybridCache.ExistsAsync\u003cYourModel\u003e(\"someKey\", cancellationToken);\n    if (recordExists)\n    {\n    // Key is present in cache\n    }\n    else\n    {\n    // Key does not exist\n    }\n    ```\n   Useful in scenarios where you only need to confirm the existence of the key rather than retrieve and deserialize its\n   data.\n\n## Enforced MessagePack Serialization\n\nWe enforce MessagePack serialization to maximize performance and simplicity:\n\n1. **Speed:** MessagePack can be several times faster than JSON and faster than Protobuf in many scenarios.\n2. **Compactness:** MessagePack typically produces smaller payloads than JSON, improving network transfer performance\n   and\n   memory usage.\n3. **Tooling Support:** Many Redis management tools (e.g., Another Redis Desktop Manager) can display MessagePack data\n   as JSON-like view for easy debugging.\n4. **Consistency:** A single, enforced serialization format avoids complexity and ensures consistent behavior across\n   your\n   application.\n\n**Benchmark Snapshot:**\n-------------------------\n\n| Format      | Serialization Speed   | Deserialization Speed | Serialized Size |\n|-------------|-----------------------|-----------------------|-----------------|\n| MessagePack | 4x faster than JSON   | 3x faster than JSON   | ~50% of JSON    |\n| Protobuf    | 1.5x faster than JSON | 1.2x faster than JSON | ~70% of JSON    |\n| JSON        | Baseline              | Baseline              | Baseline        |\n\nBecause of these advantages, `Pandatech.DistributedCache` does not provide alternative serialization options;\nMessagePack\ncovers the core needs effectively.\n\n## License\n\nPandatech.DistributedCache is licensed under the MIT License.\n\n----------------\n`Pandatech.DistributedCache` aims to simplify distributed caching for most .NET applications without incurring the\noverhead of a multi-level caching system. If your application requirements evolve, the standardized HybridCache\nabstraction ensures you can switch to other providers (such as `FusionCache` or Microsoft's future implementation)\nwithout extensive refactoring. Enjoy fasterlookups, simpler code, and safer concurrency management—all in under 500\nlines of code.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpandatecham%2Fbe-lib-distributed-cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpandatecham%2Fbe-lib-distributed-cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpandatecham%2Fbe-lib-distributed-cache/lists"}