React to changes with watch
In this tutorial, you subscribe to changes in the store and react to them the moment they happen, instead of polling for updates. You build a console app that watches a single key and then a group of keys by prefix, printing each change as it arrives. You need the .NET SDK.
Watch is push-based: you register a handler once, and the store calls it for every change to what you watch until you stop the subscription. The watch surface lives on IZaris, so you cast the client to IZaris first.
1. Create a project and add the SDK
Create a console project and add the Zaris SDK.
dotnet new console -o zaris-watch
cd zaris-watch
dotnet add package Clustron.Zaris.SDK
2. Get an IZaris client
In Program.cs, register an in-process store and resolve the client as IZaris so the Watch accessor is available.
using Clustron.Zaris.Client;
using Clustron.Zaris.Client.DependencyInjection;
using Clustron.Zaris.Abstractions;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection()
.AddClustronZaris("demo", "zaris://inproc/demo")
.BuildServiceProvider();
var client = (IZaris)await services
.GetRequiredService<IZarisClientProvider>()
.GetAsync("demo");
3. Watch a single key
Call Watch.WatchKeyAsync with the key, a WatchOptions, and a handler. It returns a tuple: the subscription handle you use to stop later, and the record captured at subscription time. Setting IncludeInitialSnapshot to true delivers the current value as the first event, so your handler starts from the real state rather than waiting for the next write.
var (subscription, initialRecord) = await client.Watch.WatchKeyAsync(
"order:1001",
new WatchOptions { IncludeInitialSnapshot = true },
ev =>
{
Console.WriteLine($"{ev.EventType}: {ev.Key} = {ev.Value}");
});
Because IncludeInitialSnapshot is true, the first event your handler receives is a WatchEventType.Snapshot carrying the current value, not a Put or Delete — subsequent mutations arrive as Put and Delete. The WatchEventType enum also includes Heartbeat (a periodic liveness signal with no data change) and Evicted (the key was dropped under memory pressure), so a robust handler branches on the event type rather than assuming only Put/Delete.
4. Trigger a change
Write to the watched key. The handler runs when the change lands, printing the new value.
await client.PutAsync("order:1001", "confirmed");
// give the push a moment to arrive before the program continues
await Task.Delay(500);
Each WatchEvent carries the Key that changed, its new Value, an EventType of Put (created or updated) or Delete (removed), and a Revision that increases with every change. Use Revision to order the events you receive.
5. Stop the subscription
A subscription runs until you stop it, so stop every one you start. Call StopAsync to end delivery and release the resources the subscription holds.
await subscription.StopAsync();
6. Watch a group of keys by prefix
To follow many related keys with one subscription, use Watch.WatchPrefixAsync. It watches every key that starts with the prefix and returns just the subscription handle. Branch on EventType to handle creates and updates separately from deletes.
var jobs = await client.Watch.WatchPrefixAsync(
"jobs:",
new WatchOptions { IncludeInitialSnapshot = false },
ev =>
{
if (ev.EventType == WatchEventType.Put)
{
Console.WriteLine($"Job changed: {ev.Key} = {ev.Value}");
}
else if (ev.EventType == WatchEventType.Delete)
{
Console.WriteLine($"Job removed: {ev.Key}");
}
});
await client.PutAsync("jobs:build", "queued");
await client.PutAsync("jobs:test", "queued");
await client.DeleteAsync("jobs:build");
await Task.Delay(500);
await jobs.StopAsync();
Writes to jobs:build and jobs:test both reach this handler because both keys start with jobs:; a write to an unrelated key would not.
What you built
You reacted to store changes as they happened, with no polling. You watched a single key and a prefix, read the change from each WatchEvent — its Key, Value, EventType, and Revision — and stopped each subscription cleanly with StopAsync. You saw why IncludeInitialSnapshot matters when your handler needs the current state to be correct, such as when it builds a local cache. Keep handlers short and non-blocking, and stop every subscription you start so it does not keep consuming resources for the life of the client.