Fluens.Kernel
0.7.7
See the version list below for details.
dotnet add package Fluens.Kernel --version 0.7.7
NuGet\Install-Package Fluens.Kernel -Version 0.7.7
<PackageReference Include="Fluens.Kernel" Version="0.7.7" />
<PackageVersion Include="Fluens.Kernel" Version="0.7.7" />
<PackageReference Include="Fluens.Kernel" />
paket add Fluens.Kernel --version 0.7.7
#r "nuget: Fluens.Kernel, 0.7.7"
#:package Fluens.Kernel@0.7.7
#addin nuget:?package=Fluens.Kernel&version=0.7.7
#tool nuget:?package=Fluens.Kernel&version=0.7.7
Fluens.Kernel
DDD building blocks and Result pattern. Zero dependencies.
Installation
dotnet add package Fluens.Kernel
Usage
Result Pattern
// Result without value
Result result = Result.Success();
Result failure = Result.Failure(new NotFoundError("User not found"));
// Result with value
Result<int> result = Result.Success(42);
Result<int> failure = Result.Failure<int>(new ValidationError("Invalid input"));
// Pattern matching
string message = result.Match(
onSuccess: value => $"Got: {value}",
onFailure: error => $"Error: {error.Message}"
);
Both Result and Result<T> are readonly struct types — zero heap allocations, default(Result) is a valid success.
Both types implement IEquatable<> (Equals, GetHashCode, ==, !=), comparing IsFailure and
Error (structurally, via Error's own equality — see below), plus the stored value for Result<T>
on the success branch. Result.Success().Equals(Result.Success()) and default(Result<T>).Equals(Result.Success(default(T)))
both return true; a success and a failure are never equal. This makes both types safe as dictionary
keys or HashSet<> entries, replacing the reflection-based ValueType.Equals fallback that MA0065
used to flag.
Result.Error and Result<T>.Error are Error? — null exactly when IsSuccess is true. Always check IsFailure (or use Match) before reading Error, or use a null-safe form such as result.Error?.Code.
Result<T>.Value is state-guarded: on success it returns the stored value (including a legitimately stored null/default); on failure it throws Result.MissingValueException. The guard keys on the result state (IsFailure), not on a null check of the backing field — always check IsSuccess/IsFailure (or use Match) before reading Value.
Result<int> failure = Result.Failure<int>(new ValidationError("Invalid input"));
int value = failure.Value; // throws Result.MissingValueException
Conversions
// Implicit: value → Result<T>
Result<int> result = 42;
// Explicit: Result<T> → Result (discards value)
Result plain = (Result)result;
// As<T>(): failure Result → Result<T>
Result failure = Result.Failure(new NotFoundError("Missing"));
Result<int> typed = failure.As<int>(); // preserves error
// As<T>() on success throws InvalidOperationException
Result.Success<T>(value) guards against silently swallowing a failure: when T is one of the four
ambiguous carrier shapes — object, ValueType, Result, or a constructed Result<U> — and value
is itself a failed result, it throws Result.FailureSwallowedException instead of yielding a success.
Use As<T>() to move a failure across result types; Result.Success<T>(value) is reserved for actual
success values.
Result failed = Result.Failure(new NotFoundError("Missing"));
Result<object> boxed = Result.Success<object>(failed); // throws Result.FailureSwallowedException
Result<object> typed = failed.As<object>(); // correct: preserves the failure
Result<int> ok = 42; // still compiles and succeeds — int is not an ambiguous carrier
Built-in error types: NotFoundError, ValidationError, ConflictError, ForbiddenError.
Message Arguments
Errors support parameterized message arguments for translations via MessageArgs and fluent WithArg:
// Create error with message arguments for parameterized translations
var error = new NotFoundError("Order {orderId} not found")
.WithArg("orderId", orderId.ToString());
// Access arguments
error.MessageArgs!["orderId"]; // "123"
// Chain multiple arguments
var error = new Error("CUSTOM", "Hello {name}, you have {count} items")
.WithArg("name", "John")
.WithArg("count", "5");
WithArg is immutable — each call returns a new Error instance without mutating the original. MessageArgs is IReadOnlyDictionary<string, string>? (null by default).
Error equality compares MessageArgs structurally and order-independently: two errors are equal
when their EqualityContract (runtime record type), Code, Message and MessageArgs all match —
a null MessageArgs and an empty dictionary are treated as equivalent, and key order does not
matter. This makes Error values built through WithArg safe to use as dictionary keys or inside a
HashSet<Error>; two errors built through identical WithArg chains deduplicate to one entry. A
derived error type (e.g. NotFoundError) never equals a differently-typed Error with the same code
and message, because the EqualityContract check still applies.
Error Codes
Standard error codes are centralized in the ErrorCodes static class:
ErrorCodes.NotFound // "NOT_FOUND"
ErrorCodes.Validation // "VALIDATION"
ErrorCodes.Conflict // "CONFLICT"
ErrorCodes.Forbidden // "FORBIDDEN"
Use these constants instead of hardcoded strings when comparing error codes:
if (result.Error is { } error && error.Code == ErrorCodes.NotFound)
{
// handle not found
}
DDD Building Blocks
// ITypedIdentifier<out T> keeps `out T` for signature consistency, but the annotation is inert:
// `where T : struct` means no two value types share an implicit reference conversion, so no
// usable covariance actually exists here.
public readonly record struct OrderId(int Value) : ITypedIdentifier<int>;
// Entities with identity-based equality
public class Order : AggregateRoot<OrderId>
{
public string Number { get; private set; } = "";
public void Place()
{
AddEvent(new OrderPlacedEvent(ID));
}
}
// Value objects with structural equality
public sealed class Money : ValueObject
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
protected override IEnumerable<object?> GetAtomicValues()
{
yield return Amount;
yield return Currency;
}
}
ValueObject is an abstract class, not a record — Equals(object?) and GetHashCode() are
sealed on the base type, so a derived value object cannot reintroduce member-wise equality that
bypasses GetAtomicValues(). Derived value objects must therefore be sealed class with explicit
constructors and get-only properties, not sealed record — they lose with-expressions,
deconstruction and a synthesized ToString() as a consequence.
Interfaces: IAuditable (CreatedAtUtc, UpdatedAtUtc), IDeletable (DeletedAtUtc), IDomainEvent (marker interface — no members).
DeletableExtensions
Filter out soft-deleted entities from IQueryable<T> sources:
IQueryable<Order> activeOrders = dbContext.Orders.WithoutDeleted();
WithoutDeleted() returns only entities where DeletedAtUtc is null.
Translatable Messages
TranslatableMessage is a structure for storing translatable content (e.g. audit log entries) as JSON in a database. The message can be translated at read time using a ResourceManager:
// Create a translatable message with arguments
var message = new TranslatableMessage("order.status.changed")
.WithArg("orderId", "ORD-123")
.WithArg("oldStatus", "Pending")
.WithArg("newStatus", "Shipped");
// Serialize to JSON for database storage (compact property names)
var json = JsonSerializer.Serialize(message, TranslatableMessageSerializerContext.Default.TranslatableMessage);
// {"c":"order.status.changed","a":{"orderId":"ORD-123","oldStatus":"Pending","newStatus":"Shipped"}}
// Translate at read time using ResourceManager
var translated = message.Translate(resourceManager, CultureInfo.CurrentUICulture);
// "Order ORD-123 changed from Pending to Shipped"
WithArg is immutable — each call returns a new TranslatableMessage instance. TranslatableMessageSerializerContext provides AOT-compatible JSON serialization with WhenWritingNull to omit null Args.
TranslatableMessage equality compares Args the same way Error compares MessageArgs: structurally
and order-independently, with null and an empty dictionary treated as equivalent. Values built
through WithArg are safe as HashSet<TranslatableMessage> entries.
License
This project is licensed under the MIT License.
| 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
- No dependencies.
NuGet packages (4)
Showing the top 4 NuGet packages that depend on Fluens.Kernel:
| Package | Downloads |
|---|---|
|
Fluens.Web
ASP.NET Core base types for Fluens web libraries: Result-to-HTTP mapping, global exception handler, and paged responses. |
|
|
Fluens.Messaging
Inbox/outbox messaging pattern for modular applications. |
|
|
Fluens.Cqrs
CQRS pattern implementation with command/query dispatchers, handler interfaces, and logging/tracing decorators. |
|
|
Fluens.Messaging.Sagas
Process Manager / Saga abstraction on top of Fluens.Messaging. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.7.9 | 65 | 9/11/2026 |
| 0.7.8 | 88 | 9/10/2026 |
| 0.7.7 | 91 | 9/10/2026 |
| 0.7.6 | 300 | 7/1/2026 |
| 0.7.5 | 281 | 6/22/2026 |
| 0.7.4 | 295 | 6/18/2026 |
| 0.7.2 | 271 | 6/18/2026 |
| 0.7.1 | 290 | 6/18/2026 |
| 0.6.6 | 349 | 3/11/2026 |
| 0.6.5 | 193 | 3/4/2026 |
| 0.6.4 | 183 | 3/4/2026 |
| 0.6.3 | 194 | 3/3/2026 |
| 0.6.2 | 198 | 3/2/2026 |
| 0.6.1 | 198 | 3/2/2026 |
| 0.6.0 | 191 | 3/1/2026 |
| 0.5.7 | 199 | 3/1/2026 |
| 0.5.6 | 191 | 3/1/2026 |
| 0.5.5 | 195 | 2/28/2026 |
| 0.5.4 | 195 | 2/28/2026 |
| 0.5.3 | 198 | 2/27/2026 |