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
<PackageReference Include="IntraDotNet.CleanArchitecture.Domain" Version="1.0.0" />
<PackageVersion Include="IntraDotNet.CleanArchitecture.Domain" Version="1.0.0" />
<PackageReference Include="IntraDotNet.CleanArchitecture.Domain" />
paket add IntraDotNet.CleanArchitecture.Domain --version 1.0.0
#r "nuget: IntraDotNet.CleanArchitecture.Domain, 1.0.0"
#:package IntraDotNet.CleanArchitecture.Domain@1.0.0
#addin nuget:?package=IntraDotNet.CleanArchitecture.Domain&version=1.0.0
#tool nuget:?package=IntraDotNet.CleanArchitecture.Domain&version=1.0.0
IntraDotNet.CleanArchitecture.Domain
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 createdCreatedBy- Who created the entityLastUpdateOn- When the entity was last modifiedLastUpdateBy- Who last modified the entityDeletedOn- When the entity was soft-deleted (nullable)DeletedBy- Who soft-deleted the entity (nullable)
Note: When used with
AuditableDbContextfrom 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
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(); } }Use Factory Methods for Creation
public static Product Create(string name, decimal price) { // Centralized validation return new Product { ... }; }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); }Use Private Setters for Collections
public class Order { private readonly List<OrderItem> _items = new(); public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly(); }
❌ Don'ts
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; } }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");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
- Application Layer - Application services and use cases
- Infrastructure.EFCore - Entity Framework Core implementation
- Clean Architecture Principles
| 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 (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