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

时间:2022-05-24 11:40:11

This question is language agnostic but I am a C# guy so I use the term POCO to mean an object that only preforms data storage, usually using getter and setter fields.

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

I just reworked my Domain Model to be super-duper POCO and am left with a couple of concerns regarding how to ensure that the property values make sense witin the domain.

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

For example, the EndDate of a Service should not exceed the EndDate of the Contract that Service is under. However, it seems like a violation of SOLID to put the check into the Service.EndDate setter, not to mention that as the number of validations that need to be done grows my POCO classes will become cluttered.

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

I have some solutions (will post in answers), but they have their disadvantages and am wondering what are some favorite approaches to solving this dilemma?

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

8 个解决方案

#1


I think you're starting off with a bad assumption, ie, that you should have objects that do nothing but store data, and have no methods but accessors. The whole point of having objects is to encapsulate data and behaviors. If you have a thing that's just, basically, a struct, what behaviors are you encapsulating?

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

#2


I always hear people argument for a "Validate" or "IsValid" method.

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

Personally I think this may work, but with most DDD projects you usually end up with multiple validations that are allowable depending on the specific state of the object.

我个人认为这可能有用,但对于大多数DDD项目,您通常会根据对象的特定状态获得允许的多个验证。

So I prefer "IsValidForNewContract", "IsValidForTermination" or similar, because I believe most projects end up with multiple such validators/states per class. That also means I get no interface, but I can write aggregated validators that read very well reflect the business conditions I am asserting.

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

I really do believe the generic solutions in this case very often take focus away from what's important - what the code is doing - for a very minor gain in technical elegance (the interface, delegate or whatever). Just vote me down for it ;)

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

#3


A colleague of mine came up with an idea that worked out pretty well. We never came up with a great name for it but we called it Inspector/Judge.

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

The Inspector would look at an object and tell you all of the rules it violated. The Judge would decide what to do about it. This separation let us do a couple of things. It let us put all the rules in one place (Inspector) but we could have multiple Judges and choose the Judge by the context.

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

One example of the use of multiple Judges revolves around the rule that said a Customer must have an Address. This was a standard three tier app. In the UI tier the Judge would produce something that the UI could use to indicate the fields that had to be filled in. The UI Judge did not throw exceptions. In the service layer there was another Judge. If it found a Customer without an Address during Save it would throw an exception. At that point you really have to stop things from proceeding.

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

We also had Judges that were more strict as the state of the objects changed. It was an insurance application and during the Quoting process a Policy was allowed to be saved in an incomplete state. But once that Policy was ready to be made Active a lot of things had to be set. So the Quoting Judge on the service side was not as strict as the Activation Judge. Yet the rules used in the Inspector were still the same so you could still tell what wasn't complete even if you decided not to do anything about it.

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

#4


One solution is to have each object's DataAccessObject take a list of Validators. When Save is called it preforms a check against each validator:

一种解决方案是让每个对象的DataAccessObject获取Validators列表。调用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
  }
}

The benefit, is very clear SoC, the disadvantage is that we don't get the check until Save() is called.

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

#5


In the past I have usually delegated validation to a service unto its own, such as a ValidationService. This in principle still ad hears to the philosophy of DDD.

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

Internally this would contain a collection of Validators and a very simple set of public methods such as Validate() which could return a collection of error object.

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

Very simply, something like this in C#

非常简单,在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);
    }
  }
}

Validators could either be added within a default constructor or injected via some other class such as a ValidationServiceFactory.

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

#6


I think that would probably be the best place for the logic, actually, but that's just me. You could have some kind of IsValid method that checks all of the conditions too and returns true/false, maybe some kind of ErrorMessages collection but that's an iffy topic since the error messages aren't really a part of the Domain Model. I'm a little biased as I've done some work with RoR and that's essentially what its models do.

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

#7


Another possibility is to have each of my classes implement

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

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

And have each setter for each class raise the event before setting (maybe I could achieve this via attributes).

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

The advantage is real-time validation checking. But messier code and it is unclear who should be doing the attaching.

优点是实时验证检查。但是代码更乱,并且不清楚谁应该做附加。

#8


Here's another possibility. Validation is done through a proxy or decorator on the Domain object:

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

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

Advantage: Instant validation. Can easily be configured via an IoC.

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

Disadvantage: If a proxy, validated properties must be virtual, if a decorator all domain models must be interface-based. The validation classes will end up a bit heavyweight - proxys have to inherit the class and decorators have to implement all the methods. Naming and organization might get confusing.

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

#1


I think you're starting off with a bad assumption, ie, that you should have objects that do nothing but store data, and have no methods but accessors. The whole point of having objects is to encapsulate data and behaviors. If you have a thing that's just, basically, a struct, what behaviors are you encapsulating?

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

#2


I always hear people argument for a "Validate" or "IsValid" method.

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

Personally I think this may work, but with most DDD projects you usually end up with multiple validations that are allowable depending on the specific state of the object.

我个人认为这可能有用,但对于大多数DDD项目,您通常会根据对象的特定状态获得允许的多个验证。

So I prefer "IsValidForNewContract", "IsValidForTermination" or similar, because I believe most projects end up with multiple such validators/states per class. That also means I get no interface, but I can write aggregated validators that read very well reflect the business conditions I am asserting.

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

I really do believe the generic solutions in this case very often take focus away from what's important - what the code is doing - for a very minor gain in technical elegance (the interface, delegate or whatever). Just vote me down for it ;)

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

#3


A colleague of mine came up with an idea that worked out pretty well. We never came up with a great name for it but we called it Inspector/Judge.

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

The Inspector would look at an object and tell you all of the rules it violated. The Judge would decide what to do about it. This separation let us do a couple of things. It let us put all the rules in one place (Inspector) but we could have multiple Judges and choose the Judge by the context.

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

One example of the use of multiple Judges revolves around the rule that said a Customer must have an Address. This was a standard three tier app. In the UI tier the Judge would produce something that the UI could use to indicate the fields that had to be filled in. The UI Judge did not throw exceptions. In the service layer there was another Judge. If it found a Customer without an Address during Save it would throw an exception. At that point you really have to stop things from proceeding.

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

We also had Judges that were more strict as the state of the objects changed. It was an insurance application and during the Quoting process a Policy was allowed to be saved in an incomplete state. But once that Policy was ready to be made Active a lot of things had to be set. So the Quoting Judge on the service side was not as strict as the Activation Judge. Yet the rules used in the Inspector were still the same so you could still tell what wasn't complete even if you decided not to do anything about it.

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

#4


One solution is to have each object's DataAccessObject take a list of Validators. When Save is called it preforms a check against each validator:

一种解决方案是让每个对象的DataAccessObject获取Validators列表。调用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
  }
}

The benefit, is very clear SoC, the disadvantage is that we don't get the check until Save() is called.

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

#5


In the past I have usually delegated validation to a service unto its own, such as a ValidationService. This in principle still ad hears to the philosophy of DDD.

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

Internally this would contain a collection of Validators and a very simple set of public methods such as Validate() which could return a collection of error object.

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

Very simply, something like this in C#

非常简单,在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);
    }
  }
}

Validators could either be added within a default constructor or injected via some other class such as a ValidationServiceFactory.

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

#6


I think that would probably be the best place for the logic, actually, but that's just me. You could have some kind of IsValid method that checks all of the conditions too and returns true/false, maybe some kind of ErrorMessages collection but that's an iffy topic since the error messages aren't really a part of the Domain Model. I'm a little biased as I've done some work with RoR and that's essentially what its models do.

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

#7


Another possibility is to have each of my classes implement

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

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

And have each setter for each class raise the event before setting (maybe I could achieve this via attributes).

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

The advantage is real-time validation checking. But messier code and it is unclear who should be doing the attaching.

优点是实时验证检查。但是代码更乱,并且不清楚谁应该做附加。

#8


Here's another possibility. Validation is done through a proxy or decorator on the Domain object:

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

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

Advantage: Instant validation. Can easily be configured via an IoC.

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

Disadvantage: If a proxy, validated properties must be virtual, if a decorator all domain models must be interface-based. The validation classes will end up a bit heavyweight - proxys have to inherit the class and decorators have to implement all the methods. Naming and organization might get confusing.

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