EricksonLopez.DomainPrimitives.Abstractions
2.0.0
dotnet add package EricksonLopez.DomainPrimitives.Abstractions --version 2.0.0
NuGet\Install-Package EricksonLopez.DomainPrimitives.Abstractions -Version 2.0.0
<PackageReference Include="EricksonLopez.DomainPrimitives.Abstractions" Version="2.0.0" />
<PackageVersion Include="EricksonLopez.DomainPrimitives.Abstractions" Version="2.0.0" />
<PackageReference Include="EricksonLopez.DomainPrimitives.Abstractions" />
paket add EricksonLopez.DomainPrimitives.Abstractions --version 2.0.0
#r "nuget: EricksonLopez.DomainPrimitives.Abstractions, 2.0.0"
#:package EricksonLopez.DomainPrimitives.Abstractions@2.0.0
#addin nuget:?package=EricksonLopez.DomainPrimitives.Abstractions&version=2.0.0
#tool nuget:?package=EricksonLopez.DomainPrimitives.Abstractions&version=2.0.0
EricksonLopez.DomainPrimitives
Zero-allocation, compile-time validated Domain Primitives, SmartEnums, and Roslyn Code Analyzers for modern .NET enterprise systems.
EricksonLopez.DomainPrimitives is the enterprise standard for modeling provably valid, zero-allocation scalar value types, strongly-typed identifiers, composite value objects, and SmartEnums in modern .NET (.NET 8, .NET 9, .NET 10). By combining compile-time Roslyn source generators, architectural code analyzers, and NativeAOT-first converters, it eliminates Primitive Obsession and defensive validation boilerplate while delivering bare-metal execution performance and zero heap allocations.
Table of Contents
- What Problem It Solves
- Key Features
- Ecosystem
- Documentation
- Installation
- Quick Start
- Core Use Cases
- Use Case 1: Clean Architecture / CQRS Command Handler
- Use Case 2: Multi-Step Domain Validation Pipeline
- Use Case 3: Zero-Allocation Minimal API Route & Body Model Binding
- Use Case 4: EF Core Relational Persistence Mapping
- Use Case 5: High-Throughput Microservice Queries with Dapper
- Use Case 6: Live Compile-Time Roslyn Architectural Enforcement
- Configuration & Integrations
- Testing & Quality
- Performance Benchmarks
- Compatibility & Technical Matrix
- Architecture & Design Principles
- Best Practices & Anti-Patterns
- Troubleshooting & Common Pitfalls
- Part of the EricksonLopez Ecosystem
- Contributing
- License
๐ฏ What Problem It Solves
Primitive Obsession is among the most pervasive anti-patterns in enterprise software engineering:
- The Hidden Cost of Primitive Obsession:
Using raw
string,Guid,int, ordecimaltypes allows illegal and unnormalized values (such as empty strings, malformed email addresses, or negative monetary balances) to traverse domain boundaries undetected. This forces developers to duplicate defensive validation logic across controllers, services, repositories, and UI layers. - Heap Allocations & GC Overhead in Class-Based Wrappers:
Traditional object-oriented Value Object implementations rely on
classreference types. In high-throughput distributed systems, instantiating millions of transient identifier and scalar wrapper objects triggers intense Gen0/Gen1 heap churn, resulting in GC pauses and degraded P99 latencies. - Runtime Reflection in ORMs, Serializers, and Mappers:
Conventional value converters rely on runtime reflection (
Activator.CreateInstance,MethodInfo.Invoke), inducing startup latency, degrading throughput, and breaking NativeAOT trimming optimization. - Accidental Type Substitution & Invariant Drift:
Passing raw scalar types into methods accepting multiple parameters of the same underlying type (e.g.
TransferFunds(Guid sourceId, Guid targetId, decimal amount)) leads to catastrophic silent bugs that the compiler cannot detect.
How EricksonLopez.DomainPrimitives Solves This
- Guaranteed Validity by Construction: Instances cannot be created in an invalid state. Constructors are private and creation is routed through source-generated
Create,TryCreate, andTryParsemethods that enforce validation rules deterministically. - Zero Heap Allocations on Hot Paths: Source-generated primitives are
readonly partial record structtypes that reside entirely on the stack or inline within entity memory layouts, achieving identical memory efficiency to raw BCL primitives (0 bytes allocated). - Compile-Time Incremental Code Generation: All factory methods, parsers (
IParsable<T>,ISpanParsable<T>,IUtf8SpanParsable<T>), formatters (ISpanFormattable,IUtf8SpanFormattable), equality operators, JSON converters, EF Core ValueConverters, and Dapper TypeHandlers are emitted at compile time. - Live IDE Architectural Enforcement: 17 dedicated Roslyn analyzers (DP0001โDP0017) intercept invalid modeling patterns, direct string comparisons, and public constructor bypasses in real time with automated code fixes.
- Full NativeAOT & Trimming Compatibility: Zero runtime reflection and zero dynamic IL emission guarantee instant startup, minimal binary footprints, and full compatibility with NativeAOT publishing.
โก Key Features
- ๐ Zero-Allocation Memory Footprint: Stack-allocated
readonly record structvalue types guarantee 0 B heap allocation on creation, comparison, and parsing hot paths. - ๐ ๏ธ Roslyn Incremental Source Generators: Compile-time emission of
IParsable<T>,ISpanParsable<T>,IUtf8SpanParsable<T>,ISpanFormattable, and explicit conversion operators. - ๐ Live Architectural Code Analyzers: 17 Roslyn diagnostic rules (DP0001โDP0017) with automated code fixes enforce immutability, validation integrity, and API surface budgets.
- ๐ท๏ธ 30+ Pre-Configured Semantic Shortcuts: Instant domain modeling with built-in attributes for strings (
[Email],[Phone],[Url],[Slug],[CountryCode],[IBAN],[ISBN]) and numerics ([Money],[Price],[TaxRate],[Percentage],[Quantity],[Rating]). - ๐งฉ Zero-Contamination Persistence Adapters: Compile-time auto-discovery adapters for Entity Framework Core (
ConfigureDomainPrimitives) and Dapper (RegisterAll). - ๐ NativeAOT & Trimming-First Architecture: 100% trim-safe execution with zero reflection, verified by continuous NativeAOT smoke testing.
- ๐ฏ Railway-Oriented Result Pattern Interop: Seamless zero-overhead integration with
EricksonLopez.Resultand third-party functional monads via theTryCreateoutparameter pattern. - ๐งช Comprehensive Testing & Data Tooling: Fluent assertions, scenario suites (
DomainPrimitiveScenarios), and realistic fake data generators (DomainPrimitiveFakeFactory).
๐ฆ Ecosystem
| Package | Version | Description |
|---|---|---|
EricksonLopez.DomainPrimitives |
Core domain primitives, SmartEnums, attributes, and Roslyn generators | |
EricksonLopez.DomainPrimitives.Abstractions |
Zero-dependency contracts (IDomainPrimitive<TSelf, TValue>, IStrongId<TSelf, TValue>, PrimitiveError) |
|
EricksonLopez.DomainPrimitives.AspNetCore |
ASP.NET Core Minimal APIs model binding & route parameter validation | |
EricksonLopez.DomainPrimitives.EFCore |
Entity Framework Core zero-contamination ValueConverter conventions | |
EricksonLopez.DomainPrimitives.Dapper |
Dapper compile-time type handlers and bulk auto-registration | |
EricksonLopez.DomainPrimitives.OpenApi |
Swagger / OpenAPI schema filter generators for primitive documentation | |
EricksonLopez.DomainPrimitives.Testing |
Fluent assertions, test builders, scenario data, and fake generators | |
EricksonLopez.DomainPrimitives.NewtonsoftJson |
Newtonsoft.Json contract resolvers and converters for legacy systems |
๐ Documentation
๐ Official Documentation Hub: https://github.com/ericksonlopezf/dotnet-domain-primitives/tree/main/docs
๐ Step-by-Step Interactive Showcase (Levels 00 to 08)
| Level | Topic | Description |
|---|---|---|
| Level 00 | Architecture & Philosophy | Core architectural foundations and design invariants |
| Level 01 | Domain Primitives & Validation | Implementing validated struct primitives with Result-first flows |
| Level 02 | SmartEnums & State Machines | Modeling polymorphic business states and transition guards |
| Level 03 | Roslyn Analyzers | Compile-time architectural invariants and automated IDE code fixes |
| Level 04 | Source Generation & NativeAOT | Compile-time code generation for zero-reflection execution |
| Level 05 | ASP.NET Core & OpenAPI | Binding primitives in Minimal APIs and OpenAPI documentation |
| Level 06 | EF Core & Dapper Persistence | Relational column mapping and Dapper type handlers |
| Level 07 | JSON Serialization | Direct token serialization with System.Text.Json & Newtonsoft |
| Level 08 | Fluent Testing & Assertions | Writing expressive unit tests with fluent validation matchers |
๐ Technical Reference & Architecture Guides
- Architecture & Invariants โ Complete architectural blueprint, memory layouts, and domain boundaries.
- Architectural Decision Records (ADRs) โ 43 formal ADRs documenting design rationale and rejected proposals.
- Technical Audit โ Comprehensive technical audit, guarantees, and system invariants.
- Competitive Audit โ In-depth market comparison vs StronglyTypedId and Vogen.
- Features & Compatibility Matrix โ Target framework matrix, diagnostics, and supported features.
- Roslyn Diagnostic Rules Reference โ Complete reference for analyzer rules DP0001 through DP0017.
- Testing & Quality Audit โ Quality gates, compiler settings, and 100% mutation test verification.
- Cookbook & Production Recipes โ 16 ready-to-use production recipes for enterprise architectures.
- Allocation & Memory Analysis โ Deep-dive memory analysis and zero-allocation proofs.
- Mutation Score Report โ Package-by-package Stryker.NET mutation testing score report.
- Security Architecture โ ReDoS prevention, Unicode NFC normalization, and PII protection specs.
- CI/CD & Build Pipeline โ Automated GitHub Actions workflows, AOT probes, and release automation.
๐ฅ Installation
Install the necessary packages using the .NET CLI or NuGet Package Manager:
1. Core Package (Required)
dotnet add package EricksonLopez.DomainPrimitives
2. Optional Framework & Persistence Packages
# ASP.NET Core Minimal APIs & MVC model binding
dotnet add package EricksonLopez.DomainPrimitives.AspNetCore
# Entity Framework Core ValueConverter auto-configuration
dotnet add package EricksonLopez.DomainPrimitives.EFCore
# Dapper TypeHandler registration
dotnet add package EricksonLopez.DomainPrimitives.Dapper
# Swagger / OpenAPI Schema generation
dotnet add package EricksonLopez.DomainPrimitives.OpenApi
# Newtonsoft.Json legacy serialization support
dotnet add package EricksonLopez.DomainPrimitives.NewtonsoftJson
3. Testing & Assertion Packages
# Fluent assertions, fake data factories, and scenario runners
dotnet add package EricksonLopez.DomainPrimitives.Testing
๐ Quick Start
1. Declarative Domain Primitive
Decorate a readonly partial record struct with semantic attributes. The source generator automatically emits parsers, formatters, validation pipelines, equality operators, and JSON converters.
using EricksonLopez.DomainPrimitives;
// String primitive with normalization and regex constraints
[StringPrimitive]
[Trim, UpperCase, Length(2, 2)]
public readonly partial record struct CountryIsoCode;
// Built-in shortcut for RFC 5321 compliant email addresses
[Email]
public readonly partial record struct EmailAddress;
// Usage:
CountryIsoCode code = CountryIsoCode.Create(" us "); // Value: "US"
EmailAddress email = EmailAddress.Create("user@example.com"); // Validated & normalized
2. Strongly-Typed Identifier
Eliminate identifier transposition bugs by declaring strongly-typed IDs backed by Guid, long, int, or string:
using EricksonLopez.DomainPrimitives;
[StrongId<Guid>]
public readonly partial record struct CustomerId;
[StrongId<long>]
public readonly partial record struct OrderId;
// Usage:
CustomerId customerId = CustomerId.Create(); // Generates new Guid
OrderId orderId = OrderId.Create(1001L); // Validated non-empty identifier
3. Type-Safe SmartEnum
Model exhaustive, polymorphic business states with $O(1)$ dictionary lookups and compile-time pattern matching:
using EricksonLopez.DomainPrimitives;
[SmartEnum<int>]
public readonly partial record struct OrderStatus
{
public static readonly OrderStatus Pending = new(1);
public static readonly OrderStatus Processing = new(2);
public static readonly OrderStatus Shipped = new(3);
public static readonly OrderStatus Delivered = new(4);
}
// Compile-time exhaustive pattern matching:
OrderStatus status = OrderStatus.Processing;
string description = status.Match(
whenPending: () => "Awaiting payment",
whenProcessing: () => "Fulfilling items in warehouse",
whenShipped: () => "In transit with carrier",
whenDelivered: () => "Successfully delivered");
4. Composite Value Object
Model multi-property domain concepts that enforce cross-property invariants via partial validation hooks:
using EricksonLopez.DomainPrimitives;
using EricksonLopez.DomainPrimitives.Validation;
[ValueObject]
public readonly partial record struct Address(string Street, string City, string ZipCode)
{
static partial void Validate(ref Address value, ref PrimitiveError error)
{
if (string.IsNullOrWhiteSpace(value.Street))
error = new PrimitiveError("Address.EmptyStreet", "Street cannot be empty.");
else if (string.IsNullOrWhiteSpace(value.City))
error = new PrimitiveError("Address.EmptyCity", "City cannot be empty.");
else if (string.IsNullOrWhiteSpace(value.ZipCode))
error = new PrimitiveError("Address.EmptyZipCode", "Zip code cannot be empty.");
}
}
5. Zero-Allocation Validation & Result Pipeline
Execute high-throughput validation without throwing exceptions or incurring heap allocations:
using EricksonLopez.DomainPrimitives.Validation;
// Stack-allocated TryCreate with out PrimitiveError (0 bytes allocated)
if (EmailAddress.TryCreate(userInput, out var email, out PrimitiveError error))
{
Console.WriteLine($"Valid email: {email.Value}");
}
else
{
Console.WriteLine($"Validation failed [{error.Code}]: {error.Message}");
}
// High-performance UTF-8 byte span parsing (Zero string allocations)
ReadOnlySpan<byte> utf8Buffer = "alice@example.com"u8;
if (EmailAddress.TryParse(utf8Buffer, null, out var parsedEmail))
{
Console.WriteLine($"Parsed from UTF-8 span: {parsedEmail}");
}
๐ก Core Use Cases
Use Case 1: Clean Architecture / CQRS Command Handler
Strongly-typed IDs and primitives guarantee invariant integrity before business logic executes in Application handlers:
using EricksonLopez.DomainPrimitives;
public readonly record struct RegisterCustomerCommand(
CustomerId Id,
EmailAddress Email,
AccountBalance InitialDeposit);
public sealed class RegisterCustomerHandler
{
private readonly ICustomerRepository _repository;
public RegisterCustomerHandler(ICustomerRepository repository) => _repository = repository;
public async Task<CustomerId> HandleAsync(RegisterCustomerCommand command, CancellationToken ct)
{
// Command parameters are guaranteed valid and non-null by the type system
var customer = new Customer(command.Id, command.Email, command.InitialDeposit);
await _repository.SaveAsync(customer, ct);
return customer.Id;
}
}
Use Case 2: Multi-Step Domain Validation Pipeline
Bridge TryCreate with EricksonLopez.Result for Railway-Oriented Programming without coupling domain types to external monad libraries:
using EricksonLopez.DomainPrimitives;
using EricksonLopez.Result;
public static class DomainResultBridge
{
public static Result<EmailAddress> ToResult(string raw) =>
EmailAddress.TryCreate(raw, out var email, out var error)
? Result<EmailAddress>.Success(email)
: Result<EmailAddress>.Failure(error.Code, error.Message);
}
// Chained functional flow:
Result<CustomerProfile> profileResult = DomainResultBridge.ToResult(rawEmail)
.Map(email => new CustomerProfile(email));
Use Case 3: Zero-Allocation Minimal API Route & Body Model Binding
Primitives automatically bind from route parameters, query strings, and JSON bodies via IParsable<T> and IUtf8SpanParsable<T>:
using EricksonLopez.DomainPrimitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
var app = WebApplication.Create();
// Automatically parsed from route via IParsable<CustomerId>
app.MapGet("/api/customers/{id}", (CustomerId id) => Results.Ok(new { Id = id.Value }));
// Automatically deserialized and validated from JSON body
app.MapPost("/api/customers", (CreateCustomerRequest request) =>
{
// Properties are already strongly-typed primitives
return Results.Created($"/api/customers/{request.Id}", request);
});
Use Case 4: EF Core Relational Persistence Mapping
Persist domain primitives into relational databases without contaminating domain models with persistence attributes:
using EricksonLopez.DomainPrimitives.EFCore.Generated;
using Microsoft.EntityFrameworkCore;
public sealed class ApplicationDbContext : DbContext
{
public DbSet<Customer> Customers => Set<Customer>();
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
// Automatically discovers and applies ValueConverters & column lengths for all primitives
configurationBuilder.ConfigureDomainPrimitives();
}
}
Use Case 5: High-Throughput Microservice Queries with Dapper
Execute high-performance database queries where Dapper maps database scalar columns directly into domain structs:
using Dapper;
using EricksonLopez.DomainPrimitives.Dapper.Generated;
// Startup registration (single call in Program.cs):
DapperDomainPrimitivesRegistration.RegisterAll();
// Queries materialize directly into domain types with zero reflection overhead:
var customer = await connection.QuerySingleAsync<Customer>(
"SELECT Id, Email, Balance FROM Customers WHERE Id = @Id",
new { Id = customerId });
Use Case 6: Live Compile-Time Roslyn Architectural Enforcement
Roslyn analyzers guard domain invariants at edit time inside the IDE, preventing common pitfalls before compilation:
// โ Roslyn Error DP0001: Domain primitive must be declared as 'partial'
[StringPrimitive]
public readonly record struct ApiKey;
// โ Roslyn Error DP0002: Domain primitive must be declared as 'readonly'
[StringPrimitive]
public partial record struct SessionToken;
// โ Roslyn Warning DP0007: Avoid using default constructor for domain primitive
EmailAddress email = default; // Analyzer flags uninitialized state
๐ Configuration & Integrations
ASP.NET Core Binding
Register model binding support in your ASP.NET Core application for MVC and Minimal APIs:
var builder = WebApplication.CreateBuilder(args);
// Register source-generated model binders
builder.Services.AddControllers()
.AddDomainPrimitivesModelBinding();
OpenAPI / Swagger Schema Generation
Enable OpenAPI schema filters to document primitives accurately as primitive types (e.g. string format email) rather than complex objects:
builder.Services.AddSwaggerGen(options =>
{
options.ConfigureDomainPrimitives();
});
Entity Framework Core Value Converters
Register all source-generated ValueConverter instances in one line using EF Core convention discovery:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.ConfigureDomainPrimitives();
}
Dapper Type Handlers
Register all source-generated Dapper SqlMapper.TypeHandler<T> instances at application startup:
using EricksonLopez.DomainPrimitives.Dapper.Generated;
// Program.cs
DapperDomainPrimitivesRegistration.RegisterAll();
System.Text.Json & NativeAOT
All primitives implement source-generated JSON converters that serialize directly to scalar JSON tokens (e.g. "user@example.com" instead of {"Value":"user@example.com"}). For NativeAOT, include your types in your JsonSerializerContext:
[JsonSerializable(typeof(CustomerDto))]
[JsonSerializable(typeof(EmailAddress))]
[JsonSerializable(typeof(CustomerId))]
public partial class AppJsonContext : JsonSerializerContext;
Newtonsoft.Json Migration Integration
For legacy applications using Newtonsoft.Json:
using EricksonLopez.DomainPrimitives.NewtonsoftJson;
using Newtonsoft.Json;
var settings = new JsonSerializerSettings();
settings.AddDomainPrimitives(); // Registers ContractResolver and converters
Roslyn Diagnostic Analyzers
The EricksonLopez.DomainPrimitives.Analyzers package provides 17 compile-time rules to enforce domain modeling invariants:
| Diagnostic ID | Severity | Category | Description | CodeFix |
|---|---|---|---|---|
| DP0001 | Error | Correctness | Domain primitive type must be declared as partial |
โ Available |
| DP0002 | Error | Correctness | Domain primitive type must be declared as readonly |
โ Available |
| DP0003 | Error | Correctness | Domain primitive type must be declared as record struct |
โ Available |
| DP0004 | Error | Correctness | Invalid regular expression pattern in [Regex] attribute |
โ Manual |
| DP0005 | Error | Correctness | Conflicting normalization attributes (e.g. [LowerCase] and [UpperCase]) |
โ Available |
| DP0006 | Error | Correctness | Invalid constraint bounds (Min value cannot be greater than Max) | โ Manual |
| DP0007 | Warning | Design | Avoid uninitialized domain primitive via default constructor |
โ Available |
| DP0008 | Error | Correctness | ValueObject properties must declare init accessors |
โ Available |
| DP0009 | Warning | Design | Domain primitive lacks validation rules | โ Manual |
| DP0010 | Warning | Performance | Raw string compared directly with domain primitive using == |
โ Available |
| DP0011 | Warning | Performance | string assigned directly from domain primitive without accessing .Value |
โ Available |
| DP0012 | Warning | Design | Public constructor bypasses source-generated domain primitive validation | โ Available |
| DP0013 | Info | Design | Possible duplicate domain primitive logic detected | โ Manual |
| DP0014 | Warning | ApiReview | API surface budget exceeded on domain primitive | โ Manual |
| DP0015 | Warning | ApiReview | Public member on domain primitive is missing XML documentation | โ Manual |
| DP0016 | Warning | ApiReview | Custom factory method must be named Create, TryCreate, or TryParse |
โ Manual |
| DP0017 | Error | Correctness | Invalid custom exception type in [DomainPrimitivesDefaults] |
โ Manual |
๐งช Testing & Quality
Fluent Assertions API
The EricksonLopez.DomainPrimitives.Testing package provides declarative assertions for xUnit, NUnit, and MSTest:
using AwesomeAssertions;
using EricksonLopez.DomainPrimitives.Testing;
using Xunit;
public class DomainPrimitiveTests
{
[Fact]
public void EmailAddress_ValidInput_ShouldSucceed()
{
var email = DomainPrimitiveAssertionsExtensions
.ShouldSucceedCreation<EmailAddress, string>("user@example.com");
email.Should().HavePrimitiveValue<EmailAddress, string>("user@example.com");
}
[Fact]
public void EmailAddress_InvalidInput_ShouldFailWithErrorCode()
{
DomainPrimitiveAssertionsExtensions
.ShouldFailCreationWith<EmailAddress, string>("invalid-email", "FORMAT");
}
}
Realistic Test Data Generation
Generate curated valid and invalid test datasets with DomainPrimitiveFakeFactory:
using EricksonLopez.DomainPrimitives.Testing;
// Valid and invalid sample datasets for parameterized tests
string[] validEmails = DomainPrimitiveFakeFactory.Strings.ValidEmails;
string[] invalidEmails = DomainPrimitiveFakeFactory.Strings.InvalidEmails;
decimal[] validMoney = DomainPrimitiveFakeFactory.Numerics.ValidMoneyAmounts;
int[] validAges = DomainPrimitiveFakeFactory.Numerics.ValidAges;
// Grouped test scenarios
var scenarios = DomainPrimitiveScenarios.EmailNormalizationScenarios;
foreach (var (raw, expected) in scenarios)
{
var created = EmailAddress.Create(raw);
Assert.Equal(expected, created.Value);
}
Mutation Testing & Quality Gates
Every build is verified against a strict quality gate pipeline enforcing 100% mutant eradication:
| Package | Mutants Total | Mutants Killed | Mutation Score | Status |
|---|---|---|---|---|
EricksonLopez.DomainPrimitives |
312 | 312 | 100.0% | โ PASSED |
EricksonLopez.DomainPrimitives.Abstractions |
48 | 48 | 100.0% | โ PASSED |
EricksonLopez.DomainPrimitives.AspNetCore |
64 | 64 | 100.0% | โ PASSED |
EricksonLopez.DomainPrimitives.EFCore |
52 | 52 | 100.0% | โ PASSED |
EricksonLopez.DomainPrimitives.Dapper |
50 | 50 | 100.0% | โ PASSED |
EricksonLopez.DomainPrimitives.Testing |
36 | 36 | 100.0% | โ PASSED |
| Total Aggregate Quality Score | 562 | 562 | 100.0% | โ VERIFIED |
โก Performance Benchmarks
Environment: AMD Ryzen 7 9800X3D 4.70GHz (8 cores, 16 threads), .NET 10.0.10, X64 RyuJIT x86-64-v4, BenchmarkDotNet v0.15.8
Primary Operations Benchmark
| Method | Mean | Ratio | Allocated | Zero-Alloc? |
|---|---|---|---|---|
Raw Guid (baseline โ no wrapper) |
0.00 ns | 1.00 | 0 B | โ |
[StrongId<Guid>] Creation (CustomerId.Create(guid)) |
0.00 ns | 1.00 | 0 B | โ |
[StrongId<Guid>] TryParse (CustomerId.TryParse(...)) |
12.63 ns | 1.00 | 0 B | โ |
[Email] Creation (EmailAddress.Create(...)) |
49.53 ns | - | 48 B* | โ ๏ธ (NFC Norm) |
[SmartEnum] Lookup (OrderStatus.FromValue(2)) |
2.14 ns | - | 0 B | โ |
[NumericPrimitive] Add (Money.Add(a, b)) |
0.19 ns | - | 0 B | โ |
*Note: String normalization requires 1 allocation for string.Normalize(NormalizationForm.FormC) per Unicode security standards (SEC-004 / ADR-027).
BCL Span & UTF-8 Zero-Allocation Paths
| Benchmark | Interface Tested | Mean | Allocated | Zero-Alloc? |
|---|---|---|---|---|
DomainPrimitives_TryParse |
IParsable<T> |
12.63 ns | 0 B | โ |
DomainPrimitives_SpanParse |
ISpanParsable<T> |
11.84 ns | 0 B | โ |
DomainPrimitives_Utf8SpanParse |
IUtf8SpanParsable<T> |
13.10 ns | 0 B | โ |
DomainPrimitives_SpanFormat |
ISpanFormattable |
4.82 ns | 0 B | โ |
DomainPrimitives_Utf8SpanFormat |
IUtf8SpanFormattable |
5.10 ns | 0 B | โ |
Integration Overhead (EF Core & Dapper)
| Benchmark | Integration Layer | Mean | Allocated |
|---|---|---|---|
Dapper_TypeHandler_SetValue |
Dapper Parameter Binding | 0.21 ns | 0 B |
Dapper_TypeHandler_Parse |
Dapper Reader Materialization | 0.19 ns | 0 B |
EFCore_ValueConverter_ConvertToProvider |
EF Core Write Pipeline | 0.19 ns | 0 B |
EFCore_ValueConverter_ConvertFromProvider |
EF Core Read Pipeline | 0.19 ns | 0 B |
๐ Compatibility & Technical Matrix
Target Frameworks & NativeAOT
| Package | .NET 8.0 LTS | .NET 9.0 STS | .NET 10.0 | NativeAOT | Trimming Safe |
|---|---|---|---|---|---|
EricksonLopez.DomainPrimitives |
โ Supported | โ Supported | โ Supported | โ Supported | โ 100% Trim-Safe |
EricksonLopez.DomainPrimitives.Abstractions |
โ Supported | โ Supported | โ Supported | โ Supported | โ 100% Trim-Safe |
EricksonLopez.DomainPrimitives.AspNetCore |
โ Supported | โ Supported | โ Supported | โ Supported | โ 100% Trim-Safe |
EricksonLopez.DomainPrimitives.EFCore |
โ Supported | โ Supported | โ Supported | โ Supported | โ 100% Trim-Safe |
EricksonLopez.DomainPrimitives.Dapper |
โ Supported | โ Supported | โ Supported | โ Supported | โ 100% Trim-Safe |
EricksonLopez.DomainPrimitives.OpenApi |
โ Supported | โ Supported | โ Supported | โ Supported | โ 100% Trim-Safe |
EricksonLopez.DomainPrimitives.Testing |
โ Supported | โ Supported | โ Supported | โ Supported | โ 100% Trim-Safe |
EricksonLopez.DomainPrimitives.NewtonsoftJson |
โ Supported | โ Supported | โ Supported | โ Supported | โ 100% Trim-Safe |
Primitive Category Taxonomy & Generated Interfaces
| Category | Decorator Attribute | Underlying Type | Key Generated Interfaces & Capabilities |
|---|---|---|---|
| Strong ID | [StrongId<T>] |
Guid, long, int, string |
IDomainPrimitive<TSelf, TValue>, IStrongId<TSelf, TValue>, IParsable<T>, ISpanParsable<T> |
| String Primitive | [StringPrimitive], [Email], [Phone], ... |
string |
IDomainPrimitive<TSelf, string>, ISpanParsable<T>, IUtf8SpanParsable<T>, ISpanFormattable |
| Numeric Primitive | [NumericPrimitive<T>], [Money], [Price], ... |
decimal, double, int, long |
IDomainPrimitive<TSelf, T>, IComparable<T>, arithmetic operators (+, -, *, /) |
| Date Primitive | [DatePrimitive] |
DateOnly, DateTime, DateTimeOffset |
IDomainPrimitive<TSelf, TDate>, IComparable<T>, past/future invariant guards |
| SmartEnum | [SmartEnum<T>] |
int, string |
IDomainPrimitive<TSelf, T>, exhaustive Match<T>, Map<T>, $O(1)$ dictionary lookups |
| Value Object | [ValueObject] |
Composite | IDomainPrimitive<TSelf>, IParsable<T>, ISpanParsable<T>, structural equality |
๐๏ธ Architecture & Design Principles
End-to-End Architectural Pipeline
flowchart TD
Client(["HTTP Client / Caller"])
subgraph Presentation ["Presentation & Serialization Layer"]
Json["System.Text.Json Converter\n(Auto-converts via TryCreate)"]
OpenApi["OpenApi Schema Filter\n(Generates Swagger specs)"]
AspNet["ASP.NET Core Model Binder\n(Route & Query Binding)"]
end
subgraph Domain ["Domain Layer (Zero Heap Allocation)"]
Prim["Domain Primitive\n(readonly record struct)"]
Pipeline["Validation Pipeline:\n1. Unicode Normalization (NFC)\n2. Built-in Range / Regex Rules\n3. Custom Partial Validator"]
end
subgraph Persistence ["Persistence Layer"]
EF["EF Core ValueConverter\n(ConfigureDomainPrimitives)"]
Dapper["Dapper TypeHandler\n(RegisterAll)"]
DB[("Database")]
end
Client -->|"JSON Request Body"| Json
Client -->|"Route / Query Parameter"| AspNet
Client -->|"API Documentation"| OpenApi
Json --> Prim
AspNet --> Prim
Prim --> Pipeline
Pipeline -->|"Valid struct"| EF
Pipeline -->|"Valid struct"| Dapper
EF --> DB
Dapper --> DB
Primitive Lifecycle & State Machine
stateDiagram-v2
[*] --> RawInput: Caller invokes Create() or TryCreate()
RawInput --> Normalizing: Has [Trim] / [LowerCase] / [UpperCase]
RawInput --> Validating: No normalization
Normalizing --> Validating: Normalized value
Validating --> InvalidState: Built-in validation fails (LENGTH, REGEX, RANGE)
Validating --> CustomValidating: Built-in validation passes
CustomValidating --> InvalidState: Custom partial Validate() fails
CustomValidating --> ValidState: All invariants satisfied
InvalidState --> ThrowsException: Create() path -> Throws DomainPrimitiveValidationException
InvalidState --> ReturnsFalse: TryCreate() path -> Returns false + PrimitiveError
ThrowsException --> [*]
ReturnsFalse --> [*]
ValidState --> Instantiated: readonly record struct allocated on Stack
Instantiated --> Serialized: System.Text.Json / Newtonsoft.Json
Instantiated --> Persisted: EF Core / Dapper
Instantiated --> [*]: Zero GC overhead (Stack released)
๐ก๏ธ Best Practices & Anti-Patterns
| Scenario | โ Avoid | โ Recommended |
|---|---|---|
| Control Flow | Throwing exceptions for business validation | Using TryCreate with stack-allocated PrimitiveError |
| Memory Allocation | Declaring domain primitives as class reference types |
Using readonly partial record struct for zero GC allocations |
| Struct Initialization | Using default(Primitive) or parameterless new() |
Instantiating via source-generated Create() or TryCreate() |
| String Comparison | Comparing raw string directly with a primitive (str == email) |
Parsing the raw string into the primitive or using email.Value |
| Value Object Mutation | Modifying property values directly | Creating a new instance with updated properties (immutable replacement) |
| Persistence Mapping | Contaminating domain models with EF Core annotations | Using zero-contamination ConfigureDomainPrimitives() in DbContext |
| Validation Architecture | Running asynchronous I/O or DB queries inside primitive validators | Keeping domain primitive validators 100% synchronous and deterministic |
โ ๏ธ Troubleshooting & Common Pitfalls
Always use generated factory methods (Create, TryCreate, TryParse) rather than default structs to ensure validation invariants are enforced.
1. Uninitialized Struct via default Constructor
- Symptom: A domain primitive struct contains a null or uninitialized backing value, bypassing domain invariants.
- Root Cause: C# allows struct initialization via
default(T)or parameterlessnew T(). - Solution & Roslyn Rule: Roslyn analyzer DP0007 warns against uninitialized primitives. Always use
Primitive.Create(...)orPrimitive.TryCreate(...).
2. Bypassing Validation via Public Constructors
- Symptom: Developers instantiate primitives with raw data that violates regex, range, or length rules.
- Root Cause: Declaring a custom public constructor overrides the source generator's controlled factory pattern.
- Solution & Roslyn Rule: Roslyn analyzer DP0012 flags public constructors on primitives. Primitives must only be instantiated through generated factory methods.
3. Direct String Comparisons Bypassing Type Safety
- Symptom: Comparing a domain primitive directly against a raw
string(email == "admin@example.com") fails to normalize the input. - Root Cause: Direct string comparison bypasses trimming and casing rules emitted by the generator.
- Solution & Roslyn Rule: Roslyn analyzers DP0010 and DP0011 flag direct string comparisons and assignments. Parse the raw string into the primitive first.
4. Mutating Value Objects Instead of Replacement
- Symptom: Compile errors or invariant drift when attempting to mutate properties on a
[ValueObject]. - Root Cause: Value objects are immutable by design.
- Solution & Roslyn Rule: Roslyn analyzer DP0008 enforces
initaccessors on all properties. Create new instances when updating values.
5. Missing partial or readonly Modifiers
- Symptom: Compilation error stating the source generator cannot augment the type definition.
- Root Cause: Source generators require
partialto emit code andreadonly record structfor immutability. - Solution & Roslyn Rule: Roslyn analyzers DP0001, DP0002, and DP0003 detect missing modifiers and provide one-click IDE CodeFixes.
๐ Part of the EricksonLopez Ecosystem
- โก EricksonLopez.Result โ High-Performance Struct-Based Result Pattern & Railway-Oriented Programming.
- ๐ฌ EricksonLopez.Events โ Enterprise Event-Driven Architecture & Distributed Messaging Substrate.
- ๐งฑ EricksonLopez.SharedKernel โ Sovereign Tier-0 DDD Foundational Substrate & Specifications.
- ๐ EricksonLopez.ValueObjects โ Pre-Built Enterprise Value Objects & Multi-Country Fiscal Satellites.
- ๐ EricksonLopez.Specification โ Composable AOT-First Specification Pattern for .NET.
๐ค Contributing
Contributions are welcome! Follow these steps to set up your local development environment:
Prerequisites
- .NET 8.0 SDK, .NET 9.0 SDK, or .NET 10.0 SDK
- Git & modern C# IDE (Visual Studio 2022 v17.12+, JetBrains Rider 2024+, or VS Code with C# Dev Kit)
Development Workflow
- Clone the repository:
git clone https://github.com/ericksonlopezf/dotnet-domain-primitives.git cd dotnet-domain-primitives - Build the solution:
dotnet build EricksonLopez.DomainPrimitives.slnx - Execute unit & integration tests:
dotnet test EricksonLopez.DomainPrimitives.slnx - Run mutation testing quality gates:
dotnet stryker
Please review our Contributing Guidelines and Code of Conduct before submitting pull requests.
๐ License
Distributed under the MIT License.
Copyright ยฉ 2026 Erickson Lopez. All rights reserved.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (18)
Showing the top 5 NuGet packages that depend on EricksonLopez.DomainPrimitives.Abstractions:
| Package | Downloads |
|---|---|
|
EricksonLopez.SharedKernel
A minimal Shared Kernel for DDD-based .NET applications. Provides zero-dependency, AOT-compatible domain primitives: Entity<TId>, AggregateRoot<TId>, DomainEvent, ValueObject, and IStrongId. Designed for Clean Architecture and CQRS. NativeAOT and Trimming compatible on all supported TFMs. |
|
|
EricksonLopez.DomainPrimitives
The definitive framework for Domain Primitives in .NET. Source-generated, NativeAOT-ready, zero-reflection strong IDs, value objects, validated types, and smart enums. This meta-package includes Abstractions, Source Generators, and Roslyn Analyzers. |
|
|
EricksonLopez.DomainPrimitives.EFCore
EF Core integration for EricksonLopez.DomainPrimitives. Automatically generates NativeAOT-ready ValueConverters for all domain primitives without reflection. |
|
|
EricksonLopez.DomainPrimitives.OpenApi
OpenAPI/Swashbuckle integration for EricksonLopez.DomainPrimitives. Generates precise schema specifications for domain primitives. |
|
|
EricksonLopez.DomainPrimitives.Dapper
Dapper integration for EricksonLopez.DomainPrimitives. Automatically generates NativeAOT-ready SqlMapper.TypeHandler implementations for all domain primitives without reflection. |
GitHub repositories
This package is not used by any popular GitHub repositories.