Skip to content

Instantly share code, notes, and snippets.

@mtmk
Last active February 11, 2025 19:33
Show Gist options
  • Save mtmk/6955161ff713b06d767398416049e0b6 to your computer and use it in GitHub Desktop.
Save mtmk/6955161ff713b06d767398416049e0b6 to your computer and use it in GitHub Desktop.
NATS .NET KV TryGet Example
// 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