BFH.Hermetic.Logos 0.3.5

Suggested Alternatives

BulletsForHumanity.Hermetic

Additional Details

The functionality of this package has been merged into BulletsForHumanity.Hermetic

dotnet add package BFH.Hermetic.Logos --version 0.3.5
                    
NuGet\Install-Package BFH.Hermetic.Logos -Version 0.3.5
                    
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="BFH.Hermetic.Logos" Version="0.3.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BFH.Hermetic.Logos" Version="0.3.5" />
                    
Directory.Packages.props
<PackageReference Include="BFH.Hermetic.Logos" />
                    
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 BFH.Hermetic.Logos --version 0.3.5
                    
#r "nuget: BFH.Hermetic.Logos, 0.3.5"
                    
#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 BFH.Hermetic.Logos@0.3.5
                    
#: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=BFH.Hermetic.Logos&version=0.3.5
                    
Install as a Cake Addin
#tool nuget:?package=BFH.Hermetic.Logos&version=0.3.5
                    
Install as a Cake Tool

BFH.Hermetic

Build & Test NuGet BFH.Hermetic NuGet BFH.Hermetic.Trine

Early development. Hermetic is under active development and not yet at 1.0. APIs may change or break between releases. Until 1.0, breaking changes bump the minor version and non-breaking changes bump the patch version.


What Is Hermetic?

Attribute-driven code generation for event-sourced .NET applications.

You declare a command. You write a handler. Hermetic generates everything in between — endpoints, projections, DI registrations, and fully typed Refit client methods — at compile time, with zero runtime reflection.

Hermetic is built on four ideas:

  1. Trine — a domain modelling philosophy that organises every domain as three interdependent planes: MIND (identity), ENERGY (process), MATTER (containers)
  2. Hierarchical Keys — structured, derived stream keys that eliminate ID generation, database round-trips, and foreign key lookups
  3. The Event Contract — a bidirectional, compile-time-verified chain that proves every event in the system is correct before the code compiles
  4. The Sealed Pipeline — Roslyn generators that transmute declarations into running infrastructure with zero hand-wiring

It hooks into plain ASP.NET Core. It is fully trimmable, fully AOT-compatible, and uses no runtime reflection. Every generated path is aggressively inlined.


See It In Action

You write this. A command, an event, an aggregate, and a handler:

// The event
[AppliedBy<Thing>]
public sealed record ThingCreated(ThingId ThingId, string Name) : IEventLaw;

// The command + handler
[RaisesEvent<ThingCreated>]
public sealed record CreateThing(ThingId ThingId, string Name) : ICommandLaw, IThingCommand
{
    public sealed class Handler : CommandHandler<CreateThing>
    {
        public override async IAsyncEnumerable<IEffectLaw> Handle(
            CreateThing cmd, [EnumeratorCancellation] CancellationToken ct)
        {
            yield return new OpenChronicleEffect<Thing>(
                cmd.ThingId.Value,
                new ThingCreated(cmd.ThingId, cmd.Name));
        }
    }
}

// The aggregate
[AppliedBy<ThingCreated>]
public sealed partial record Thing(ThingId Id, string Name)
    : IAggregateRoot<ThingId, Thing>
{
    [Applies<ThingCreated>]
    public static Thing Create(IEventEnvelope<ThingId, ThingCreated> e)
        => new(e.Data.ThingId, e.Data.Name);
}

You call this. A fully typed, IntelliSense-visible method on your Refit interface:

await _thingApi.CreateThingAsync(ThingId.New(), "My first thing", ct);

Hermetic generates everything in between:

What Where How
POST /api/command/create-thing Backend Minimal-API endpoint with validation, handler dispatch, event routing, and SaveChangesAsync
CreateThing.Handler DI registration Backend Scoped service registration
ThingProjection Backend Marten SingleStreamProjection<Thing, ThingId> with inline lifecycle
CreateThingAsync(thingId, name, ct) Client Typed extension method on your [CommandApiSeal] Refit interface
SendCreateThingCommandAsync(cmd, ct) Client Object-form extension method for pre-built commands
Telemetry routing Client Transparent middleware via [CommandPrinciple] — zero boilerplate at the call site

No routing code. No projection registration. No Refit method written by hand. No endpoint wiring.

You declared what the domain is. Hermetic manifested the rest.


Trine — How to Model a Domain

Trine is the structural principle behind every domain built with Hermetic. It holds that every coherent domain can be understood as three planes:

Plane Element Nature In a domain
MIND Sulphur — the animating principle Identity · being · what a thing is The root entity, identity aggregates, the atoms of meaning
ENERGY Mercury — the bridge, the messenger Process · movement · what crosses boundaries Aggregates that coordinate between MIND and MATTER
MATTER Salt — the body, crystallised form Container · persistence · what holds shape Structures that hold and organise what MIND produces

Every domain has exactly one omnipresent root at the apex of MIND — the single entity from which all identity emanates. The pattern is recursive: an aggregate that owns children becomes the root for those children. The same structural relationship — source, derivation, containment — repeats at every scale.

The three planes map to a folder structure, because the structure is the architecture:

MIND/
  [root entity, identity aggregates, atoms of meaning]
  Laws/             <- all contracts live here
  ENERGY/
    [process and coordination aggregates]
    MATTER/
      [container and persistence aggregates]

ENERGY nests inside MIND. MATTER nests inside ENERGY. What is above contains what is below; what is below depends on what is above.

Full guide: Modelling a Domain walks through Trine with a complete example domain. For the philosophical background, see Trine — Tripartite Domain Architecture.


The Hierarchical Key System

Every event stream is keyed by a structured path that encodes the full ancestor chain:

ROOT                           <- the omnipresent root
ROOT|org:42                    <- a top-level identity aggregate
ROOT|org:42|proj:7             <- a child aggregate
ROOT|org:42|proj:7|task:3      <- a grandchild aggregate

Keys are derived, not assigned. The handler computes the key from the aggregate's current state and injects it into the event. No GUIDs. No database round-trips. No client-side ID generation.

Why this matters:

  • Ancestor derivation without queries. Strip the last segment to navigate up. [BubblesTo<TAncestor>] uses this to copy events to any ancestor stream without a database lookup.
  • No foreign key lookups. A key like ROOT|org:42|proj:7|task:3 is simultaneously a reference to the task, its project, and its organisation.
  • Deterministic. Command handlers run sequentially against the latest aggregate state — the next child key is always computable. No race conditions.
  • Immutable. An entity's position in the hierarchy is permanent. If something needs to move, it carries its own stable identity and holds a reference to its current location.

Full guide: Hierarchical Keys covers the key grammar, Parts, discriminators, parameters, and the Reference/Anchor system.


The Event Contract

Hermetic enforces event correctness through a bidirectional contract — compile-time rules that guarantee every event has a handler and every handler has an event. The contract has three links:

1. Commands declare what events they produce:

[RaisesEvent<ThingCreated>]         // always yielded
[CanRaiseEvent<ThingNotified>]      // conditionally yielded
public sealed record CreateThing(...) : ICommandLaw { ... }

2. Events declare who handles them:

[AppliedBy<Thing>]
public sealed record ThingCreated(...) : IEventLaw;

3. Handlers declare what events they process:

[Applies<ThingCreated>]
public static Thing Create(IEventEnvelope<ThingId, ThingCreated> e) => ...;

The chain is closed: Command → [RaisesEvent] → Event → [AppliedBy] → Aggregate → [Applies] → back to Event. Break any link and the build fails.

This extends across five propagation modes:

Mode What happens Compile-time guarantee
P1 — Own stream Event applied by aggregate root [AppliedBy<T>][Applies<T>]
P2 — Query aggregate Event updates a read model [UpdatesQueryAggregate<T>][Applies<T>]
P3 — Parent bubble Event copies to ancestor stream via key derivation [BubblesTo<T>] requires [AppliedBy<T>]
P4 — Cross-stream Handler writes different event to different stream Both events checked independently
P5 — Shared event Same event written to multiple streams Multiple [AppliedBy<T>], each verified

Full guide: Events and Commands covers the contract, handlers, effects, and all propagation modes in detail.


The Sealed Pipeline

Hermetic is the sealed circuit between the Law (domain contracts) and the API boundary. When you declare a command, the pipeline produces — at compile time:

Generator What it produces
CommandEndpointsSigilWork POST /api/command/{name} endpoint per command handler
QueryEndpointsSigilWork GET /api/query/{name}/{id} endpoint per query
CommandHandlersSigilWork DI registrations for all command handlers
MartenProjectionsSigilWork Marten projection classes + registration with correct lifecycle
CommandApiSealWork Typed command extension methods on Refit interfaces
QueryApiSealWork Typed query extension methods on Refit interfaces
PolymorphicLawWork [JsonPolymorphic] / [JsonDerivedType] partials
EssencePrimitiveConverterWork JSON converter registrations for primitive types
NpgsqlTypeResolverWork Npgsql type converters for LINQ queries with essence types

The pipeline is controlled by three kinds of attributes:

  • Seals ([CommandApiSeal<T>], [QueryApiSeal<T>]) — scope which commands/queries appear on a Refit interface
  • Principles ([CommandPrinciple], [QueryPrinciple]) — inject cross-cutting concerns (telemetry, retries) into every generated call
  • Sigils ([MartenProjectionsSigil], [CommandHandlersSigil], etc.) — mark where generators emit registration code

Every generated method carries [MethodImpl(AggressiveInlining | AggressiveOptimization)]. There is no dictionary lookup, no string-based dispatch, no reflection.

Full guide: The Sealed Pipeline covers Seals, Principles, and Sigils with examples.


Primitives — Essence

Before declaring commands and aggregates, you need typed identifiers and value objects. Hermetic provides three primitive interfaces, each with compile-time enforcement (WORD analyzer series) and source-generated members:

Interface Kind Declaration
IIdentifier<T> Typed ID — parsable, comparable, stringable public readonly partial record struct ThingId : IIdentifier<Guid>;
ISmartEnum Closed enumeration with a string key public sealed partial record ThingStatus : ISmartEnum;
IEssence<T> Validated value object — always constructed via Create() public sealed partial record ThingName : Essence<string>;

The partial keyword is always required — the Logos generators fill in serialization, parsing, equality, and validation.

Full guide: Primitives


The Three Aggregate Roles

Interface Role Chronicle Projection Accepts commands
IAggregateRoot<TId, TSelf> Owns the chronicle; is its own read model Own stream Inline Yes
ICommandAggregate<TId, TRoot> Command scoping; events land on root's stream Root's stream Inline Yes
IQueryAggregate<TId, TRoot> Pure read model; reacts to events Root's stream Async No

IAggregateRoot is self-referential — it extends IQueryAggregate<TId, TSelf>, making every aggregate root its own primary read model. The three roles mirror the Tria Prima at the aggregate level: the root is (Sulphur), the command aggregate acts (Mercury), the query aggregate holds a view (Salt).

Full guide: Aggregates


Packages

Package What it is Install
BFH.Hermetic Core contracts, all attributes, and the Logos generators bundled as analyzers dotnet add package BFH.Hermetic
BFH.Hermetic.Trine Meta-package — a semantic dependency on the Trine domain modelling style; pulls in BFH.Hermetic dotnet add package BFH.Hermetic.Trine
BFH.Hermetic.Logos The standalone Roslyn analyzer + generator package — already bundled inside BFH.Hermetic Advanced use only
BFH.Hermetic.Logos.Fixes Code fix providers for Logos analyzers — already bundled inside BFH.Hermetic Advanced use only

For most projects: install BFH.Hermetic. The Logos generators and code fixers are embedded inside it and activate automatically.


Technical Properties

Property Detail
Runtime reflection None. Zero. The entire generation pipeline operates at compile time.
Trimming Fully trimmable. All generated code is trim-safe.
AOT Fully compatible with Native AOT. No dynamic type loading, no Reflection.Emit.
Inlining Every generated method carries [MethodImpl(AggressiveInlining | AggressiveOptimization)].
Targets net10.0 for runtime projects · netstandard2.0 + net10.0 for analyzers/generators
Primary integrations Marten (event sourcing + document DB) · Refit (typed HTTP)
Server framework Plain ASP.NET Core minimal APIs — no custom server, no middleware framework
OpenAPI Generated endpoints produce standard OpenAPI descriptions for cross-platform client generation

Hermetic without Marten, without Refit? The framework is designed to hook into plain ASP.NET Core. The Marten and Refit integrations are the primary tested path, but the generation pipeline reads only from attributes and interfaces defined in BFH.Hermetic. Plugging different infrastructure behind the same sealed surface is architecturally possible, though not yet validated.


Documentation

Guides — How to Build With Hermetic

Guide What it covers
Modelling a Domain Trine in practice — three planes, the omnipresent root, folder structure, worked example
Primitives IIdentifier, ISmartEnum, IEssence — typed IDs, closed enumerations, value objects
Hierarchical Keys The key system — grammar, Parts, discriminators, parameters, References and Anchors
Aggregates Three aggregate roles — Root, Command, Query — stream ownership, when to use which
Events and Commands The event contract, command handlers, effects, five propagation modes
The Sealed Pipeline Seals, Principles, Sigils — how declarations become infrastructure

Architecture — How Hermetic Works

Document What it covers
Trine — Tripartite Domain Architecture Full Trine model: three planes, omnipresent root, hierarchical key system, event propagation
Hermetic — Full Pipeline Reference All attributes, all generators, the Principle pattern, Marten projection pipeline, end-to-end
Analyzer & Fixer Registry Complete WORD and LAW diagnostic registry with severity, rules, and implementation status

License

Hermetic is licensed under the Business Source License 1.1.

You may use Hermetic to build applications — commercial or otherwise. You may not use it to create a competing code-generation framework or offer it as a hosted service.

The license converts automatically to Apache 2.0 four years after each versioned release.


Feedback

This is a preview. The API will change. If you are building with Hermetic and something is wrong, missing, or beautiful — open an issue. Early signal shapes everything.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.