Doulex.DomainDriven.Repo.FileSystem
1.6.3
dotnet add package Doulex.DomainDriven.Repo.FileSystem --version 1.6.3
NuGet\Install-Package Doulex.DomainDriven.Repo.FileSystem -Version 1.6.3
<PackageReference Include="Doulex.DomainDriven.Repo.FileSystem" Version="1.6.3" />
<PackageVersion Include="Doulex.DomainDriven.Repo.FileSystem" Version="1.6.3" />
<PackageReference Include="Doulex.DomainDriven.Repo.FileSystem" />
paket add Doulex.DomainDriven.Repo.FileSystem --version 1.6.3
#r "nuget: Doulex.DomainDriven.Repo.FileSystem, 1.6.3"
#:package Doulex.DomainDriven.Repo.FileSystem@1.6.3
#addin nuget:?package=Doulex.DomainDriven.Repo.FileSystem&version=1.6.3
#tool nuget:?package=Doulex.DomainDriven.Repo.FileSystem&version=1.6.3
Doulex.DomainDriven
A comprehensive Domain-Driven Design (DDD) framework for .NET that provides essential building blocks for implementing clean architecture patterns with robust exception handling and Entity Framework Core integration.
โจ Features
- ๐๏ธ Complete DDD Building Blocks: Aggregate roots, entities, value objects, domain events, and repositories
- ๐ Unit of Work Pattern: Transaction management and change tracking
- ๐ก๏ธ Comprehensive Exception Handling: Rich domain exceptions with automatic EF Core translation
- ๐ Entity Framework Core Integration: Ready-to-use repository implementations
- ๐ Intelligent Retry Logic: Automatic detection of retryable database operations
- ๐ Structured Logging Support: Exception properties designed for monitoring and debugging
- ๐ฏ Clean Architecture: Separation of concerns with clear boundaries
- โก High Performance: Minimal overhead with efficient implementations
๐ฆ NuGet Packages
Package | Version | Downloads | Description |
---|---|---|---|
Doulex.DomainDriven | Core DDD abstractions and exception system | ||
Doulex.DomainDriven.Repo.EFCore | Entity Framework Core implementation |
๐ Quick Start
Installation
Install the core package:
dotnet add package Doulex.DomainDriven
For Entity Framework Core support:
dotnet add package Doulex.DomainDriven.Repo.EFCore
Or via Package Manager Console:
PM> Install-Package Doulex.DomainDriven
PM> Install-Package Doulex.DomainDriven.Repo.EFCore
Basic Usage
1. Define Your Domain Entities
// Define an aggregate root
public class User : AggregateRoot<int>
{
public string Email { get; private set; }
public string Name { get; private set; }
public User(string email, string name)
{
Email = email;
Name = name;
}
public void UpdateName(string newName)
{
if (string.IsNullOrWhiteSpace(newName))
throw new ArgumentException("Name cannot be empty");
Name = newName;
}
}
2. Create Repository Interface
public interface IUserRepository : IRepository<User, int>
{
Task<User?> GetByEmailAsync(string email, CancellationToken cancellationToken = default);
Task<bool> EmailExistsAsync(string email, CancellationToken cancellationToken = default);
}
3. Implement Repository with EF Core
public class UserRepository : EntityFrameworkCoreRepository<User, int>, IUserRepository
{
public UserRepository(DbContext context) : base(context)
{
}
public async Task<User?> GetByEmailAsync(string email, CancellationToken cancellationToken = default)
{
return await GetAsync(u => u.Email == email, cancellationToken);
}
public async Task<bool> EmailExistsAsync(string email, CancellationToken cancellationToken = default)
{
return await ExistsAsync(u => u.Email == email, cancellationToken);
}
}
4. Configure Services
// In Program.cs or Startup.cs
services.AddDbContext<YourDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddScoped<IUnitOfWork, EntityFrameworkCoreUnitOfWork<YourDbContext>>();
services.AddScoped<IUserRepository, UserRepository>();
5. Use in Application Services
public class UserService
{
private readonly IUserRepository _userRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<UserService> _logger;
public UserService(IUserRepository userRepository, IUnitOfWork unitOfWork, ILogger<UserService> logger)
{
_userRepository = userRepository;
_unitOfWork = unitOfWork;
_logger = logger;
}
public async Task<User> CreateUserAsync(string email, string name)
{
try
{
// Check if user already exists
if (await _userRepository.EmailExistsAsync(email))
{
throw new ExistsException($"User with email {email} already exists");
}
// Create and save user
var user = new User(email, name);
await _userRepository.AddAsync(user);
await _unitOfWork.SaveChangesAsync();
_logger.LogInformation("User created successfully: {Email}", email);
return user;
}
catch (RepoUpdateException ex)
{
_logger.LogError("Database update failed: {Message}", ex.Message);
throw;
}
}
}
๐ก๏ธ Exception Handling
The framework provides comprehensive exception handling with automatic EF Core exception translation. All database exceptions are automatically converted to domain exceptions with rich context information:
Exception Types
Exception | Description | Use Case |
---|---|---|
RepoUpdateException |
Database update failures | General database update operations |
RepoUpdateConcurrencyException |
Optimistic concurrency conflicts | Multiple users editing same data |
RepoTimeoutException |
Operation timeouts | Long-running queries |
RepoTransactionException |
Transaction failures | Commit/rollback issues |
ExistsException |
Entity already exists | Duplicate entity creation |
Retry Logic Example
public async Task<bool> UpdateUserWithRetryAsync(User user, int maxRetries = 3)
{
var retryCount = 0;
while (retryCount < maxRetries)
{
try
{
await _userRepository.UpdateAsync(user);
await _unitOfWork.SaveChangesAsync();
return true;
}
catch (RepoUpdateConcurrencyException ex)
{
retryCount++;
_logger.LogWarning("Concurrency conflict (attempt {Attempt}): {Message}",
retryCount, ex.Message);
if (retryCount >= maxRetries)
throw;
// Exponential backoff
await Task.Delay(TimeSpan.FromMilliseconds(100 * Math.Pow(2, retryCount - 1)));
}
catch (RepoTimeoutException ex)
{
retryCount++;
_logger.LogWarning("Timeout detected (attempt {Attempt}): {Message}",
retryCount, ex.Message);
if (retryCount >= maxRetries)
throw;
// Wait before retry
await Task.Delay(TimeSpan.FromMilliseconds(500));
}
}
return false;
}
๐ Transaction Management
Basic Transaction Usage
public async Task TransferDataAsync(int fromUserId, int toUserId, decimal amount)
{
ITransaction? transaction = null;
try
{
transaction = await _unitOfWork.BeginTransactionAsync();
// Perform multiple operations
var fromUser = await _userRepository.GetAsync(fromUserId);
var toUser = await _userRepository.GetAsync(toUserId);
fromUser.DebitAmount(amount);
toUser.CreditAmount(amount);
await _userRepository.UpdateAsync(fromUser);
await _userRepository.UpdateAsync(toUser);
await _unitOfWork.SaveChangesAsync();
await transaction.CommitAsync();
_logger.LogInformation("Transfer completed: {Amount} from {FromUser} to {ToUser}",
amount, fromUserId, toUserId);
}
catch (RepoTransactionException ex)
{
_logger.LogError("Transaction failed: {Operation} - {Message}",
ex.FailedOperation, ex.Message);
if (transaction != null)
{
await transaction.RollbackAsync();
}
throw;
}
finally
{
transaction?.Dispose();
}
}
๐๏ธ Architecture Overview
The framework follows Domain-Driven Design principles with clean architecture:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Application Layer โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ Application โ โ Domain โ โ
โ โ Services โ โ Services โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Domain Layer โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ Aggregate โ โ Domain โ โ
โ โ Roots โ โ Events โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ Entities โ โ Value โ โ
โ โ โ โ Objects โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Infrastructure Layer โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ EF Core โ โ Exception โ โ
โ โ Repositories โ โ Translation โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ Unit of Work โ โ Database โ โ
โ โ โ โ Context โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ง Advanced Configuration
Custom Repository Implementation
public class CustomUserRepository : EntityFrameworkCoreRepository<User, int>, IUserRepository
{
public CustomUserRepository(DbContext context) : base(context)
{
}
public async Task<User[]> GetActiveUsersAsync(CancellationToken cancellationToken = default)
{
try
{
return await DbSet
.Where(u => u.IsActive)
.OrderBy(u => u.Name)
.ToArrayAsync(cancellationToken);
}
catch (Exception ex)
{
// Automatic exception translation
var translatedEx = ExceptionTranslator.TranslateException(ex, "GetActiveUsers");
throw translatedEx;
}
}
}
Dependency Injection Setup
// Program.cs (ASP.NET Core)
var builder = WebApplication.CreateBuilder(args);
// Add Entity Framework
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Add DDD services
builder.Services.AddScoped<IUnitOfWork, EntityFrameworkCoreUnitOfWork<ApplicationDbContext>>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<UserService>();
var app = builder.Build();
Exception Monitoring
public class ExceptionMonitoringService
{
private readonly ILogger<ExceptionMonitoringService> _logger;
public void LogDomainException(DomainDrivenException ex)
{
_logger.LogError("Domain Exception: {ErrorCode} - {Message} | Severity: {Severity} | Timestamp: {Timestamp}",
ex.ErrorCode, ex.Message, ex.Severity, ex.Timestamp);
// Send alerts for critical exceptions
if (ex.Severity == ExceptionSeverity.Critical || ex.Severity == ExceptionSeverity.Fatal)
{
// Send notification to administrators
SendAlert(ex);
}
}
}
๐ Performance Considerations
- Minimal Overhead: Exception translation only occurs when exceptions are thrown
- Efficient Queries: Repository methods use optimized EF Core queries
- Connection Pooling: Leverage EF Core's built-in connection pooling
- Async Operations: All database operations are asynchronous
- Memory Efficient: Context information uses existing EF Core data structures
๐งช Testing
Unit Testing Repositories
[Test]
public async Task GetAsync_WithValidId_ReturnsUser()
{
// Arrange
var options = new DbContextOptionsBuilder<TestDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
await using var context = new TestDbContext(options);
var repository = new UserRepository(context);
var user = new User("test@example.com", "Test User");
await repository.AddAsync(user);
await context.SaveChangesAsync();
// Act
var result = await repository.GetAsync(user.Id);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Email, Is.EqualTo("test@example.com"));
}
Integration Testing with Exception Handling
[Test]
public async Task SaveChangesAsync_WithConcurrencyConflict_ThrowsConcurrencyException()
{
// Arrange
var user1 = await _repository.GetAsync(1);
var user2 = await _repository.GetAsync(1);
// Act & Assert
user1.UpdateName("New Name 1");
user2.UpdateName("New Name 2");
await _repository.UpdateAsync(user1);
await _unitOfWork.SaveChangesAsync(); // First update succeeds
await _repository.UpdateAsync(user2);
// Second update should throw concurrency exception
Assert.ThrowsAsync<RepoUpdateConcurrencyException>(
async () => await _unitOfWork.SaveChangesAsync());
}
๐ Migration Guide
From Raw EF Core
- Replace DbContext direct usage with Unit of Work pattern
- Implement repository interfaces for your aggregate roots
- Add exception handling around database operations
- Update dependency injection configuration
From Other DDD Frameworks
- Map existing entities to inherit from
AggregateRoot<TKey>
- Implement repository interfaces using provided base classes
- Replace existing exception handling with domain exceptions
- Update service layer to use new repository interfaces
๐ Additional Resources
๐ค Contributing
We welcome contributions! Please see our Contributing Guidelines for details.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
๐ Changelog
See CHANGELOG.md for a detailed history of changes.
โ Support
- ๐ Documentation: Check the README and inline documentation
- ๐ Issues: Report bugs on GitHub Issues
- ๐ฌ Discussions: Join discussions on GitHub Discussions
- ๐ง Email: Contact the maintainers for enterprise support
๐ Acknowledgments
- Inspired by Domain-Driven Design principles by Eric Evans
- Built on top of Entity Framework Core
- Thanks to all contributors and the .NET community
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
โญ Star this repository if you find it helpful!
๐ Watch for updates and new releases.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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. |
.NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.1 is compatible. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- DeepCloner (>= 0.10.4)
- Doulex.DomainDriven (>= 1.6.3)
- Newtonsoft.Json (>= 13.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.