Orion.Abstractions 0.2.0

There is a newer version of this package available.
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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Orion.Abstractions" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Orion.Abstractions" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Orion.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Orion.Abstractions --version 0.2.0
                    
#r "nuget: Orion.Abstractions, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Orion.Abstractions@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Orion.Abstractions&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Orion.Abstractions&version=0.2.0
                    
Install as a Cake Tool

<p align="center"> <img src="docs/logo.png" alt="Orion.Abstractions" width="150" /> </p>

Orion.Abstractions

CI/CD NuGet

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, and OperationCanceledException always 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 named ActivitySource and Meter, plus a static-tag stamping pattern for multi-tenant / multi-region dashboard splitting without a second Meter.
  • Testable clock (IOrionClock / SystemOrionClock) - a thin seam over TimeProvider so every Orion background worker, lease, and scheduler shares one clock contract and one DI registration.
  • Deterministic test clock (FrozenOrionClock, in Orion.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 via TryAdd, so it is safe to call from multiple Orion packages and a consumer override always wins.
  • No dependencies beyond Microsoft.Extensions.DependencyInjection.Abstractions; multi-targets net8.0, net9.0, and net10.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.Testing from test projects and inject FrozenOrionClock wherever production injects IOrionClock. Advancing the clock makes lease-expiry, grace-period, and scheduler tests deterministic and instant.
  • SafeObserverInvoker is 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

Contributing

Contributions are welcome. See CONTRIBUTING.md and the CODE_OF_CONDUCT.md.

License

MIT.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
1.2.0 291 7/29/2026
1.1.0 118 7/29/2026
1.0.0 1,151 7/20/2026
0.3.0 402 6/22/2026
0.2.0 181 6/18/2026
0.1.0 169 6/18/2026