Last active
February 11, 2025 19:33
-
-
Save mtmk/6955161ff713b06d767398416049e0b6 to your computer and use it in GitHub Desktop.
NATS .NET KV TryGet Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// dotnet new console | |
// dotnet add package nats.net | |
// Program.cs: | |
using System.Net.Http.Json; | |
using System.Text.RegularExpressions; | |
using NATS.Net; | |
// You can create a free account on https://cloud.synadia.com and | |
// download your credentials file; find your user, click on | |
// 'Get Connected' then 'Download Credential' buttons. | |
await using var client = new NatsClient( | |
url: "tls://connect.ngs.global", | |
credsFile: "/path/to/NGS-Default-CLI.creds"); | |
// Or if using a local NATS server. Make sure to enable JetStream | |
// e.g. > nats.server.exe -js | |
// await using var client = new NatsClient(); | |
var kv = client.CreateKeyValueStoreContext(); | |
// MaxBytes needed for the cloud cluster. | |
// you can omit for local servers | |
var config = new NatsKVConfig(bucket: "github-cache-1") | |
{ | |
MaxBytes = 10 * 1024 * 1024 // 10 MB | |
}; | |
var store = await kv.CreateStoreAsync(config); | |
List<string> userRequestSimulation = ["octocat", "nats-io", "octocat", "nats-io", "nats-io"]; | |
foreach (var userName in userRequestSimulation) | |
{ | |
await ProcessUser(userName); | |
} | |
return; | |
// Simulate user processing using cached data store | |
async Task ProcessUser(string userName) | |
{ | |
var expiry = DateTimeOffset.Now.AddSeconds(10); | |
var gitHubUser = await GetCached(userName, FetchFromGitHub, expiry); | |
Log($"Processing {gitHubUser}"); | |
await Task.Delay(TimeSpan.FromSeconds(4)); // simulate processing | |
} | |
// Generic object caching using NATS KV with attached expiry data | |
async Task<T> GetCached<T>(string key, Func<string, Task<T>> fallback, DateTimeOffset expiry) | |
{ | |
// Using 'Try' version of get operation to avoid | |
// unnecessary exception handling. | |
var result = await store.TryGetEntryAsync<CacheEntry<T>>(key); | |
if (result.Success) | |
{ | |
var kvEntry = result.Value; | |
var cacheEntry = kvEntry.Value; | |
if (cacheEntry != default && DateTimeOffset.Now < cacheEntry.Expiry) | |
{ | |
Log("Cache hit"); | |
var cachedValue = cacheEntry.Value; | |
return cachedValue; | |
} | |
} | |
Log("Cache miss"); | |
var value = await fallback(key); | |
// Using 'TryPut' since if we can't cache the value we only log it | |
var putResult = await store.TryPutAsync<CacheEntry<T>>(key, new CacheEntry<T>(value, expiry)); | |
if (!putResult.Success) | |
{ | |
Log($"[WARN] Failed to put cache entry for {key}: {putResult.Error.GetType().Name}: {putResult.Error.Message}"); | |
// Optionally throttle to avoid hitting rate limits | |
await Task.Delay(1000); | |
} | |
return value; | |
} | |
// The expensive operation we want to cache the results of | |
async Task<GitHubUser?> FetchFromGitHub(string user) | |
{ | |
if (!Regex.IsMatch(user, @"^[\w\-\.]+$")) | |
{ | |
throw new FormatException("Invalid username"); | |
} | |
var httpClient = new HttpClient(); | |
httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClient"); | |
Log($"Fetching {user} from GitHub"); | |
var gitHubUser = await httpClient.GetFromJsonAsync<GitHubUser>($"https://api.github.com/users/{user}"); | |
return gitHubUser; | |
} | |
void Log(string message) | |
{ | |
Console.WriteLine($"{DateTime.Now:HH:mm:ss} {message}"); | |
} | |
public record struct CacheEntry<T>(T Value, DateTimeOffset Expiry); | |
public record GitHubUser(string Name, string Blog); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment