Optima.Net.Domain 1.0.2

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

Optima.Net.Domain

Optima.Net.Domain is a lightweight, framework-agnostic toolkit that provides explicit building blocks for writing clear, maintainable, and auditable Domain-Driven Design (DDD) code.

This library is intentionally focused. It does not try to be a framework. Instead, it provides a small set of primitives that help you express facts, decisions, and outcomes in a way that scales from simple business rules to finance-grade decision systems.

This document is both:

  • a comprehensive reference, and
  • a step-by-step tutorial for engineers new to DDD-style modelling.

If you read it top-to-bottom, you should be able to confidently apply Specifications, Policies, and Anti-Corruption Layers in real systems.

If you like what I did here or it inspired you to learn something new, please consider buying me a coffee: https://buymeacoffee.com/snamretsuek The support would be greatly appreciated!


Table of Contents

  1. Motivation and Mental Model
  2. Core Concepts Overview
  3. Specifications (Sync))
  4. Composite Specifications
  5. Policies (Sync)
  6. Composite Policies
  7. Async Specifications and Policies
  8. Diagnostics}
  9. Diagnostic Helpers
  10. Decisions / Outcomes)
  11. Identity and Naming
  12. End-to-End Examples
  13. Anti-Corruption Layers (ACL)
  14. ACL – Real-World Code Examples
  15. ACL Anti-Patterns
  16. Architectural Enforcement & Tests
  17. Application Layer Primitives
  18. UseCase Example
  19. Saga Example
  20. Design Philosophy and Boundaries

1. Motivation and Mental Model

In many systems, business rules end up:

  • buried in if statements,
  • duplicated across services,
  • tightly coupled to infrastructure,
  • or impossible to explain after the fact.

Optima.Net.Domain exists to fix this by making rules and decisions explicit.

The Mental Model

Concept Purpose Question Answered
Specification Fact Is this true?
Policy Decision May we proceed?
Diagnostics Explanation Why did this fail?
Decision Outcome What happened?

This separation is the foundation of the library.


2. Core Concepts Overview

Specifications

  • Express facts
  • Are small and composable
  • Contain no side effects
  • Can be reused across policies

Policies

  • Express business intent
  • Are fulfilled only if all required facts hold
  • Often composed from multiple specifications
  • Can be composed from other policies

Diagnostics

  • Observe evaluation
  • Never change behaviour
  • Produce immutable result trees

Decisions

  • Capture evaluation outcomes
  • Are immutable and serializable
  • Suitable for logging and auditing

3. Specifications (Sync)

A specification answers a single factual question.

Example Domain: Orders

public sealed class OrderLine
{
    public string Product { get; }
    public decimal Price { get; }
    public bool CanShipByAir { get; }

    public OrderLine(string product, decimal price, bool canShipByAir)
    {
        Product = product;
        Price = price;
        CanShipByAir = canShipByAir;
    }
}
public sealed class Order
{
    private readonly List<OrderLine> _lines = new();

    public IReadOnlyCollection<OrderLine> Lines => _lines;
    public decimal Total => _lines.Sum(l => l.Price);

    public void AddLine(OrderLine line) => _lines.Add(line);
}

Individual Specifications

public sealed class OrderHasLinesSpec : Specification<Order>
{
    public override bool IsSatisfiedBy(Order order)
        => order.Lines.Any();
}
public sealed class OrderCanShipByAirSpec : Specification<Order>
{
    public override bool IsSatisfiedBy(Order order)
        => order.Lines.All(l => l.CanShipByAir);
}
public sealed class OrderTotalExceedsSpec : Specification<Order>
{
    private readonly decimal _threshold;

    public OrderTotalExceedsSpec(decimal threshold)
        => _threshold = threshold;

    public override bool IsSatisfiedBy(Order order)
        => order.Total > _threshold;
}

Each specification:

  • Answers one question
  • Has no knowledge of business intent
  • Is independently testable

4. Composite Specifications

Specifications can be composed to express more complex facts.

var shippableOrderSpec =
    new OrderHasLinesSpec()
        .And(new OrderCanShipByAirSpec());
var discountEligibleSpec =
    new OrderHasLinesSpec()
        .And(new OrderTotalExceedsSpec(500m));

Composite specifications still express facts. They do not represent decisions.


5. Policies (Sync)

Policies express decisions and are typically built from specifications.

Order Fulfillment Policy

public sealed class OrderFulfillmentPolicy : Policy<Order>
{
    private static readonly ISpecification<Order> _specification =
        new OrderHasLinesSpec()
            .And(new OrderCanShipByAirSpec());

    public override bool IsSatisfiedBy(Order order)
        => _specification.IsSatisfiedBy(order);
}

This policy answers:

May this order be fulfilled?


Discount Eligibility Policy

public sealed class OrderDiscountPolicy : Policy<Order>
{
    private static readonly ISpecification<Order> _specification =
        new OrderHasLinesSpec()
            .And(new OrderTotalExceedsSpec(500m));

    public override bool IsSatisfiedBy(Order order)
        => _specification.IsSatisfiedBy(order);
}

6. Composite Policies

In many domains, multiple policies must be fulfilled before progression is allowed.

Finance Example: Credit Application

public sealed class CreditApplication
{
    public decimal Amount { get; }
    public bool HasPriorDefaults { get; }
    public bool PassedAmlChecks { get; }

    public CreditApplication(
        decimal amount,
        bool hasPriorDefaults,
        bool passedAmlChecks)
    {
        Amount = amount;
        HasPriorDefaults = hasPriorDefaults;
        PassedAmlChecks = passedAmlChecks;
    }
}

Specifications

public sealed class CreditWithinLimitSpec : Specification<CreditApplication>
{
    public override bool IsSatisfiedBy(CreditApplication app)
        => app.Amount <= 100_000m;
}
public sealed class NoPriorDefaultsSpec : Specification<CreditApplication>
{
    public override bool IsSatisfiedBy(CreditApplication app)
        => !app.HasPriorDefaults;
}
public sealed class PassedAmlChecksSpec : Specification<CreditApplication>
{
    public override bool IsSatisfiedBy(CreditApplication app)
        => app.PassedAmlChecks;
}

Policies Built from Specifications

public sealed class CreditApprovalPolicy : Policy<CreditApplication>
{
    private static readonly ISpecification<CreditApplication> _specification =
        new CreditWithinLimitSpec()
            .And(new NoPriorDefaultsSpec());

    public override bool IsSatisfiedBy(CreditApplication app)
        => _specification.IsSatisfiedBy(app);
}
public sealed class AmlClearancePolicy : Policy<CreditApplication>
{
    private static readonly ISpecification<CreditApplication> _specification =
        new PassedAmlChecksSpec();

    public override bool IsSatisfiedBy(CreditApplication app)
        => _specification.IsSatisfiedBy(app);
}

Composite Policy Gate

public sealed class CreditIssuancePolicy
    : CompositePolicy<CreditApplication>
{
    protected override IReadOnlyCollection<IPolicy<CreditApplication>> Policies =>
        new IPolicy<CreditApplication>[]
        {
            new CreditApprovalPolicy(),
            new AmlClearancePolicy()
        };
}

This expresses a progression gate:

Credit may only be issued if all required policies are fulfilled.


7. Async Specifications and Policies

Async variants exist for:

  • Database checks
  • External services
  • Fraud / AML systems
public sealed class AsyncAmlSpec : AsyncSpecification<CreditApplication>
{
    public override Task<bool> IsSatisfiedByAsync(
        CreditApplication app,
        CancellationToken ct = default)
        => Task.FromResult(app.PassedAmlChecks);
}

Async policies mirror sync policies exactly. Sync and async are never mixed.


8. Diagnostics

Diagnostics explain why something passed or failed.

Specification Diagnostics

var evaluator = new DiagnosticSpecificationEvaluator();
var diagnostics = evaluator.Evaluate(specification, order);

Policy Diagnostics

var evaluator = new PolicyDiagnosticEvaluator();
var diagnostics = evaluator.Evaluate(policy, application);

Diagnostics produce immutable trees describing evaluation.


9. Diagnostic Helpers

Helpers make diagnostics usable without changing behaviour.

var failedSpecs = diagnostics.FailedSpecificationTypes();
var failedPolicies = diagnostics.FailedPolicyTypes();
var leafFailures = diagnostics.FailedLeaves();

10. Decisions / Outcomes

Decisions capture evaluation outcomes.

var decision = policy.Decide(application);
public sealed record PolicyDecision(
    string PolicyType,
    bool Fulfilled);

Decisions are:

  • Immutable
  • Serializable
  • Suitable for audit trails

11. Identity and Naming

Diagnostics use structural identity by default:

PolicyType = policy.GetType().Name

Optional interfaces allow human-readable naming without affecting diagnostics.


12. End-to-End Example

if (!creditIssuancePolicy.IsSatisfiedBy(application))
{
    var diagnostics =
        policyDiagnosticEvaluator.Evaluate(
            creditIssuancePolicy,
            application);

    var failedPolicies = diagnostics.FailedPolicyTypes();
}

This flow is:

  • Explicit
  • Deterministic
  • Auditable
  • Framework-free

13. Anti-Corruption Layers (ACL)

Purpose

Anti-Corruption Layers exist to protect the domain from foreign models, foreign semantics, and foreign change.

An ACL is not a mapper. It is not infrastructure. It is not a shared contract.

It is a semantic firewall.

If an external system changes tomorrow, the domain must not notice.


Intent

An ACL:

  • Translates foreign meaning into domain meaning
  • Enforces domain invariants before the domain is touched
  • Rejects unsupported or ambiguous concepts explicitly
  • Absorbs versioning and contract drift
  • Prevents domain pollution

Explicit Non-Goals

ACLs do not:

  • Call HTTP APIs
  • Deserialize JSON
  • Publish messages
  • Retry operations
  • Log infrastructure errors
  • Share DTOs or enums

Those concerns belong elsewhere.


Inbound vs Outbound ACLs

Inbound ACL (Domain Protection)
  • Direction: External → Domain
  • Strict and defensive
  • Explicitly rejects invalid input
  • Returns semantic translation outcomes
public interface IInboundTranslator<in TForeign, TDomain>
{
    TranslationResult<TDomain> Translate(TForeign input);
}

Outbound ACL (Consumer Protection)
  • Direction: Domain → External
  • Shapes domain data for consumers
  • Failure is exceptional
public interface IOutboundTranslator<TDomain, TForeign>
{
    TForeign Translate(TDomain input);
}

Inbound and outbound ACLs solve different problems and must never be merged.


14. ACL – Real-World Code Examples

This section demonstrates production-grade ACL usage. These examples are intentionally non-trivial.


Example Scenario

You integrate with a third-party Payments API that:

  • Has breaking versions
  • Uses terminology incompatible with your domain
  • Allows invalid state combinations

Your domain models Payments as a strict aggregate.


External Models (Foreign, Untrusted)

public sealed class ExternalPaymentV1
{
    public string PaymentId { get; init; } = default!;
    public string Status { get; init; } = default!;
    public decimal Amount { get; init; }
}
public sealed class ExternalPaymentV2
{
    public string Id { get; init; } = default!;
    public string State { get; init; } = default!;
    public MoneyDto Money { get; init; } = default!;
}

These models are never visible to the domain.


Domain Model (Protected)

public sealed class Payment
{
    public PaymentId Id { get; }
    public PaymentStatus Status { get; }
    public Money Amount { get; }

    private Payment(PaymentId id, PaymentStatus status, Money amount)
    {
        Id = id;
        Status = status;
        Amount = amount;
    }

    public static Payment Create(
        PaymentId id,
        PaymentStatus status,
        Money amount)
    {
        if (amount.Value <= 0)
            throw new InvalidOperationException("Amount must be positive");

        return new Payment(id, status, amount);
    }
}

Inbound ACL Translators (Version-Specific)

public sealed class PaymentV1InboundTranslator
    : IInboundTranslator<ExternalPaymentV1, Payment>
{
    public TranslationResult<Payment> Translate(ExternalPaymentV1 input)
    {
        if (!PaymentStatus.TryFromExternal(input.Status, out var status))
        {
            return TranslationResult<Payment>.Failure(
                new TranslationIssue(
                    "UnsupportedStatus",
                    $"Status '{input.Status}' is not supported"));
        }

        if (input.Amount <= 0)
        {
            return TranslationResult<Payment>.Failure(
                new TranslationIssue(
                    "InvalidAmount",
                    "Amount must be greater than zero"));
        }

        return TranslationResult<Payment>.Success(
            Payment.Create(
                PaymentId.From(input.PaymentId),
                status,
                Money.From(input.Amount)));
    }
}
public sealed class PaymentV2InboundTranslator
    : IInboundTranslator<ExternalPaymentV2, Payment>
{
    public TranslationResult<Payment> Translate(ExternalPaymentV2 input)
    {
        if (!PaymentStatus.TryFromExternal(input.State, out var status))
        {
            return TranslationResult<Payment>.Failure(
                new TranslationIssue(
                    "UnsupportedState",
                    $"State '{input.State}' is not supported"));
        }

        return TranslationResult<Payment>.Success(
            Payment.Create(
                PaymentId.From(input.Id),
                status,
                Money.From(input.Money.Value)));
    }
}

Each translator:

  • Encodes exactly one external version
  • Contains no branching on version
  • Can be deleted safely

Version Routing (Orchestration Layer)

public sealed class PaymentIngressHandler
{
    private readonly PaymentV1InboundTranslator _v1;
    private readonly PaymentV2InboundTranslator _v2;

    public PaymentIngressHandler(
        PaymentV1InboundTranslator v1,
        PaymentV2InboundTranslator v2)
    {
        _v1 = v1;
        _v2 = v2;
    }

    public TranslationResult<Payment> Handle(
        object payload,
        int version)
    {
        return version switch
        {
            1 => _v1.Translate((ExternalPaymentV1)payload),
            2 => _v2.Translate((ExternalPaymentV2)payload),
            _ => TranslationResult<Payment>.Failure(
                new TranslationIssue(
                    "UnsupportedVersion",
                    $"Version '{version}' is not supported"))
        };
    }
}

The domain is completely unaware of:

  • External models
  • Versions
  • Translation failures

Composition with Domain Services

Note: The following examples use a simplified Payment concept to illustrate architectural boundaries.

The CapturePaymentUseCase class is to be found in the application layer. If it has multiple steps then you should call it a CapturePaymentSaga and follow the Saga pattern.

Real-world systems often model intent more explicitly (e.g. DisbursementInstruction) and delegate execution to external engines. The architectural principles remain the same.

public sealed class CapturePaymentUseCase
{
    private readonly PaymentIngressHandler _ingress;
    private readonly PaymentService _paymentService;

    public CapturePaymentUseCase(
        PaymentIngressHandler ingress,
        PaymentService paymentService)
    {
        _ingress = ingress;
        _paymentService = paymentService;
    }

    public void Execute(object payload, int version)
    {
        var result = _ingress.Handle(payload, version);

        if (!result.IsSuccess)
        {
            // Reject / dead-letter / alert
            return;
        }

        _paymentService.Capture(result.Value!);
    }
}

This class does not:

  • enforce business invariants

  • contain domain rules

  • decide what “Capture” means

Instead, it:

  • coordinates a single user/system intent

  • wires together ACL + domain

  • controls whether the domain is invoked

  • handles success vs failure paths


public sealed class CapturePaymentSaga
{
    private readonly PaymentIngressHandler _ingress;
    private readonly PaymentService _paymentService;

    // Infrastructure collaborators
    private readonly IDeadLetterPublisher _deadLetterPublisher;
    private readonly IMetricsSink _metrics;
    private readonly IPaymentCompensator _compensator;
    private readonly IOperationsNotifier _opsNotifier;

    public CapturePaymentSaga(
        PaymentIngressHandler ingress,
        PaymentService paymentService,
        IDeadLetterPublisher deadLetterPublisher,
        IMetricsSink metrics,
        IPaymentCompensator compensator,
        IOperationsNotifier opsNotifier)
    {
        _ingress = ingress;
        _paymentService = paymentService;
        _deadLetterPublisher = deadLetterPublisher;
        _metrics = metrics;
        _compensator = compensator;
        _opsNotifier = opsNotifier;
    }

    public void Execute(object payload, int version)
    {
        // STEP 1 — Translate (ACL)
        var translation = _ingress.Handle(payload, version);
        if (!translation.IsSuccess)
        {
            HandleTranslationFailure(translation);
            return;
        }

        var payment = translation.Value!;

        // STEP 2 — Domain behavior
        try
        {
            _paymentService.Capture(payment);
        }
        catch (Exception ex)
        {
            HandleDomainFailure(payment, ex);
            return;
        }

        // STEP 3 — Saga completes successfully
        _metrics.RecordSuccess();
    }

    // ─────────────────────────────
    // FAILURE OWNERSHIP (IN THE SAGA)
    // ─────────────────────────────

    private void HandleTranslationFailure(
        TranslationResult<Payment> failure)
    {
        _deadLetterPublisher.Publish(failure);
        _metrics.RecordIngressFailure();
    }

    private void HandleDomainFailure(
        Payment payment,
        Exception exception)
    {
        _compensator.Compensate(payment);
        _opsNotifier.Notify(exception);
        _metrics.RecordDomainFailure();
    }
}


15. ACL Anti-Patterns

These are design failures, not stylistic disagreements.

  • Domain services accepting DTOs
  • One translator handling multiple versions
  • Version flags inside domain logic
  • AutoMapper or reflection-based translation
  • ACLs performing IO or retries
  • Shared contracts with external systems

If you see any of these, the ACL has failed.


16. Architectural Enforcement & Tests

ACL boundaries must be enforced automatically.

Example Rules

  • Domain does not reference Integration
  • Domain does not reference ACL
  • ACL references Domain only

Example Architectural Tests (Conceptual)

  • Assert no dependency from Optima.Net.DomainIntegration
  • Assert no dependency from Optima.Net.DomainAcl
  • Assert no external DTO types appear in domain assemblies

If these tests fail, the architecture is already compromised.


17. Application Layer Primitives

The Application namespace defines control flow. It is the home of Use Cases and Sagas.

If the Domain answers “Is this allowed?” and the ACL answers “What does this mean?” then the Application layer answers:

  • What should the system do next?”

This namespace exists to make system behaviour explicit, auditable, and testable.

Intent

  • express system intent explicitly

  • coordinate ACLs, domain logic, and outcomes

  • own flow control, sequencing, and stopping conditions

  • make use cases and sagas visible

  • prevent orchestration logic from leaking into:

    • controllers

    • message handlers

    • domain services

    • ACLs

The Application layer coordinates. It never decides business truth.

Explicit Non-Goals

The Application namespace does not:

  • enforce business rules (Policies do that)

  • evaluate facts (Specifications do that)

  • translate external models (ACL does that)

  • perform IO

  • call databases, queues, HTTP APIs, or schedulers

  • decide domain correctness

If any of these appear here, the boundary is already broken.

Core Concepts

There are exactly two executable primitives:

  • Use Case

  • Saga

And one semantic marker:

  • Compensating Saga

Nothing else is required.


18. UseCase Example

A Use Case represents a single, synchronous application intent.

Characteristics:

  • single entry point

  • deterministic

  • no retries

  • no compensation

  • completes or rejects immediately

Mental model

“Given this input, do we proceed or not?”

Minimal Contract

namespace Optima.Net.Domain.Application;

public interface IUseCase<in TRequest, out TResult>
{
    TResult Execute(TRequest request);
}

There is no async variant by default. Async is a transport concern, not a modeling concern.

Realistic Example – Create Disbursement Instruction

public sealed class CreateDisbursementInstructionUseCase
    : IUseCase<CreateDisbursementRequest, ApplicationResult>
{
    private readonly IInboundTranslator<CreateDisbursementDto, DisbursementInstruction> _translator;
    private readonly DisbursementPolicy _policy;
    private readonly DisbursementRepository _repository;

    public CreateDisbursementInstructionUseCase(
        IInboundTranslator<CreateDisbursementDto, DisbursementInstruction> translator,
        DisbursementPolicy policy,
        DisbursementRepository repository)
    {
        _translator = translator;
        _policy = policy;
        _repository = repository;
    }

    public ApplicationResult Execute(CreateDisbursementRequest request)
    {
        // Step 1 – Translate meaning (ACL)
        var translation = _translator.Translate(request.Payload);
        if (!translation.IsSuccess)
            return ApplicationResult.Rejected(translation.Issues);

        var instruction = translation.Value!;

        // Step 2 – Ask the domain if we may proceed
        if (!_policy.IsSatisfiedBy(instruction))
            return ApplicationResult.Rejected("Disbursement policy not satisfied");

        // Step 3 – Persist intent
        _repository.Save(instruction);

        return ApplicationResult.Completed();
    }
}

Notice the following Key observations:

  • No business rules here

  • No IO except through collaborators

  • No retries

  • No workflow branching

  • Pure orchestration


19. Saga Example

Saga

A Saga represents a multi-step or failure-aware process.

Characteristics:

  • multiple steps

  • may span time

  • owns failure semantics

  • may retry, compensate, escalate, or stop

  • does not return a value

Mental model

“How do we try to make this happen safely?”

Minimal Contract

namespace Optima.Net.Domain.Application;

public interface ISaga<in TInput>
{
    void Execute(TInput input);
}

If a Saga returns a value, it is lying. Outcomes are observed via state, decisions, or events.

Realistic Example – Execute Disbursement Saga

public sealed class ExecuteDisbursementSaga
    : ISaga<DisbursementInstruction>, ICompensatingSaga
{
    private readonly PaymentsEngineGateway _payments;
    private readonly DisbursementRepository _repository;

    public ExecuteDisbursementSaga(
        PaymentsEngineGateway payments,
        DisbursementRepository repository)
    {
        _payments = payments;
        _repository = repository;
    }

    public void Execute(DisbursementInstruction instruction)
    {
        try
        {
            instruction.MarkInProgress();

            foreach (var line in instruction.Lines)
            {
                _payments.Execute(line);
                instruction.MarkLineCompleted(line);
            }

            instruction.MarkCompleted();
            _repository.Save(instruction);
        }
        catch (Exception ex)
        {
            Compensate(instruction, ex);
            throw;
        }
    }

    private void Compensate(
        DisbursementInstruction instruction,
        Exception exception)
    {
        instruction.MarkFailed(exception.Message);
        _repository.Save(instruction);

        // Best-effort rollback / escalation
        // No assumption of full reversibility
    }
}

Key observations:

  • Compensation is explicit

  • Compensation is contextual

  • No fake symmetry

  • The Saga owns failure semantics

  • Infrastructure is called, but not owned


** Compensating Saga Marker**

Some Sagas include explicit compensation logic.

To make that intent visible and enforceable, Optima provides a marker interface.


public interface ICompensatingSaga { }

Design Rule

If a Saga contains compensation or reversal logic, it must implement ICompensatingSaga.

This is:

  • a semantic signal

  • enforceable via code review or analyzers

  • intentionally behavior-free

There is no Compensate() method on the interface. Compensation is designed explicitly, not abstracted.


Application Results vs Domain Decisions

Application outcomes describe flow, not truth.

Domain Decisions explain why

Application Results explain what happened next

Examples:

  • Completed

  • Rejected

  • Deferred

  • Failed

Do not reuse domain decisions as application outcomes.

Dependency Rules (Non-Negotiable)

Allowed:


Application -> Policies
Application -> Specifications
Application -> Decisions
Application -> ACL
ACL -> Domain

Not Allowed:


Domain -> Application
ACL -> Application
Application -> Infrastructure

Application enforcing business rules

Anti-Patterns

  • Controllers orchestrating flow
  • Message handlers containing business branches
  • Domain services deciding next steps
  • ACLs retrying or compensating
  • “God” UseCases with multiple responsibilities
  • Naming everything a Saga “for consistency”

20. Design Philosophy and Boundaries

Optima.Net.Domain intentionally avoids:

  • Rule engines
  • Workflow engines
  • Infrastructure concerns
  • Repositories or persistence abstractions

Its purpose is singular:

Make domain intent explicit, enforceable, and auditable.

If a rule matters, model it as a specification.
If a decision matters, model it as a policy.
If an integration matters, protect it with an ACL.

Mental Model Summary

  • Specifications → Is this true?

  • Policies → May we proceed?

  • Decisions → What happened, and why?

  • Application → What should we do next?

  • ACL → What does this external thing mean?


© Optima.Net.Domain

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.
  • net8.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Optima.Net.Domain:

Package Downloads
Optima.Net.NegotiatR

NegotiatR is a deterministic, single-pass negotiation engine that proposes alternative intents when policies fail. It consumes policy diagnostics, never re-evaluates rules, and guarantees outcomes. Designed for domain-driven systems, it keeps fallback decisions explicit, auditable, and separate from policy evaluation, workflows, and execution logic, without retries or implicit control flow.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.1.3 159 1/7/2026
3.1.2 147 12/29/2025
3.1.1 110 12/29/2025
3.1.0 111 12/27/2025
3.0.1 128 12/26/2025
3.0.0 150 12/26/2025
2.0.0 190 12/24/2025
1.0.2 194 12/23/2025
1.0.1 188 12/22/2025
1.0.0 189 12/22/2025

RELEASENOTES.md