4

我来这里是想听听您对我们迄今为止所采取的验证方法的看法。我们仍处于开发过程的早期,所以我们仍然可以改变它。验证对于这个应用程序和我们的客户来说非常重要,所以我们需要找到最优化的方法。让我描述一下我们到目前为止所做的事情......

我们正在构建这个应用程序,它将被不同的客户使用。我们不控制所有客户端,因此对所有层的验证都有严格的要求。我们确实控制了一些客户端应用程序,其中一个是大约 100 个用户使用的 WPF 应用程序。在此应用程序中,工作流程如下:

|                     Client                   |                                 Backend Service                             |
ViewModel -> ClientRepository -> ServiceClient -> Service (WCF) -> ApplicationService -> DomainModel -> Repository -> Database

我们将以下内容视为执行验证的候选者。

  • 客户端:ViewModel 验证,用于支持带有必填字段、长度等的 UI。
  • 后端:服务请求 DTO 验证,因为我们不能依赖客户端始终提供 100% 有效值。
  • 后端:域模型实体验证。我们不希望我们的实体最终处于无效状态,因此每个实体在执行操作时都会包含不同的检查。
  • 后端:数据库验证,例如失败的约束(FK、唯一性、长度等)

客户端 ViewModel 验证非常明显,对于我们自己的客户端,应在到达服务之前纠正尽可能多的错误。但不能代表其他使用我们服务的应用程序,应该假设最坏的情况。

服务请求 DTO 应主要针对第三方应用程序和我们自己客户端中的错误进行验证。确保请求正确,可以防止稍后在处理请求时弹出错误,从而保证更有效的服务。与 ViewModel 验证一样,这归结为不同属性的必填字段、长度和格式(例如电子邮件)。

领域模型中的实体本身应该确保它们始终具有完全有效的属性/属性,我们就是这样实现的,以Customer实体为例。

public class Customer : Entity
{
    private Customer() : base() { }

    public Customer(Guid id, string givenName, string surname)
        : this(id, givenName, null, surname) { }

    public Customer(Guid id, string givenName, string middleName, string surname)
        : base(id)
    {
        if (string.IsNullOrWhiteSpace(givenName))
            throw new ArgumentException(GenericErrors.StringNullOrEmpty, "givenName");
        if (string.IsNullOrWhiteSpace(surname))
            throw new ArgumentException(GenericErrors.StringNullOrEmpty, "surname");

        GivenName = givenName.Trim();
        Surname = surname.Trim();

        if (!string.IsNullOrWhiteSpace(middleName))
            MiddleName = middleName.Trim();
    }
}

现在,虽然这确保了属性是有效的,但一个CustomerValidator类作为一个整体验证了 Customer 类,确保它处于有效状态并且不仅具有有效的属性。这CustomerValidator是使用 FluentValidation 框架实现的。它在将客户对象提交到数据库之前在应用程序服务中调用。

到目前为止,您如何看待我们的方法?

我有点担心的是,到处都在使用异常。例如ArgumentException上面的示例,但InvalidOperationException在调用对象当前状态中不允许的某些方法的情况下也是如此。

希望这些异常很少被抛出,因为服务请求 DTO 已经过验证,因此我认为它可能没问题?例如,当验证服务请求 DTO 时,绝不应引发参数异常,除非验证中的某处出现错误。因此,您可以说域模型中的这些参数检查充当了额外的安全层。InvalidOperationException另一方面,如果客户端调用了一个服务方法,该服务方法调用了在其当前状态下不可用的 Customer 对象上的方法(因此它应该失败),则可以引发。

你怎么看?如果听起来一切正常,当出现故障时,如何通过 WCF 适当地通知用户?无论是ArgumentException,InvalidOperationException还是包含错误列表的异常(由 ApplicationService 在使用 CustomerValidator 类验证客户对象后抛出)。我是否应该以某种方式捕获所有这些异常并将它们转换为 WCF 抛出的一些一般错误异常,从而客户端可以对其做出反应并通知用户发生了什么?

我很想听听您对我们方法的看法。我们正在开始构建这个相当大的应用程序,我们真的想找到一种执行验证的好方法。在我们的应用程序中有一些非常关键的部分,其中数据的正确性非常重要,因此验证很重要!

4

2 回答 2

2

我自己的观点是域的一致性应该由域来处理。所以不需要CustomerValidator各种。

至于例外,您应该考虑到,ArgumentNullException它们应该是普遍存在的语言的术语(有关更深入的解释,请参见http://epic.tesio.it/2013/03/04/exceptions-are-terms-ot-the -ubiquitous-language.html)。

顺便说一句,即使您的所有 DTO 之前都已经过验证,您也不应该从域中删除正确的验证。业务不变量是它自己的责任。

至于性能:异常是有计算成本的,但在我目前看到的大多数 DDD 场景中,它们都不是问题。特别是当命令来自人类时,它们不是问题。

编辑
验证始终是域的责任。取一个ISIN值对象:由它的构造函数通过抛出适当的异常来确保它自己的不变量。在编码良好的域中,您不能保存无效对象的实例。因此,您不需要任何验证器来累积错误。

同样,工厂可以确保业务不变量当且仅当它们是获取实例的唯一方式。技术不变量,例如 db column lenght,应该不在这个领域,因此工厂可能是他们的好位置。这也将具有启用异常链接的优势:SqlExceptions 对客户来说没有太多的表现力。

对于表达性异常,客户端只需尝试/捕获他们可以处理的异常(并记住向用户呈现异常是一种处理方式)。

于 2013-03-23T21:56:17.890 回答
0

数据进入您的系统的某个时刻。这可能是动作参数(视图模型)或服务层中的参数的形式。此时您必须始终进行超验证(使所有内容都可以为空,然后禁止为空,检查整数上的负数等)并保证在这些入口点一切都是 100% 正确的。然后系统的其余部分不必担心它,除非验证是针对特定的边缘情况。客户端验证很好,但你绝不能完全依赖它。验证有时可能会断开连接。此外,没有保证调用您的操作的客户就是您认为的客户(例如,我们都更改了 url 中的查询参数以查看会发生什么)。

我对您发布的代码的问题是,在这一点上,您的域中的数据可能有效也可能无效。如果您总是在流程的外部边界执行验证,您永远不必担心。此外,您永远不会想知道“我将验证放在哪里?”

于 2013-03-25T13:53:48.193 回答