我正在使用FluentValidation来验证我的服务操作。我的代码如下:
using FluentValidation;
IUserService
{
void Add(User user);
}
UserService : IUserService
{
public void Add(User user)
{
new UserValidator().ValidateAndThrow(user);
userRepository.Save(user);
}
}
UserValidator实现了FluentValidation.AbstractValidator。
DDD表示域层必须与技术无关。
我正在做的是使用验证框架而不是自定义例外。
将验证框架放在域层中是一个坏主意吗?
答案 0 :(得分:39)
好吧,即使您通过声明IUserValidator
界面来屏蔽域名,我也会发现您的设计存在一些问题。
起初,似乎这会导致与存储库和其他基础设施问题相同的抽象策略,但我认为存在巨大差异。
使用repository.save(...)
时,您实际上并不关心域视图中的所有实现,因为如何保留事物不是域关注。
但是,不变执法是一个域名问题,您不应该深入了解基础架构详细信息(UserValidtor
现在可以这样看),以了解它们包含的内容和内容。基本上,如果你沿着这条路走下去,你最终会做什么,因为规则将在框架术语中表达,并且将存在于域之外。
为什么会住在外面?
domain -> IUserRepository
infrastructure -> HibernateUserRepository
domain -> IUserValidator
infrastructure -> FluentUserValidator
也许你的设计存在一个更基本的问题,如果你坚持这个学校,你甚至不会问这个问题:永远有效的实体。
从这个角度来看,不变执行是域实体本身的责任,因此甚至不能存在而不是有效的。因此,不变规则简单地表示为契约,并且当违反这些规则时抛出异常。
这背后的原因是很多错误来自于对象处于他们本不应该存在的状态。为了揭示一个我从Greg Young那里读到的例子:
我们建议我们现在有一个
SendUserCreationEmailService
UserProfile
...我们如何才能在Name
的服务中合理化 不是null
?我们再检查一下吗?或者更可能......你不是 懒得检查,并希望最好的"你希望有人打扰你 在发送给您之前验证它。当然使用TDD之一 我们应该写的第一个测试是,如果我发送客户 一个null
名称,它应该引发错误。但是一旦我们开始写作 这些测试一遍又一遍地我们意识到......等等我们 永远不允许名称变为null我们不会进行所有这些测试" - Greg Young评论http://jeffreypalermo.com/blog/the-fallacy-of-the-always-valid-entity/
现在不要误解我,显然你不能以这种方式强制执行所有验证规则,因为某些规则特定于禁止该方法的某些业务操作(例如保存实体的草稿副本),但这些规则不是&和#39; t被视为与不变执行相同的方式,这是适用于每个场景的规则(例如,客户必须具有名称)。
如果我们现在查看您的代码并尝试应用始终有效的方法,我们会清楚地看到UserValidator
对象没有它的位置。
UserService : IUserService
{
public void Add(User user)
{
//We couldn't even make it that far with an invalid User
new UserValidator().ValidateAndThrow(user);
userRepository.Save(user);
}
}
因此,此时域中没有FluentValidation的位置。如果您仍然不相信,那么问问自己如何整合价值对象?每次实例化时,您是否会UsernameValidator
验证Username
值对象?显然,这没有任何意义,使用价值对象很难与非常有效的方法相结合。
这实际上是我努力奋斗的事情,我一直在问自己一段时间(而且我还不完全相信我会说些什么)。
基本上,我所了解的是,收集和返回错误不是域名的工作,而是UI问题。如果无效数据使其进入域名,则只会引发您的注意。
因此,像FluentValidation这样的框架将在UI中找到它们自然的家,并且将验证视图模型而不是域实体。
我知道,似乎很难接受会有一定程度的重复,但这主要是因为你可能是像我一样处理UI和域的全栈开发人员实际上那些可以而且应该可能被视为完全不同的项目。此外,就像视图模型和域模型一样,视图模型验证和域验证可能类似,但用途不同。
另外,如果您仍然担心自己是干的话,有人曾告诉我,代码重用也是"耦合"我认为这个事实在这里特别重要。
我不会在这里重新解释这些,但有各种方法可以处理域中的延迟验证,例如规范模式和Ward Cunningham在其Checks模式语言中描述的Deferred Validation方法。如果你有Vaughn Vernon的“实施领域驱动设计”一书,你也可以阅读第208-215页。
验证是一个非常困难的主题,并且证据是,截至今天,人们仍然不同意应该如何完成。有很多因素,但最终你想要的是一个实用,可维护和富有表现力的解决方案。您不能总是纯粹主义者,并且必须接受一些规则将被破坏的事实(例如,您可能必须在实体中泄漏一些不显眼的持久性详细信息才能使用您选择的ORM。)
因此,如果您认为您可以接受一些FluentValidation详细信息进入您的域并且它更实用的事实,那么我无法确定它是否会做更多从长远来看,弊大于利,但我不会。
答案 1 :(得分:1)
如果我理解正确的话,我认为这样做没有任何问题,只要它被抽象为基础设施问题,就像你的回购提取持久性技术一样。
作为一个例子,我为我的项目创建了一个IObjectValidator,它按对象类型返回验证器,以及它的静态实现,因此我没有与技术本身耦合。
public interface IObjectValidator
{
void Validate<T>(T instance, params string[] ruleSet);
Task ValidateAsync<T>(T instance, params string[] ruleSet);
}
然后我用Fluent Validation实现了它,就像这样:
public class FluentValidationObjectValidator : IObjectValidator
{
private readonly IDependencyResolver dependencyResolver;
public FluentValidationObjectValidator(IDependencyResolver dependencyResolver)
{
this.dependencyResolver = dependencyResolver;
}
public void Validate<T>(T instance, params string[] ruleSet)
{
var validator = this.dependencyResolver
.Resolve<IValidator<T>>();
var result = ruleSet.Length == 0
? validator.Validate(instance)
: validator.Validate(instance, ruleSet: ruleSet.Join());
if(!result.IsValid)
throw new ValidationException(MapValidationFailures(result.Errors));
}
public async Task ValidateAsync<T>(T instance, params string[] ruleSet)
{
var validator = this.dependencyResolver
.Resolve<IValidator<T>>();
var result = ruleSet.Length == 0
? await validator.ValidateAsync(instance)
: await validator.ValidateAsync(instance, ruleSet: ruleSet.Join());
if(!result.IsValid)
throw new ValidationException(MapValidationFailures(result.Errors));
}
private static List<ValidationFailure> MapValidationFailures(IEnumerable<FluentValidationResults.ValidationFailure> failures)
{
return failures
.Select(failure =>
new ValidationFailure(
failure.PropertyName,
failure.ErrorMessage,
failure.AttemptedValue,
failure.CustomState))
.ToList();
}
}
请注意,我还使用IDependencyResolver抽象了我的IOC容器,以便我可以使用我想要的任何实现。 (目前使用Autofac)。
所以这是autofac的一些奖励代码;)
public class FluentValidationModule : Module
{
protected override void Load(ContainerBuilder builder)
{
// registers type validators
builder.RegisterGenerics(typeof(IValidator<>));
// registers the Object Validator and configures the Ambient Singleton container
builder
.Register(context =>
SystemValidator.SetFactory(() => new FluentValidationObjectValidator(context.Resolve<IDependencyResolver>())))
.As<IObjectValidator>()
.InstancePerLifetimeScope()
.AutoActivate();
}
}
代码可能会丢失我的一些助手和扩展程序,但我相信这对你来说已经足够了。
我希望我有所帮助:)
修改强>
由于一些编程人员不喜欢使用&#34;服务定位器反模式&#34;,这里有一个非常简单的示例,说明如何删除它仍然很高兴:)
代码提供了一个字典属性,应该按类型填充所有验证器。
public class SimpleFluentValidationObjectValidator : IObjectValidator
{
public SimpleFluentValidationObjectValidator()
{
this.Validators = new Dictionary<Type, IValidator>();
}
public Dictionary<Type, IValidator> Validators { get; private set; }
public void Validate<T>(T instance, params string[] ruleSet)
{
var validator = this.Validators[typeof(T)];
if(ruleSet.Length > 0) // no ruleset option for this example
throw new NotImplementedException();
var result = validator.Validate(instance);
if(!result.IsValid)
throw new ValidationException(MapValidationFailures(result.Errors));
}
public Task ValidateAsync<T>(T instance, params string[] ruleSet)
{
throw new NotImplementedException();
}
private static List<ValidationFailure> MapValidationFailures(IEnumerable<FluentValidationResults.ValidationFailure> failures)
{
return failures
.Select(failure =>
new ValidationFailure(
failure.PropertyName,
failure.ErrorMessage,
failure.AttemptedValue,
failure.CustomState))
.ToList();
}
}
答案 2 :(得分:1)
对您的问题的答案取决于您希望将哪种验证放入验证器类。验证可以是域模型的一部分,在您的情况下,您已使用FluentValidation实现它,我没有看到任何问题。域模型的关键 - 您可以在任何地方使用域模型,例如,如果您的项目包含Web部件,api,与其他子系统集成。每个模块都引用您的域模型,并且对所有模块都一样。