Skip to main content

Client lifecycle

A Zaris client is a long-lived, thread-safe object meant to be created once and reused everywhere. Some resources you obtain through it — transactions and watch subscriptions — are short-lived and must be disposed or stopped. Getting this split right keeps your application efficient and leak-free.

Create the client

You obtain a client through IZarisClientProvider, which manages creation and reuse for you. Do not construct clients yourself.

var client = await provider.GetAsync("default");

The provider hands you a client for the named store and reuses the underlying connection across calls.

Reuse the client

Resolve a client once and reuse it for the life of the component that needs it.

Create once, reuse everywhere.

Store the resolved client in a field rather than resolving it per call. Here a service resolves its client at construction and reuses it for every operation.

public class OrderService
{
private readonly IZarisClient _client;

public OrderService(IZarisClientProvider provider)
{
_client = provider.GetAsync("orders").GetAwaiter().GetResult();
}
}

Thread safety

The client is thread-safe, so a single instance serves your whole application. You can share one client across threads, use it for parallel operations, and call it from async workflows concurrently.

Multiple threads, one shared client, safe.

Do not resolve a client per operation

The client is expensive to create relative to a single operation, so resolving a fresh one for each call is the pattern to avoid — especially inside a loop or a per-request handler.

var client = await provider.GetAsync("default");

await client.PutAsync("key", "value");

The PutAsync call is fine; the mistake is putting the GetAsync("default") next to it so a new client is resolved every time this code runs. Repeatedly resolving clients adds overhead, churns connections, and reduces throughput. Resolve once and reuse the instance instead.

warning

Reuse the client; dispose the short-lived resources. The client is long-lived and shared, so you almost never dispose it yourself. Transactions and watch subscriptions are the opposite: each one you create must be disposed or stopped, or it leaks for the life of the client.

Dispose the right things

The disposal rule differs by object. The table summarizes it.

ObjectLifetimeYour responsibility
ClientLong-lived, sharedManaged by the provider; you do not dispose it manually
TransactionPer unit of workDispose it, ideally with await using
Watch subscriptionUntil no longer neededStop it with StopAsync

Client

You typically do not dispose the client. Its lifecycle is owned by the provider and it is reused across the application.

Transactions

A transaction is scoped to one unit of work and must be disposed. Bind it with await using so it is released even if an exception is thrown.

await using var tx = await client.BeginTransactionAsync();

Watch subscriptions

A watch subscription runs until you stop it. Keep the subscription handle returned when you subscribe and call StopAsync when you no longer need the events.

Watch subscriptions — along with Scan, Leases, Locks, and Counters — are exposed by IZaris, the full client interface. The client the provider hands you also implements IZaris, so cast to it to reach these members.

var full = (IZaris)client;

var (subscription, _) = await full.Watch.WatchKeyAsync(
"key",
new WatchOptions { IncludeInitialSnapshot = true },
ev => { /* handle event */ });

// later, when you no longer need updates
await subscription.StopAsync();

Application lifetime and multiple stores

A client should live as long as the application. Register it once at startup, reuse it across your services, and let it be released when the application shuts down.

If you use several stores, resolve one client per store. Each is independently managed, reusable, and thread-safe.

var orders = await provider.GetAsync("orders");
var cache = await provider.GetAsync("cache");

Best practices

These habits keep resource use correct.

  • Resolve each client once and reuse it; never resolve one inside a loop or per request.
  • Let the provider own client lifecycle rather than disposing clients yourself.
  • Dispose every transaction, ideally with await using.
  • Stop every watch subscription with StopAsync when you are done with it.
  • Keep long-running logic outside transactions to hold them for as short a time as possible.

Next steps