UmaDb.Client
0.7.8
dotnet add package UmaDb.Client --version 0.7.8
NuGet\Install-Package UmaDb.Client -Version 0.7.8
<PackageReference Include="UmaDb.Client" Version="0.7.8" />
<PackageVersion Include="UmaDb.Client" Version="0.7.8" />
<PackageReference Include="UmaDb.Client" />
paket add UmaDb.Client --version 0.7.8
#r "nuget: UmaDb.Client, 0.7.8"
#:package UmaDb.Client@0.7.8
#addin nuget:?package=UmaDb.Client&version=0.7.8
#tool nuget:?package=UmaDb.Client&version=0.7.8
UmaDb .NET Client
A high-performance .NET client for UmaDB, designed for Dynamic Consistency Boundaries (DCB).
Documentation: C# client guide — connection, concepts, recipes (append/read, consistency boundaries, projections, tracking, idempotent append), API reference, and managing client lifetime.
F# client: under development.
Architecture Decision Records (ADR)
ADR 1: Target Performance & Low Allocations
- Decision: Use
ValueTask<T>for the internal engine and gRPC transport viaprotobuf-net.Grpc. - Rationale: UmaDB handles high-frequency event streams.
ValueTaskgives zero-allocation paths for synchronous completions, reducing GC pressure.
ADR 2: Triple-Surface API (The Language Bridge)
- Decision: Expose different types for C# and F# through a Core library and an Wrappers.
- Rationale: C# and F# idiomatic code differs, thus different clients (wrappers) can align better to each idiom. Both sit on the same high-perf engine.
ADR 3: F# client uses task / taskSeq (not Async<'T>) as its primary async model
- Decision: The public F# client (
UmaDb.Fsharp) uses F#task/taskSeq(i.e. .NETTask/IAsyncEnumerable) as its primary async surface, especially for reads and streaming, rather than wrapping everything inAsync<'T>/AsyncSeq. - Rationale:
- Direct gRPC alignment: The generated gRPC stubs expose
TaskandIAsyncEnumerable<ReadResponse>with explicitCancellationToken. Usingtask/taskSeqlets the F# client sit directly on these types without extra adapter layers or duplicate cancellation channels. - Streaming & cancellation semantics: Long-lived reads/subscriptions are driven by explicit
CancellationTokens and gRPC stream lifetime.taskSeqkeeps this 1:1 with the transport;Async<'T>would introduce an additional implicit token (Async.CancellationToken) that does not map cleanly to gRPC. - Performance on hot paths: UmaDB clients are used in high-throughput consumers and projections. Every conversion between
Task/IAsyncEnumerableandAsync<'T>/AsyncSeqadds state machines, allocations, and potential context switches. Keeping the public surface ontask/taskSeqminimizes overhead in the streaming path. - F# ergonomics preserved: F# developers can still opt into
Async<'T>at their own boundaries (e.g.Task→Async<'T>viaAsync.AwaitTask, or materializingtaskSeqto a list), but the core library remains “close to the wire” and does not force that model internally.
- Direct gRPC alignment: The generated gRPC stubs expose
ADR 4: Event metadata is exposed as a keyed map (client), backed by a repeated wire type
- Decision:
Event.metadatais exposed to users as a keyed map —Map<string, string> optionin the F# client andIReadOnlyDictionary<string, string>in the C# client — while the wire/Core representation stays arepeated MetadataEntrylist (ResizeArray<MetadataEntry>inUmaDb.Core). - Context: An earlier revision of this client used an ordered list of pairs because the server did not enforce key uniqueness, so a map would have silently dropped duplicate keys on read. As of UmaDB 0.7.8 the server rejects duplicate metadata keys on append, so keys are now guaranteed unique in stored events.
- Rationale:
- With uniqueness guaranteed server-side, a keyed map is the natural, most ergonomic representation for keyed lookup data (correlation id, source, etc.) and mirrors the Python client's
dict[str, str]. - The wire type stays
repeated MetadataEntry(not proto3map) for compatibility; the client converts to/from the map at the edge. Conversion on read is last-wins (Map.ofSeq/ dictionary indexer) so any legacy event written before the uniqueness check does not throw. - Entry order is not preserved by these types (F#
Mapis key-sorted;Dictionaryis unordered). This is acceptable because metadata is keyed lookup data — consumers read by key and do not depend on entry order.
- With uniqueness guaranteed server-side, a keyed map is the natural, most ergonomic representation for keyed lookup data (correlation id, source, etc.) and mirrors the Python client's
Managing UmaClient
Reuse one client per process — gRPC channels are meant to be reused (Microsoft guidance). Creating a client per call adds connection overhead.
- C#: Register
UmaClientas a singleton in DI; the host disposes it on shutdown. - F#: Create one client at startup (e.g.
use client = UmaClient.Connect(...)inmain) and pass it where needed.
For DI registration, subscription workers, and full API details, see the C# client documentation.
Performance
- Trimming: Compatible with .NET 10 trimming and AOT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FSharp.Core (>= 10.1.302)
- Grpc.Net.Client (>= 2.80.0)
- protobuf-net.Grpc (>= 1.2.2)
- System.Security.Cryptography.Pkcs (>= 10.0.10)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.