Skip to content

JerrettDavis/JD.Domain

Repository files navigation

JD.Domain

A production-ready, opt-in domain modeling suite for .NET that brings rich domain models, business rules, and configuration to any codebaseβ€”whether database-first, code-first, or hybrid.

Build Status codecov License: MIT


Why JD.Domain?

Traditional EF Core models often end up anemicβ€”plain data bags with validation scattered across controllers, services, or separate validators. JD.Domain changes that by providing:

  • Rich Domain Models: Embed invariants, validators, and policies directly in your domain
  • Opt-In Architecture: Adopt incrementally without forced dependencies or framework lock-in
  • Database-First Friendly: Works seamlessly with reverse-engineered EF Core entities
  • Code Generation: Generate FluentValidation validators, rich domain types, and more
  • Version Management: Track domain evolution with snapshots and detect breaking changes

Quick Start

Installation

Install the core packages:

# Automatic manifest generation
dotnet add package JD.Domain.ManifestGeneration
dotnet add package JD.Domain.ManifestGeneration.Generator

# Core runtime and rules
dotnet add package JD.Domain.Rules
dotnet add package JD.Domain.Runtime

# EF Core integration
dotnet add package JD.Domain.EFCore

# ASP.NET Core integration (optional)
dotnet add package JD.Domain.AspNetCore

Define Your Domain

Add attributes to your entity classes for automatic manifest generation:

using System.ComponentModel.DataAnnotations;
using JD.Domain.ManifestGeneration;
using JD.Domain.Rules;

// Configure manifest generation
[assembly: GenerateManifest("ECommerce", Version = "1.0.0")]

// Define entities with attributes
[DomainEntity]
public class Customer
{
    [Key]
    public int Id { get; set; }

    [Required]
    [MaxLength(200)]
    public string Name { get; set; } = string.Empty;

    [Required]
    [MaxLength(500)]
    public string Email { get; set; } = string.Empty;
}

[DomainEntity]
public class Order
{
    [Key]
    public int Id { get; set; }

    [Required]
    public int CustomerId { get; set; }

    [Required]
    public decimal Total { get; set; }
}

// Build generates ECommerceManifest.GeneratedManifest automatically
// NO manual string writing required!

// Define business rules
var customerRules = new RuleSetBuilder<Customer>("Default")
    .Invariant("Name.Required", c => !string.IsNullOrWhiteSpace(c.Name))
        .WithMessage("Customer name is required")
    .Invariant("Email.Format", c => c.Email.Contains("@"))
        .WithMessage("Email must be valid")
    .Build();

// Use auto-generated manifest and evaluate rules at runtime
using JD.Domain.Generated;
var runtime = DomainRuntime.CreateEngine(ECommerceManifest.GeneratedManifest, customerRules);
var customer = new Customer { Name = "", Email = "invalid" };
var result = await runtime.EvaluateAsync(customer);

if (!result.IsValid)
{
    foreach (var error in result.Errors)
        Console.WriteLine($"{error.PropertyName}: {error.Message}");
}

Configure EF Core

using JD.Domain.EFCore;
using JD.Domain.Generated;

public class AppDbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Apply auto-generated domain configuration to EF Core
        modelBuilder.ApplyDomainManifest(ECommerceManifest.GeneratedManifest);

        base.OnModelCreating(modelBuilder);
    }
}

ASP.NET Core Integration

using JD.Domain.AspNetCore;
using JD.Domain.Generated;

var builder = WebApplication.CreateBuilder(args);

// Register domain validation with auto-generated manifest
builder.Services.AddDomainValidation(options =>
{
    options.AddManifest(ECommerceManifest.GeneratedManifest);
});

var app = builder.Build();

// Use domain validation middleware
app.UseDomainValidation();

// Validate in minimal API endpoints
app.MapPost("/customers", async (Customer customer, IDomainEngine engine) =>
{
    var result = await engine.EvaluateAsync(customer);
    if (!result.IsValid)
        return Results.ValidationProblem(result.ToValidationErrors());

    // Save customer...
    return Results.Created($"/customers/{customer.Id}", customer);
});

Key Features

🎯 Three Workflows

Code-First: Define your domain with the fluent DSL and generate EF Core configurations.

Database-First: Reverse-engineer entities from an existing database and layer on business rules.

Hybrid: Mix code-first and database-first approaches while tracking evolution with snapshots.

See workflow guide β†’

πŸ“ Rich Business Rules

Define four types of rules:

  • Invariants: Always-true constraints (e.g., "Email is required")
  • Validators: Context-dependent validation (e.g., "Email format is valid")
  • Policies: Authorization and business policies (e.g., "User can approve orders")
  • Derivations: Computed properties (e.g., "Total = Quantity Γ— Price")

Learn about rules β†’

πŸ”„ Source Generators

Generate code from your domain manifest:

  • FluentValidation Validators: Convert JD rules to FluentValidation automatically
  • Rich Domain Types: Construction-safe domain models with Result<T> and property validation

Explore generators β†’

πŸ“Έ Version Management

Track domain evolution with snapshots:

# Create a snapshot of your domain
jd-domain snapshot --manifest domain.json --output snapshots/

# Compare versions to detect changes
jd-domain diff snapshots/v1.json snapshots/v2.json --format md

# Generate migration plans
jd-domain migrate-plan snapshots/v1.json snapshots/v2.json

Version management guide β†’

Packages

JD.Domain is organized into focused, composable packages:

Package Purpose
JD.Domain.Abstractions Core contracts and the DomainManifest model
JD.Domain.ManifestGeneration ⭐ Attributes for automatic manifest generation
JD.Domain.ManifestGeneration.Generator ⭐ Roslyn source generator for manifests (NO manual strings!)
JD.Domain.Modeling Fluent DSL for domain modeling (alternative approach)
JD.Domain.Configuration EF Core-compatible configuration DSL
JD.Domain.Rules Business rules (invariants, validators, policies)
JD.Domain.Runtime Rule evaluation engine
JD.Domain.EFCore Entity Framework Core integration
JD.Domain.AspNetCore ASP.NET Core middleware and filters
JD.Domain.Validation Validation contracts for web APIs
JD.Domain.Generators.Core Base infrastructure for code generators
JD.Domain.DomainModel.Generator Generate rich domain types
JD.Domain.FluentValidation.Generator Generate FluentValidation validators
JD.Domain.Snapshot Domain snapshot serialization
JD.Domain.Diff Snapshot comparison and breaking change detection
JD.Domain.Cli Command-line tools (jd-domain)
JD.Domain.T4.Shims T4 template integration

See package matrix β†’

Sample Applications

Explore complete working examples:

  • Manifest Generation ⭐: Automatic manifest generation from entity classes (NO manual strings!)
  • CodeFirst: Define domain with DSL, generate EF configs
  • DbFirst: Add rules to reverse-engineered entities
  • Hybrid: Mix approaches with snapshot versioning

Documentation

Requirements

  • .NET 8.0 or later (packages target .NET Standard 2.0 for broad compatibility)
  • Entity Framework Core 8.0+ (for EF Core integration)
  • ASP.NET Core 8.0+ (for ASP.NET Core integration)

Contributing

Contributions are welcome! Please see our contributing guide for details on:

  • Setting up your development environment
  • Coding standards and conventions
  • Submitting pull requests
  • Reporting issues

Design Principles

JD.Domain is built on three core principles:

  1. Opt-In: Use what you need, ignore the rest. No forced dependencies.
  2. Modular: Packages are focused and composable.
  3. Deterministic: Snapshots and generation are stable and CI-friendly.

License

JD.Domain is licensed under the MIT License.

Support


Built with ❀️ by Jerrett Davis