如何将数据验证与我的简单域对象(PO​​CO)分开?

时间:2009-01-03 18:05:36

标签: language-agnostic separation-of-concerns solid-principles modular-design

这个问题与语言无关,但我是C#家伙所以我使用术语POCO来表示一个只能预先形成数据存储的对象,通常使用getter和setter字段。

我只是将我的领域模型重新设计成超级POCO,并且对于如何确保属性值在域中有意义而留下了一些问题。

例如,服务的EndDate不应超过服务所在合同的EndDate。但是,将检查放入Service.EndDate设置器似乎违反了SOLID,更不用说随着需要完成的验证数量的增加,我的POCO类将变得混乱。

我有一些解决方案(将在答案中发布),但它们有它们的缺点,我想知道解决这个难题的最佳方法是什么?

8 个答案:

答案 0 :(得分:6)

我认为你是从一个错误的假设开始的,即你应该拥有除了存储数据之外什么都不做的对象,并且除了访问器之外没有任何方法。拥有对象的重点是封装数据和行为。如果你有一个基本上是结构的东西,你封装了什么行为?

答案 1 :(得分:3)

我总是听到人们争论“验证”或“IsValid”方法。

就我个人而言,我认为这可能有用,但对于大多数DDD项目,您通常最终会这样做 具有多个允许的验证,具体取决于对象的特定状态。

所以我更喜欢“IsValidForNewContract”,“IsValidForTermination”或类似的,因为我相信大多数项目最终都会有每个类的多个这样的验证器/状态。这也意味着我没有接口,但我可以编写 read 的聚合验证器,很好地反映了我所声称的业务条件。

我确实相信这种情况下的通用解决方案经常把重点放在远离重要的地方 - 代码在做什么 - 在技术优雅方面获得非常小的收获(界面,代表或其他)。只是投票给我吧;)

答案 2 :(得分:3)

我的一位同事想出了一个很好的想法。我们从来没有想出一个伟大的名字,但我们称之为Inspector / Judge。

Inspector会查看一个对象并告诉您违反的所有规则。法官将决定该怎么做。这种分离让我们做了几件事。它让我们将所有规则放在一个地方(督察),但我们可以有多名法官,并根据情况选择法官。

使用多名裁判的一个例子围绕着说客户必须拥有地址的规则。这是一个标准的三层应用程序。在UI层中,Judge将生成UI可用于指示必须填写的字段的内容.UI Judge不会抛出异常。在服务层有另一名法官。如果在保存期间找到没有地址的客户,则会抛出异常。那时你真的要阻止事情继续进行。

我们还让法官更加严格,因为对象的状态发生了变化。这是一个保险申请,在报价过程中,政策被允许保存在一个不完整的状态。但是,一旦该政策准备好被激活,就必须设置很多东西。因此,服务方面的报价法官并不像激活法官那样严格。然而,检查员使用的规则仍然是相同的,所以即使你决定不做任何事情,你仍然可以告诉你哪些不完整。

答案 3 :(得分:2)

一种解决方案是让每个对象的DataAccessObject获取Validator列表。调用Save时,它会针对每个验证器执行检查:

public class ServiceEndDateValidator : IValidator<Service> {
  public void Check(Service s) {
    if(s.EndDate > s.Contract.EndDate)
      throw new InvalidOperationException();
  }
}

public class ServiceDao : IDao<Service> {
  IValidator<Service> _validators;
  public ServiceDao(IEnumerable<IValidator<Service>> validators) {_validators = validators;}
  public void Save(Service s) {
    foreach(var v in _validators)
      v.Check(service);
    // Go on to save
  }
}

好处,非常清楚SoC,缺点是我们不会在调用Save()之前得到检查。

答案 4 :(得分:2)

过去,我通常会将验证委托给自己的服务,例如ValidationService。这原则上仍然听到了DDD的哲学。

在内部,它将包含一组Validators和一组非常简单的公共方法,例如Validate(),它们可以返回错误对象的集合。

非常简单,在C#

中就是这样的
public class ValidationService<T>
{
  private IList<IValidator> _validators;

  public IList<Error> Validate(T objectToValidate)
  {
    foreach(IValidator validator in _validators)
    {
      yield return validator.Validate(objectToValidate);
    }
  }
}

验证器可以在默认构造函数中添加,也可以通过其他类(如ValidationServiceFactory)注入。

答案 5 :(得分:0)

我认为这可能是逻辑的最佳位置,实际上,但那只是我。您可以使用某种IsValid方法检查所有条件并返回true / false,可能是某种ErrorMessages集合,但这是一个不确定的主题,因为错误消息实际上不是域模型的一部分。我有点偏颇,因为我已经完成了与RoR的一些工作,而这正是它的模型所做的。

答案 6 :(得分:0)

另一种可能性是让我的每个类都实现

public interface Validatable<T> {
  public event Action<T> RequiresValidation;
}

让每个类的每个setter在设置之前引发事件(也许我可以通过属性实现这一点)。

优点是实时验证检查。但是代码更乱,目前还不清楚谁应该进行附加。

答案 7 :(得分:0)

这是另一种可能性。验证是通过Domain对象上的代理或装饰器完成的:

public class ServiceValidationProxy : Service {
  public override DateTime EndDate {
    get {return EndDate;}
    set {
      if(value > Contract.EndDate)
        throw new InvalidOperationexception();
      base.EndDate = value;
    }
  }
}

优势:即时验证。可以通过IoC轻松配置。

缺点:如果代理,验证属性必须是虚拟的,那么装饰器所有域模型必须是基于接口的。验证类最终会有点重量级 - 代理必须继承类,而装饰器必须实现所有方法。命名和组织可能会让人感到困惑。