Orion.Abstractions
0.2.0
See the version list below for details.
dotnet add package Orion.Abstractions --version 0.2.0
NuGet\Install-Package Orion.Abstractions -Version 0.2.0
<PackageReference Include="Orion.Abstractions" Version="0.2.0" />
<PackageVersion Include="Orion.Abstractions" Version="0.2.0" />
<PackageReference Include="Orion.Abstractions" />
paket add Orion.Abstractions --version 0.2.0
#r "nuget: Orion.Abstractions, 0.2.0"
#:package Orion.Abstractions@0.2.0
#addin nuget:?package=Orion.Abstractions&version=0.2.0
#tool nuget:?package=Orion.Abstractions&version=0.2.0
<p align="center"> <img src="docs/logo.png" alt="Orion.Abstractions" width="150" /> </p>
Orion.Abstractions
Shared foundation primitives for the Orion family of .NET libraries. Three primitives kept being re-implemented (and kept drifting) across the family: fault-safe observer invocation, OpenTelemetry instrumentation conventions, and a testable clock. They now live here, once, correctly. The package has no Orion dependencies of its own, so any library can depend on it to inherit the Orion conventions.
Features
- Fault-safe observer invocation (
SafeObserverInvoker) - a null observer is a no-op, observer faults are swallowed so an observability outage cannot break the load-bearing path, andOperationCanceledExceptionalways propagates on cancellation. Includes a resolve-inside-the-guard variant so a throwing observer constructor cannot abort the host path at resolution time. - OpenTelemetry conventions (
OrionInstrumentation) - a base class that pairs a consistently namedActivitySourceandMeter, plus a static-tag stamping pattern for multi-tenant / multi-region dashboard splitting without a secondMeter. - Testable clock (
IOrionClock/SystemOrionClock) - a thin seam overTimeProviderso every Orion background worker, lease, and scheduler shares one clock contract and one DI registration. - Deterministic test clock (
FrozenOrionClock, inOrion.Abstractions.Testing) - a frozen, advanceable clock for testing lease expiry, grace periods, and scheduled work without real delays. - One-line DI registration (
AddOrionAbstractions) - registers the production clock viaTryAdd, so it is safe to call from multiple Orion packages and a consumer override always wins. - No dependencies beyond
Microsoft.Extensions.DependencyInjection.Abstractions; multi-targetsnet8.0,net9.0, andnet10.0; nullable enabled and warnings-as-errors.
Install
dotnet add package Orion.Abstractions
# Optional: the testing companion (FrozenOrionClock), reference from your test project only
dotnet add package Orion.Abstractions.Testing
Quick start
using Microsoft.Extensions.DependencyInjection;
using Moongazing.Orion.Abstractions;
using Moongazing.Orion.Abstractions.Time;
var services = new ServiceCollection();
services.AddOrionAbstractions(); // registers IOrionClock -> SystemOrionClock (TryAdd)
using var provider = services.BuildServiceProvider();
var clock = provider.GetRequiredService<IOrionClock>();
DateTimeOffset now = clock.UtcNow;
long start = clock.GetTimestamp();
// ... do work ...
TimeSpan elapsed = clock.GetElapsedTime(start);
Usage
Fault-safe observer invocation
Route every consumer-supplied observer hook through SafeObserverInvoker. A null observer is skipped, a faulting observer is swallowed (and optionally reported), and cancellation is never downgraded to a swallowed warning.
using Moongazing.Orion.Abstractions.Observers;
// Synchronous: a null observer is a no-op; a fault is swallowed and reported.
SafeObserverInvoker.Invoke(observer, o => o.OnSomething(payload),
onFault: ex => logger.LogWarning(ex, "observer faulted; host continued"));
// Asynchronous: OperationCanceledException propagates when the token is cancelled.
await SafeObserverInvoker.InvokeAsync(observer,
o => o.OnSomethingAsync(payload),
onFault: ex => logger.LogWarning(ex, "observer faulted"),
cancellationToken: ct);
// Resolution itself inside the guard: a throwing observer ctor cannot abort the host path.
SafeObserverInvoker.Resolve(
() => serviceProvider.GetService<IMyObserver>(),
o => o.OnSomething(payload),
onFault: ex => logger.LogWarning(ex, "observer resolution faulted"));
OpenTelemetry instrumentation
Derive a sealed diagnostics class from OrionInstrumentation. It exposes one ActivitySource and one Meter sharing a name and version. Create your instruments on Meter, and stamp every measurement through Tag(...) so the configured static tags are appended.
using System.Diagnostics.Metrics;
using Moongazing.Orion.Abstractions.Diagnostics;
public sealed class MyDiagnostics : OrionInstrumentation
{
public MyDiagnostics() : base("Moongazing.MyPackage", "1.0.0")
{
Things = Meter.CreateCounter<long>("my.things");
}
public Counter<long> Things { get; }
}
var diag = new MyDiagnostics();
// Set once at startup (single-threaded). These tags stamp every later measurement.
diag.SetStaticTags(new Dictionary<string, string> { ["tenant"] = tenantId });
// Tag(...) appends the static tags to the per-measurement tag.
diag.Things.Add(1, diag.Tag(new("outcome", "ok")));
When no static tags are configured, Tag(...) short-circuits to a single-element array, so the common single-tenant path stays allocation-light.
Testable clock
Depend on IOrionClock instead of DateTime.UtcNow or Stopwatch. Production binds SystemOrionClock (over TimeProvider.System); tests bind FrozenOrionClock.
using Moongazing.Orion.Abstractions.Testing;
var clock = new FrozenOrionClock(); // starts frozen at 2026-01-01Z by default
long start = clock.GetTimestamp();
clock.Advance(TimeSpan.FromSeconds(31)); // drive a lease past expiry, no real delay
Assert.Equal(TimeSpan.FromSeconds(31), clock.GetElapsedTime(start));
Advance moves both the wall clock and the monotonic timestamp; SetUtcNow moves only the wall clock. Both reject going backward, matching a real monotonic clock.
Configuration
AddOrionAbstractions() uses TryAddSingleton, so the first registration wins. To supply your own clock (for example, a SystemOrionClock over a custom TimeProvider), register it before calling AddOrionAbstractions:
services.AddSingleton<IOrionClock>(new SystemOrionClock(myTimeProvider));
services.AddOrionAbstractions(); // TryAdd no-ops because a clock is already registered
Telemetry / Diagnostics
OrionInstrumentation is the integration point for OpenTelemetry. The ActivitySource and Meter are named with the value you pass to the base constructor, so wire them into your OpenTelemetry pipeline by that name:
builder.Services.AddOpenTelemetry()
.WithTracing(t => t.AddSource("Moongazing.MyPackage"))
.WithMetrics(m => m.AddMeter("Moongazing.MyPackage"));
The static-tag pattern lets you split dashboards by tenant, region, or environment without standing up a second Meter. Tags configured after the host starts emitting do not retroactively apply to already-emitted measurements, which is why SetStaticTags is intended to be called once at startup.
Testing
- Reference
Orion.Abstractions.Testingfrom test projects and injectFrozenOrionClockwherever production injectsIOrionClock. Advancing the clock makes lease-expiry, grace-period, and scheduler tests deterministic and instant. SafeObserverInvokeris static and side-effect-free apart from the callbacks you pass, so it is straightforward to assert the no-op, happy, fault-swallowing, and cancellation-propagating paths directly.
A micro-benchmark suite (BenchmarkDotNet) covers the allocation- and CPU-bearing surface: tag stamping, observer dispatch, and the clock seam. See benchmarks.md. No measured numbers are committed; run the suite locally to produce them for your hardware.
Packages
| Package | Purpose |
|---|---|
Orion.Abstractions |
The shared primitives above. |
Orion.Abstractions.Testing |
FrozenOrionClock and future test doubles. |
Versioning
Follows Semantic Versioning. The library multi-targets net8.0, net9.0, and net10.0. (The benchmark host runs on net8.0 and net9.0 only, because BenchmarkDotNet 0.14.0 has no .NET 10 job moniker.)
Documentation
- docs/FEATURES.md - a deeper breakdown of each feature and its public types and methods.
- docs/ROADMAP.md - ideas under consideration.
- benchmarks.md - what the benchmark suite measures and how to run it.
Contributing
Contributions are welcome. See CONTRIBUTING.md and the CODE_OF_CONDUCT.md.
License
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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
-
net8.0
-
net9.0
NuGet packages (27)
Showing the top 5 NuGet packages that depend on Orion.Abstractions:
| Package | Downloads |
|---|---|
|
OrionAudit
EF Core change audit trail with JSON Patch diffs, multi-tenant support, time-travel reconstruction, and OpenTelemetry instrumentation. |
|
|
OrionPatch
Transactional outbox primitive for .NET. Enqueue messages inside an EF Core SaveChanges transaction; a background dispatcher hands them to a pluggable IOutboxSink at-least-once. Ships ChannelOutboxSink (in-process); broker sinks are opt-in sub-packages. |
|
|
OrionVault
Column-level transparent data encryption at rest for EF Core. AES-256-GCM with key rotation, [Encrypted] attribute, fluent API, bundled Roslyn analyzer, and OpenTelemetry instrumentation. |
|
|
OrionPatch.EntityFrameworkCore
EF Core storage backend for OrionPatch. Adds the OrionPatch_Outbox table; SaveChangesInterceptor flushes buffered messages into the user's transaction. |
|
|
OrionPatch.Testing
Test helpers for OrionPatch. In-memory IOutboxStorage, deterministic dispatcher driver, CapturingOutboxSink, TestClock, and fluent OutboxAssertions. Zero EF Core dependency. |
GitHub repositories
This package is not used by any popular GitHub repositories.