Skip to content

Conversation

@xavierjohn
Copy link
Owner

What

Adds FunctionalDdd.ArdalisSpecification - a lightweight integration package that bridges Ardalis.Specification with FunctionalDDD's Railway Oriented Programming patterns.

Why

  • Ardalis.Specification is the most popular specification pattern library for .NET (~2M+ downloads)
  • It returns null or throws on not found - no Result<T> integration
  • FunctionalDDD users need seamless ROP chains with specifications
  • Building our own specification library would duplicate existing, mature functionality

Key Features

Extension Method Returns Description
FirstOrNotFoundAsync Result<T> First match or NotFoundError
SingleOrNotFoundAsync Result<T> Single match, NotFoundError if none, ConflictError if multiple

Usage with Value Objects

// Specifications accept value objects, not primitives
public sealed class ProductBySkuSpec : SingleResultSpecification<Product>
{
    public ProductBySkuSpec(Sku sku) =>  // ← Accepts Sku from PrimitiveValueObjects
        Query.Where(p => p.Sku == sku);
}

// Fluent ROP pipeline
var result = await _repository
    .SingleOrNotFoundAsync(new ProductBySkuSpec(sku))
    .EnsureAsync(p => p.IsActive, Error.Validation("Product inactive"))
    .MapAsync(p => new ProductDto(p));

Works with PrimitiveValueObjects Library

// Value objects from FunctionalDdd.PrimitiveValueObjects
public partial class ProductId : RequiredGuid<ProductId>;
public partial class Sku : RequiredString<Sku>;
public partial class ProductName : RequiredString<ProductName>;

// Money from the library
var product = new Product(
    ProductId.Create(Guid.NewGuid()),
    Sku.Create("LAPTOP-001"),
    ProductName.Create("Gaming Laptop"),
    Money.Usd(1299.99m)  // ← Uses library's Money type
);

Files Added

ArdalisSpecification/
├── src/
│   ├── ArdalisSpecification.csproj
│   ├── RepositoryResultExtensions.cs
│   └── ReadRepositoryResultExtensions.cs
├── tests/
│   └── (27 passing tests)
└── README.md

Examples/SpecificationExample/
├── Domain/
│   ├── Product.cs
│   └── ValueObjects.cs          # Uses RequiredGuid, RequiredString
├── Specifications/
│   └── ProductSpecifications.cs # Type-safe specs with value objects
├── Data/
│   └── ProductDbContext.cs      # EF Core with value object converters
└── Program.cs                   # Comprehensive demo

Benefits

Benefit Description
Type Safety Specifications accept Sku, not string - compiler prevents errors
Validation First Invalid input fails at TryCreate, never reaches database
ROP Integration Seamless chaining with Ensure, Bind, Map, Tap
Minimal Footprint Just 2 extension classes wrapping mature Ardalis library
No Duplication Leverages existing ecosystem instead of reinventing

Introduced FunctionalDdd.ArdalisSpecification package with extension methods for Ardalis.Specification repositories, enabling Result<T>-based error handling and fluent Railway Oriented Programming (ROP) chains. Added `FirstOrNotFoundAsync`, `SingleOrNotFoundAsync`, `ToListAsync`, and `AnyAsync` for both IRepositoryBase<T> and IReadRepositoryBase<T>. Provided comprehensive unit tests and a sample application demonstrating real-world usage with EF Core. Updated solution, project references, and documentation.
Replaced primitive types in Product aggregate and related specs with strongly-typed value objects (ProductId, Sku, ProductName, CategoryName, Money) from FunctionalDDD. Updated EF Core mappings for value object support and conversions. All specifications and queries now require value objects, enforcing compile-time type safety and validation. Added ValueObjects.cs for domain-specific types and enabled source generation. Demonstration code and DTOs updated to showcase benefits of value objects, including improved validation, maintainability, and expressiveness.
@github-actions
Copy link

Test Results

2 583 tests  +27   2 583 ✅ +27   22s ⏱️ -1s
    9 suites + 1       0 💤 ± 0 
    9 files   + 1       0 ❌ ± 0 

Results for commit 64aaf05. ± Comparison against base commit da56ee0.

@codecov
Copy link

codecov bot commented Jan 28, 2026

Codecov Report

❌ Patch coverage is 93.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.36%. Comparing base (da56ee0) to head (64aaf05).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...pecification/src/ReadRepositoryResultExtensions.cs 93.33% 1 Missing ⚠️
...lisSpecification/src/RepositoryResultExtensions.cs 93.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #270      +/-   ##
==========================================
+ Coverage   90.33%   90.36%   +0.03%     
==========================================
  Files          99      101       +2     
  Lines        2586     2616      +30     
  Branches      538      544       +6     
==========================================
+ Hits         2336     2364      +28     
- Misses        149      151       +2     
  Partials      101      101              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@xavierjohn xavierjohn merged commit 17a7d0d into main Jan 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants