Optima.Net.DomainModel 1.0.4

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

Optima.Net.DomainModel

Introduction

Welcome to Optima.Net.DomainModel. This package defines the structural foundation of your domain. It ensures that your software model only allows valid and legal domain states.

Optima.Net.DomainModel defines what exists in the domain (entities, value objects, aggregates) and what must always be true (invariants). It does not manage workflows, persistence, or event dispatching.

This package depends on Optima.Net.Events, which is included automatically when you install this package. You can read more about it here:
https://www.nuget.org/packages/Optima.Net.Events

TL;DR (for people who do not want to read the README)

If you prefer to see DomainModel in action instead of reading about it,
you can clone and run the test harness here:

https://github.com/snamretsuek/Optima.Net.TestHarnesses

It contains console applications that:

  • wire everything together correctly,
  • show real execution flows,
  • and let you step through the code in a debugger.

If you still have questions after running the harness, then yes, you'll need to come back and read this README. 😃

NB: The test harnesses are for demonstration purposes only. The test harness project contains test harnesses for multiple Optima.Net packages. You need to run the one that applies to Optima.Net.DomainModel.


Purpose

Every business has rules. For example:

  • A customer must have a name.
  • An order cannot have a negative total.
  • A product must have a price.

Optima.Net.DomainModel makes those rules enforceable by code. It ensures that your domain objects cannot exist in an illegal state.

This library defines the shape and rules of your domain. It does not handle application logic or persistence.


Key Concepts

Entity

An Entity is something that has a unique identity. The identity is immutable and defines equality.

using System;
using Optima.Net.DomainModel.Entities;

public sealed class Customer : Entity<Guid>
{
    public string Name { get; }

    public Customer(Guid id, string name)
        : base(id)
    {
        Name = name;
    }
}

Usage example:

var c1 = new Customer(Guid.NewGuid(), "Alice");
var c2 = new Customer(c1.Id, "Alice Clone");
Console.WriteLine(c1 == c2); // True - same identity

Value Object

A Value Object is defined by its contents, not by identity.

public record Money(decimal Amount, string Currency);

Usage example:

var a = new Money(100, "USD");
var b = new Money(100, "USD");
Console.WriteLine(a == b); // True

Invariant

An Invariant is a rule that must always be true. If it is violated, an exception is thrown immediately.

using Optima.Net.DomainModel.Invariants;

Invariant.MustBeTrue(total > 0, "Order total must be positive.");

If this rule fails, an InvariantViolationException will be thrown, stopping the system from continuing in an invalid state.

Enforcing Invariants (Optional Integration with Optima.Net.Domain)

By design, Optima.Net.DomainModel defines pure, immutable domain entities and value objects. It does not perform invariant checking, validation, or policy enforcement within the model itself. This is deliberate � invariants belong to the domain layer, not the model.

However, developers who wish to enforce aggregate-level or entity-level invariants can optionally integrate with the Optima.Net.Domain package.

Optima.Net.Domain provides a declarative policy and specification framework that allows you to express and evaluate invariants without introducing procedural logic inside your models.

Example Integration

public sealed class Customer : Entity<CustomerId>
{
    public string Email { get; }
    public DateOnly DateOfBirth { get; }

    public Customer(CustomerId id, string email, DateOnly dob)
        : base(id)
    {
        Email = email;
        DateOfBirth = dob;

        // Optionally enforce invariants using Optima.Net.Domain
        var policy = new CustomerInvariantsPolicy();
        var justification = new CustomerInvariantsJustification();

        var evaluator = new PolicyDiagnosticEvaluator(new SpecificationEvaluator());
        var result = evaluator.Evaluate(policy, justification, this);

        if (!result.Fulfilled)
        {
            throw new DomainModelInvariantViolationException(result);
        }
    }
}

In this example:

  • CustomerInvariantsPolicy defines what invariants must hold true (for example, age limits, email validity).
  • CustomerInvariantsJustification lists the underlying specifications for those invariants.
  • PolicyDiagnosticEvaluator (from Optima.Net.Domain) evaluates them deterministically.
  • If any invariant fails, the model constructor rejects invalid state.

This pattern provides explicit, explainable, and testable invariants without embedding business logic directly in your domain entities.

Key Points

  • Invariants remain declarative and auditable.
  • Evaluation logic lives outside the model, preserving purity.
  • Integrating with Optima.Net.Domain is optional � the model package itself has no dependency on it.

Domain Facts (GenericEvent and DynamicPayload)

Domain facts describe what has happened in your system. They are represented by GenericEvent and DynamicPayload from Optima.Net.Events.

  • GenericEvent represents an immutable domain fact.
  • DynamicPayload holds the data for that fact.

Example:

using Optima.Net.Events.Payloads;
using Optima.Net.Events.Models;

var payload = new DynamicPayload("OrderCreated");
payload.Add("OrderId", Guid.NewGuid());
payload.Add("TotalAmount", 250.00m);

var evt = new GenericEvent<DynamicPayload>
{
    EventId = Guid.NewGuid(),
    EventType = payload.PayloadName,
    Source = "OrderAggregate",
    SchemaVersion = "V1.0.0",
    Timestamp = DateTime.UtcNow,
    Payload = payload
};

This represents the domain fact: "OrderCreated occurred with these values."

Access the payload fields like this:

var orderId = evt.Payload["OrderId"];
var total = evt.Payload["TotalAmount"];

For a deeper explanation of these types, see the readme for Optima.Net.Events:
https://www.nuget.org/packages/Optima.Net.Events


Aggregate Root Example

An Aggregate Root defines a consistency boundary. It enforces invariants and emits domain events. It does not handle or dispatch them.

using System;
using Optima.Net.DomainModel.Entities;
using Optima.Net.DomainModel.Invariants;
using Optima.Net.Events;
using Optima.Net.Events.Payloads;

public sealed class Order : AggregateRoot<Guid>
{
    public string OrderNumber { get; }
    public decimal TotalAmount { get; private set; }

    private Order(Guid id, string orderNumber, decimal totalAmount)
        : base(id)
    {
        Invariant.MustBeTrue(!string.IsNullOrWhiteSpace(orderNumber), "Order number must not be empty.");
        Invariant.MustBeTrue(totalAmount > 0, "Order total must be greater than zero.");

        OrderNumber = orderNumber;
        TotalAmount = totalAmount;

        EmitOrderCreatedEvent();
    }

    public static Order Create(Guid id, string orderNumber, decimal totalAmount)
    {
        return new Order(id, orderNumber, totalAmount);
    }

    public void UpdateTotal(decimal newAmount)
    {
        Invariant.MustBeTrue(newAmount > 0, "Order total must be greater than zero.");
        TotalAmount = newAmount;
        EmitOrderUpdatedEvent();
    }

    private void EmitOrderCreatedEvent()
    {
        var payload = new DynamicPayload("OrderCreated");
        payload.Add("OrderId", Id);
        payload.Add("OrderNumber", OrderNumber);
        payload.Add("TotalAmount", TotalAmount);
        payload.Add("CreatedAtUtc", DateTime.UtcNow);

        var evt = new GenericEvent<DynamicPayload>
        {
            EventId = Guid.NewGuid(),
            EventType = payload.PayloadName,
            Source = nameof(Order),
            SchemaVersion = "V1.0.0",
            Timestamp = DateTime.UtcNow,
            Payload = payload
        };

        EmitDomainFact(evt);
    }

    private void EmitOrderUpdatedEvent()
    {
        var payload = new DynamicPayload("OrderUpdated");
        payload.Add("OrderId", Id);
        payload.Add("OrderNumber", OrderNumber);
        payload.Add("TotalAmount", TotalAmount);
        payload.Add("UpdatedAtUtc", DateTime.UtcNow);

        var evt = new GenericEvent<DynamicPayload>
        {
            EventId = Guid.NewGuid(),
            EventType = payload.PayloadName,
            Source = nameof(Order),
            SchemaVersion = "V1.0.0",
            Timestamp = DateTime.UtcNow,
            Payload = payload
        };

        EmitDomainFact(evt);
    }
}

Usage example:

var order = Order.Create(Guid.NewGuid(), "ORD-1001", 250.00m);
order.UpdateTotal(300.00m);

foreach (var fact in order.DomainFacts)
{
    Console.WriteLine($"{fact.EventType} emitted at {fact.Timestamp:u}");
}

Common Mistakes and Why They Are Wrong

Mistake Why It Is Wrong
Returning diagnostics from invariants Invariants are binary: valid or illegal.
Letting aggregates handle or dispatch events Aggregates may emit domain events, but they must never dispatch or react to them.
Including persistence logic DomainModel should not depend on infrastructure or data storage.
Using Optional or Result for invariants Invalid states must throw exceptions immediately.

Example Output

OrderCreated emitted at 2026-01-04 18:12:03Z
OrderUpdated emitted at 2026-01-04 18:12:05Z

Summary

Optima.Net.DomainModel defines the legal structure and boundaries of your domain.

It:

  • Prevents illegal states.
  • Enforces invariants.
  • Emits domain events.
  • Includes the Optima.Net.Events dependency automatically.
  • Works immediately after installation without any setup.

Each Optima package is designed to work independently, but you can explore other packages in the Optima.Net ecosystem if you wish to expand your domain-driven architecture.


License

This project is licensed under the MIT License.


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 was computed.  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 was computed.  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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.4 119 1/8/2026
1.0.3 118 1/8/2026
1.0.2 116 1/8/2026
1.0.1 113 1/4/2026
1.0.0 112 1/4/2026

RELEASENOTES.md