Optima.Net.Domain
1.0.0
Prefix Reserved
See the version list below for details.
dotnet add package Optima.Net.Domain --version 1.0.0
NuGet\Install-Package Optima.Net.Domain -Version 1.0.0
<PackageReference Include="Optima.Net.Domain" Version="1.0.0" />
<PackageVersion Include="Optima.Net.Domain" Version="1.0.0" />
<PackageReference Include="Optima.Net.Domain" />
paket add Optima.Net.Domain --version 1.0.0
#r "nuget: Optima.Net.Domain, 1.0.0"
#:package Optima.Net.Domain@1.0.0
#addin nuget:?package=Optima.Net.Domain&version=1.0.0
#tool nuget:?package=Optima.Net.Domain&version=1.0.0
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 and Policies 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
- Motivation and Mental Model
- Core Concepts Overview
- Specifications (Sync)
- Composite Specifications
- Policies (Sync)
- Composite Policies
- Async Specifications and Policies
- Diagnostics
- Diagnostic Helpers
- Decisions / Outcomes
- Identity and Naming
- End-to-End Examples
- Design Philosophy and Boundaries
1. Motivation and Mental Model
In many systems, business rules end up:
- buried in
ifstatements, - 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. Design Philosophy and Boundaries
Optima.Net.Domain intentionally avoids:
- Rule engines
- Workflow engines
- Infrastructure concerns
- Base entities or repositories
Its purpose is singular:
Make domain intent explicit and executable.
If a rule matters, model it as a specification.
If a decision matters, model it as a policy.
© Optima.Net.Domain
| Product | Versions 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. |
-
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.
RELEASENOTES.md