好的,这是翻译后的技术文章,保留了原始代码块、格式和C#语言标识,并添加了一个吸引人的标题:
领域验证(Domain Validation)是在 .NET 10 中使用整洁架构(Clean Architecture)和领域驱动设计(Domain-Driven Design, DDD)原则构建健壮、可维护应用程序的基石。它确保业务规则和领域不变条件(invariants)得到一致地强制执行,同时保持清晰的关注点分离(separation of concerns),并防止无效状态破坏您的领域模型。
理解领域验证基础 领域验证与输入验证(input validation)有着根本性的不同。输入验证确保数据在应用程序边界处满足基本格式要求,而领域验证则强制执行定义领域对象有效性的业务规则和不变条件。在 DDD 中,领域实体(domain entities)应该始终是有效的实体——绝不应存在实体可以处于无效状态的情况。
“始终有效的领域模型”(Always-Valid Domain Model)原则指出,领域对象应该保护自己,避免变成无效状态。这种方法提供了几个关键优势:
两种主要的验证方法 1. 基于异常的验证(Exception-Based Validation) 传统方法使用异常来指示验证失败:
public sealed class Email : ValueObject
{
private static readonly Regex EmailRegex = new(
@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public string Value { get; }
private Email(string value)
{
Value = value;
}
public static Email Create(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new DomainException("Email cannot be empty");
if (value.Length > 255)
throw new DomainException("Email cannot exceed 255 characters");
if (!EmailRegex.IsMatch(value))
throw new DomainException("Invalid email format");
return new Email(value.ToLowerInvariant());
}
}
优势:
劣势:
2. 结果模式验证(Result Pattern Validation) 结果模式(Result pattern)提供了一种函数式的错误处理方法:
public sealed class Result<T>
{
private readonly T? _value;
private readonly Error? _error;
private Result(T value)
{
_value = value;
_error = null;
IsSuccess = true;
}
private Result(Error error)
{
_value = default;
_error = error;
IsSuccess = false;
}
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public T Value => IsSuccess
? _value!
: throw new InvalidOperationException("Cannot access value of failed result");
public Error Error => IsFailure
? _error!
: throw new InvalidOperationException("Cannot access error of successful result");
public static Result<T> Success(T value) => new(value);
public static Result<T> Failure(Error error) => new(error);
}
优势:
劣势:
Result
对象用于保护不变条件的守卫子句(Guard Clauses) 守卫子句提供了一种优雅的方式来强制执行验证规则,同时保持代码的整洁和可读性:
public static class Guard
{
public static void NotNull<T>(T value,
[CallerArgumentExpression(nameof(value))] string? paramName = null)
{
if (value is null)
throw new ArgumentNullException(paramName);
}
public static void NotEmpty(string value,
[CallerArgumentExpression(nameof(value))] string? paramName = null)
{
if (string.IsNullOrWhiteSpace(value))
throw new DomainException($"{paramName} cannot be empty");
}
public static void GreaterThan<T>(T value, T minimum,
[CallerArgumentExpression(nameof(value))] string? paramName = null)
where T : IComparable<T>
{
if (value.CompareTo(minimum) <= 0)
throw new DomainException($"{paramName} must be greater than {minimum}");
}
}
在领域实体中的用法:
public sealed class Product : Entity<ProductId>
{
public string Name { get; private set; }
public Money Price { get; private set; }
public int StockQuantity { get; private set; }
public Product(string name, Money price, int stockQuantity)
: base(new ProductId(Guid.NewGuid()))
{
Guard.NotEmpty(name, nameof(name));
Guard.NotNull(price, nameof(price));
Guard.GreaterThan(stockQuantity, -1, nameof(stockQuantity));
Name = name;
Price = price;
StockQuantity = stockQuantity;
}
}
领域错误目录(Domain Error Catalogs) 创建集中化的错误目录以提高可维护性:
public static class CustomerErrors
{
public static readonly Error NameRequired = new("Customer.NameRequired", "Customer name is required");
public static readonly Error NameTooLong = new("Customer.NameTooLong", "Customer name cannot exceed 100 characters");
public static readonly Error EmailRequired = new("Customer.EmailRequired", "Customer email is required");
public static readonly Error EmailInvalid = new("Customer.EmailInvalid", "Customer email format is invalid");
public static readonly Error NotFound = new("Customer.NotFound", "Customer not found");
}
public sealed record Error(string Code, string Message); // 错误记录类型
聚合验证与不变条件(Aggregate Validation and Invariants) 聚合(Aggregates)充当一致性边界(consistency boundaries),必须强制执行其内部实体之间的不变条件:
public sealed class Order : AggregateRoot<OrderId>
{
private readonly List<OrderItem> _items = new();
public CustomerId CustomerId { get; private set; }
public Money TotalAmount { get; private set; }
public OrderStatus Status { get; private set; }
public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
public static Result<Order> Create(CustomerId customerId, List<OrderItem> items)
{
// 业务规则:订单必须至少包含一个项目
if (!items.Any())
return Result<Order>.Failure(OrderErrors.EmptyOrder);
// 业务规则:订单金额不能超过最大值
var totalAmount = items.Sum(item => item.Price.Amount * item.Quantity);
if (totalAmount > 10000)
return Result<Order>.Failure(OrderErrors.ExceedsMaximumValue);
var order = new Order(customerId, new Money(totalAmount, "USD"));
foreach (var item in items)
{
order._items.Add(item);
}
return Result<Order>.Success(order);
}
}
与 .NET 10 中 FluentValidation 的集成 虽然领域验证应位于领域层(domain layer),但 FluentValidation 在应用层(application layer)对其进行了补充:
public sealed class CreateCustomerCommandValidator : AbstractValidator<CreateCustomerCommand>
{
public CreateCustomerCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("Customer name is required")
.MaximumLength(100)
.WithMessage("Customer name cannot exceed 100 characters");
RuleFor(x => x.Email)
.NotEmpty()
.WithMessage("Customer email is required")
.EmailAddress()
.WithMessage("Customer email format is invalid");
}
}
结合两种方法的应用层处理程序:
public sealed class CreateCustomerCommandHandler : IRequestHandler<CreateCustomerCommand, Result<CustomerId>>
{
private readonly ICustomerRepository _customerRepository;
private readonly IUnitOfWork _unitOfWork;
public async Task<Result<CustomerId>> Handle(CreateCustomerCommand request, CancellationToken cancellationToken)
{
// 通过工厂方法进行领域验证
var customerResult = Customer.Create(request.Name, request.Email);
if (customerResult.IsFailure)
return Result<CustomerId>.Failure(customerResult.Error);
_customerRepository.Add(customerResult.Value);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return Result<CustomerId>.Success(customerResult.Value.Id);
}
}
领域验证的最佳实践 选择正确的验证策略 在以下情况下使用异常:
在以下情况下使用结果模式:
正确分层验证
使验证显式化 使用业务利益相关者可以理解的清晰、描述性的错误消息和代码。避免层之间的验证重复——依靠领域对象来维护其自身的有效性。
.NET 10 的特定增强功能 .NET 10 带来了几项与领域验证相关的改进:
CountBy
和 AggregateBy
方法简化了验证聚合在 .NET 10 中,结合整洁架构和 DDD 的领域验证为构建可维护、业务导向的应用程序提供了坚实的基础。通过在领域层使用守卫子句和结果模式等适当模式实施验证,同时保持清晰的关注点分离,您可以创建既技术上合理又与业务需求保持一致的系统。关键是为您的特定用例选择正确的验证策略,并确保业务规则在您的领域模型中得到一致的强制执行。