IntraDotNet.CleanArchitecture.Domain 1.0.0

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

IntraDotNet.CleanArchitecture.Domain

.NET C# License

Table Of Contents

Overview

This package represents the Domain Layer of the IntraDotNet Clean Architecture framework. It provides the foundational building blocks for defining your core business logic, domain entities, and business rules that are completely independent of any infrastructure, frameworks, or external concerns.

The Domain Layer is the heart of your application and follows these principles:

  • No external dependencies - Only references .NET standard libraries
  • Technology agnostic - Independent of databases, UI, or frameworks
  • Business logic focused - Contains only domain concepts and rules
  • Persistence ignorant - Entities don't know how they're stored

What's Included

  • Entity Marker Interfaces: Define characteristics of your domain entities (identifiers, auditing, versioning)
  • Common Contracts: Shared interfaces for persistence concerns
  • Type-safe Identifiers: Support for GUID and Integer-based entity identities
  • Audit Tracking: Built-in support for creation, modification, and deletion tracking
  • Soft Delete: Logical deletion capabilities
  • Optimistic Concurrency: Row versioning support

Key Concepts

Domain Entities

Domain entities are objects with a unique identity that runs through time and different states. They represent core business concepts in your application.

Key characteristics:

  • Have a unique identifier (ID)
  • Have a lifecycle (created, modified, deleted)
  • Encapsulate business logic and rules
  • Can change state while maintaining identity

Marker Interfaces

Marker interfaces in this library represent domain concepts, not infrastructure concerns. They indicate what an entity is from a business perspective:

  • IGuidIdentifier - "This entity has a globally unique identity"
  • IIntIdentifier - "This entity has a sequential numeric identity"
  • IAuditable - "This entity tracks who created and modified it"
  • ISoftDeleteAuditable - "This entity can be logically deleted"
  • IRowVersion - "This entity supports optimistic concurrency"

These interfaces allow the infrastructure layer to handle cross-cutting concerns (like auditing) automatically, while keeping the domain layer clean.

Project Structure

Common/
  └── Persistence/
      ├── IGuidIdentifier.cs          # GUID-based entity identity
      ├── IIntIdentifier.cs           # Integer-based entity identity
      ├── IAuditable.cs               # Full audit tracking
      ├── ISoftDeleteAuditable.cs     # Soft delete support
      └── IRowVersion.cs              # Optimistic concurrency

Entity Marker Interfaces

IGuidIdentifier

Defines an entity with a globally unique identifier (GUID). Best for:

  • Distributed systems
  • Entities that need IDs before database insertion
  • Systems with merging data from multiple sources
  • Public-facing APIs (harder to enumerate)

IIntIdentifier

Defines an entity with an auto-incrementing integer identifier. Best for:

  • Simple, sequential IDs
  • Performance-critical scenarios (smaller index size)
  • Human-readable identifiers
  • Single-database applications

IAuditable

Provides full audit trail including creation, modification, and soft deletion tracking:

  • CreatedOn - When the entity was created
  • CreatedBy - Who created the entity
  • LastUpdateOn - When the entity was last modified
  • LastUpdateBy - Who last modified the entity
  • DeletedOn - When the entity was soft-deleted (nullable)
  • DeletedBy - Who soft-deleted the entity (nullable)

Note: When used with AuditableDbContext from the Infrastructure.EFCore package, these fields are populated automatically!

ISoftDeleteAuditable

Provides only soft-delete tracking:

  • DeletedOn - When the entity was soft-deleted (nullable)
  • DeletedBy - Who soft-deleted the entity (nullable)

Use this when you only need soft-delete capability without full audit tracking.

IRowVersion

Enables optimistic concurrency control using row versioning:

  • RowVersion - A byte array that changes on each update

Prevents lost updates when multiple users edit the same entity simultaneously.

Examples

Basic Entity with GUID Identity

using IntraDotNet.CleanArchitecture.Domain.Common.Persistence;

namespace YourApp.Domain.Entities;

/// <summary>
/// Represents a product in the catalog.
/// </summary>
public class Product : IGuidIdentifier
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public string SKU { get; set; } = string.Empty;

    // Parameterless constructor for EF Core
    private Product() { }

    // Factory method for creating new products
    public static Product Create(string name, decimal price, string sku)
    {
        // Domain validation
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException("Product name is required", nameof(name));
        
        if (price <= 0)
            throw new ArgumentException("Price must be greater than zero", nameof(price));

        return new Product
        {
            Id = Guid.NewGuid(),
            Name = name,
            Price = price,
            SKU = sku
        };
    }

    // Domain methods
    public void UpdatePrice(decimal newPrice)
    {
        if (newPrice <= 0)
            throw new ArgumentException("Price must be greater than zero", nameof(newPrice));
        
        Price = newPrice;
    }
}

Entity with Integer Identity

using IntraDotNet.CleanArchitecture.Domain.Common.Persistence;

namespace YourApp.Domain.Entities;

/// <summary>
/// Represents a category in the system.
/// </summary>
public class Category : IIntIdentifier
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;

    private Category() { }

    public static Category Create(string name, string description)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException("Category name is required", nameof(name));

        return new Category
        {
            Name = name,
            Description = description
        };
    }
}

Entity with Optimistic Concurrency Control

using IntraDotNet.CleanArchitecture.Domain.Common.Persistence;
using System.ComponentModel.DataAnnotations;

namespace YourApp.Domain.Entities;

/// <summary>
/// Represents inventory stock levels with concurrency protection.
/// </summary>
public class InventoryItem : IGuidIdentifier, IRowVersion
{
    public Guid Id { get; set; }
    public string ProductSKU { get; set; } = string.Empty;
    public int Quantity { get; set; }
    
    [Timestamp]
    public byte[]? RowVersion { get; set; }

    private InventoryItem() { }

    public static InventoryItem Create(string productSKU, int initialQuantity)
    {
        if (initialQuantity < 0)
            throw new ArgumentException("Initial quantity cannot be negative", nameof(initialQuantity));

        return new InventoryItem
        {
            Id = Guid.NewGuid(),
            ProductSKU = productSKU,
            Quantity = initialQuantity
        };
    }

    public void AdjustQuantity(int adjustment)
    {
        var newQuantity = Quantity + adjustment;
        
        if (newQuantity < 0)
            throw new InvalidOperationException("Cannot reduce quantity below zero");
        
        Quantity = newQuantity;
    }
}

Entity with Soft Delete Support Only

using IntraDotNet.CleanArchitecture.Domain.Common.Persistence;

namespace YourApp.Domain.Entities;

/// <summary>
/// Represents a customer that can be soft-deleted.
/// </summary>
public class Customer : IGuidIdentifier, ISoftDeleteAuditable
{
    public Guid Id { get; set; }
    public string Email { get; set; } = string.Empty;
    public string FullName { get; set; } = string.Empty;
    
    // Soft delete fields (automatically populated by AuditableDbContext)
    public DateTimeOffset? DeletedOn { get; set; }
    public string? DeletedBy { get; set; }

    private Customer() { }

    public static Customer Create(string email, string fullName)
    {
        // Validation logic
        return new Customer
        {
            Id = Guid.NewGuid(),
            Email = email,
            FullName = fullName
        };
    }

    public bool IsDeleted => DeletedOn.HasValue;
}

Entity with Full Audit Tracking

using IntraDotNet.CleanArchitecture.Domain.Common.Persistence;

namespace YourApp.Domain.Entities;

/// <summary>
/// Represents an order with complete audit trail.
/// </summary>
public class Order : IGuidIdentifier, IAuditable
{
    public Guid Id { get; set; }
    public string OrderNumber { get; set; } = string.Empty;
    public decimal TotalAmount { get; set; }
    public OrderStatus Status { get; set; }

    // Audit fields (automatically populated by AuditableDbContext)
    public DateTimeOffset CreatedOn { get; set; }
    public string CreatedBy { get; set; } = string.Empty;
    public DateTimeOffset LastUpdateOn { get; set; }
    public string LastUpdateBy { get; set; } = string.Empty;
    public DateTimeOffset? DeletedOn { get; set; }
    public string? DeletedBy { get; set; }

    private Order() { }

    public static Order Create(string orderNumber, decimal totalAmount)
    {
        return new Order
        {
            Id = Guid.NewGuid(),
            OrderNumber = orderNumber,
            TotalAmount = totalAmount,
            Status = OrderStatus.Pending
        };
    }

    // Domain behavior
    public void MarkAsShipped()
    {
        if (Status != OrderStatus.Pending)
            throw new InvalidOperationException("Only pending orders can be shipped");
        
        Status = OrderStatus.Shipped;
    }

    public bool IsDeleted => DeletedOn.HasValue;
}

public enum OrderStatus
{
    Pending,
    Shipped,
    Delivered,
    Cancelled
}

Combining Multiple Interfaces

using IntraDotNet.CleanArchitecture.Domain.Common.Persistence;
using System.ComponentModel.DataAnnotations;

namespace YourApp.Domain.Entities;

/// <summary>
/// A highly protected entity with GUID identity, full auditing, and optimistic concurrency.
/// </summary>
public class CriticalBusinessEntity : IGuidIdentifier, IAuditable, IRowVersion
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal CriticalValue { get; set; }
    
    // Audit fields
    public DateTimeOffset CreatedOn { get; set; }
    public string CreatedBy { get; set; } = string.Empty;
    public DateTimeOffset LastUpdateOn { get; set; }
    public string LastUpdateBy { get; set; } = string.Empty;
    public DateTimeOffset? DeletedOn { get; set; }
    public string? DeletedBy { get; set; }
    
    // Concurrency control
    [Timestamp]
    public byte[]? RowVersion { get; set; }

    private CriticalBusinessEntity() { }

    public static CriticalBusinessEntity Create(string name, decimal value)
    {
        return new CriticalBusinessEntity
        {
            Id = Guid.NewGuid(),
            Name = name,
            CriticalValue = value
        };
    }
}

Best Practices

✅ Do's

  1. Keep Domain Logic in Entities

    public class Order
    {
        public void AddItem(OrderItem item)
        {
            // Validation and business rules here
            if (item.Quantity <= 0)
                throw new ArgumentException("Quantity must be positive");
    
            _items.Add(item);
            RecalculateTotal();
        }
    }
    
  2. Use Factory Methods for Creation

    public static Product Create(string name, decimal price)
    {
        // Centralized validation
        return new Product { ... };
    }
    
  3. Encapsulate Business Rules

    public void ApplyDiscount(decimal percentage)
    {
        if (percentage < 0 || percentage > 100)
            throw new ArgumentException("Discount must be between 0 and 100");
    
        Price *= (1 - percentage / 100);
    }
    
  4. Use Private Setters for Collections

    public class Order
    {
        private readonly List<OrderItem> _items = new();
        public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
    }
    

❌ Don'ts

  1. Don't Add Infrastructure Dependencies

    // ❌ Bad - Domain depends on Entity Framework
    public class Product
    {
        [Required]
        [StringLength(100)]
        public string Name { get; set; }
    }
    
    // ✅ Good - Pure domain
    public class Product
    {
        public string Name { get; set; }
    
        public void SetName(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentException("Name is required");
            Name = name;
        }
    }
    
  2. Don't Manually Set Audit Fields

    // ❌ Bad - Manual audit field setting
    var product = new Product
    {
        Name = "Widget",
        CreatedOn = DateTimeOffset.UtcNow,
        CreatedBy = "john.doe"
    };
    
    // ✅ Good - Let AuditableDbContext handle it
    var product = Product.Create("Widget");
    
  3. Don't Expose Public Setters Unnecessarily

    // ❌ Bad - Anyone can set invalid state
    public class Order
    {
        public decimal TotalAmount { get; set; }
    }
    
    // ✅ Good - Controlled through business logic
    public class Order
    {
        public decimal TotalAmount { get; private set; }
    
        private void RecalculateTotal()
        {
            TotalAmount = _items.Sum(i => i.LineTotal);
        }
    }
    

Integration with Infrastructure

When you use these interfaces with the IntraDotNet.CleanArchitecture.Infrastructure.EFCore package:

Automatic Auditing

Entities implementing IAuditable or ISoftDeleteAuditable automatically get their audit fields populated when using AuditableDbContext:

// You write this:
var product = Product.Create("Widget", 99.99m);
await _repository.AddAsync(product);
await _unitOfWork.SaveChangesAsync();

// AuditableDbContext automatically sets:
// product.CreatedBy = "john.doe" (from ICurrentUserService)
// product.CreatedOn = DateTimeOffset.UtcNow
// product.LastUpdateBy = "john.doe"
// product.LastUpdateOn = DateTimeOffset.UtcNow

Automatic Soft Delete

Entities implementing ISoftDeleteAuditable are automatically soft-deleted:

// You write this:
_repository.Delete(product);
await _unitOfWork.SaveChangesAsync();

// AuditableDbContext automatically:
// - Changes state from Deleted to Modified
// - Sets product.DeletedBy = "john.doe"
// - Sets product.DeletedOn = DateTimeOffset.UtcNow
// - Keeps the entity in the database

Optimistic Concurrency

Entities implementing IRowVersion are protected from concurrent updates:

// User A and User B both load the same entity
var productA = await _repositoryA.GetByIdAsync(id);
var productB = await _repositoryB.GetByIdAsync(id);

// User A updates and saves
productA.UpdatePrice(99.99m);
await _unitOfWorkA.SaveChangesAsync(); // ✅ Success

// User B updates and saves
productB.UpdatePrice(89.99m);
await _unitOfWorkB.SaveChangesAsync(); // ❌ DbUpdateConcurrencyException

See Also

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 (3)

Showing the top 3 NuGet packages that depend on IntraDotNet.CleanArchitecture.Domain:

Package Downloads
IntraDotNet.CleanArchitecture.Application

Application layer foundation for Clean Architecture. Provides repository interfaces, Unit of Work pattern, Result pattern, and base service implementations.

IntraDotNet.CleanArchitecture.Infrastructure.EFCore

Entity Framework Core infrastructure implementation for Clean Architecture. Provides AuditableDbContext with automatic audit tracking, repository implementations, Unit of Work pattern, and soft delete support.

KeyZee

A minimal self-hostable encrypted key value pair sdk that can be used with any modern relational database written in .NET Core

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 184 12/31/2025

Initial release of IntraDotNet.CleanArchitecture.Domain
     - IGuidIdentifier and IIntIdentifier interfaces
     - IAuditable interface for creation and modification tracking
     - ISoftDeleteAuditable interface for soft delete support
     - IRowVersion interface for optimistic concurrency