视图模型验证与领域模型验证

23

如果在何时进行客户端验证时需要进行域级验证?

我使用ASP.NET MVC来开发我的Web应用程序。我喜欢区分我的领域模型和视图模型。我的领域模型包含来自数据库的数据,而我的视图模型包含视图/页面上的数据。

假设我正在处理客户数据。

我将在我的数据库中拥有一个名为“Customers”的表格。

我将有一个客户类,它可能看起来像这样:

public class Customer
{
     public int Id { get; set; }

     public string FirstName { get; set; }

     public string LastName { get; set; }

     public DateTime DateOfBirth { get; set; }
}

我将创建一个客户端视图模型来表示我在视图上拥有的数据:

[Validator(typeof(CustomerCreateViewModelValidator))]
public class CustomerCreateViewModel
{
     public string FirstName { get; set; }

     public string LastName { get; set; }

     public DateTime DateOfBirth { get; set; }
}

我将创建一个视图来接受我的 CustomerCreateViewModel,并将我的输入字段绑定到我的视图模型:

@model MyProject.ViewModels.Customers.CustomerCreateViewModel

@using (Html.BeginForm())
{
     <table>
          <tr>
               <td>
                    @Html.TextBoxFor(x => x.FirstName)
                    @Html.ValidationMessageFor(x => x.FirstName)
               </td>
          </tr>
          <tr>
               <td>
                    @Html.TextBoxFor(x => x.LastName)
                    @Html.ValidationMessageFor(x => x.LastName)
               </td>
          </tr>
     </table>

     <button id="SaveButton" type="submit">Save</button>
}

如您所见,我有一个包含验证规则的CustomerCreateViewModelValidator。用户在文本框中输入一些数据后,将单击提交按钮。如果某些字段为空,则验证失败。如果输入了所有必填字段,则验证成功。然后我将从视图模型映射数据到域模型,如下所示:

Customer customer = Mapper.Map<Customer>(viewModel);

我使用这个顾客领域模型并将其传递到我的仓库层,它将数据添加到我的表中。

在什么时候需要对领域模型进行验证?我在我的视图模型上进行所有的验证。我可以在将数据添加到数据库之前在我的领域模型中验证数据,但是考虑到已经在视图模型上进行了验证,这样做不就是在客户端复制相同的验证吗?

有人能解释一下这个验证问题吗?


你在层之间有单独的验证规则吗?我的意思是,UI中的某些内容是否可能被视为有效但在域中被认为无效? - Simon Whitehead
目前两者应该是相同的。我正在概括验证,不仅仅是针对我的项目。 - Brendan Vogt
1
我认为DDD会倾向于在每个领域对象上拥有一个Validate()实例方法,用于验证自身。不过我离DDD专家还很远。对于这个有趣的问题点赞。 - Simon Whitehead
4个回答

16

始终要在两个级别上进行验证。

您需要验证视图模型,因为如果用户做错了什么,您希望尽快轻松地向其反馈。您也不希望在模型无效时打扰其他领域逻辑。

但是,一旦验证了视图模型,您还想验证领域中的所有内容是否正常。对于简单模型,这些检查可能相同,因此看起来像重复逻辑,但是一旦应用程序增长,您可能会有多个用户界面,或者许多不同的应用程序使用相同的领域模型,检查领域变得非常重要。

例如,如果您的应用程序增长,以至于您最终提供API让客户直接以编程方式与应用程序交互,则验证领域模型成为必要,因为您无法保证所使用的用户界面已将数据验证到所需的标准(甚至根本未对其进行验证)。可以说,接收到的API数据应该像验证视图模型一样进行验证,这可能是个好主意,因为这实现了与验证视图模型相同的目标。但是,无论通过哪种方式进入(从UI还是API),都希望始终保证数据有效,因此在中心位置定义它是理想的。

两个验证级别的目标也不同。我期望视图模型验证会告诉我所有问题(例如缺少名字,姓氏太长,DoB不是日期)。但是,我认为领域逻辑在第一个错误时失败并仅报告该错误是可以的。同样,对于简单模型,可能有可能收集所有错误并将它们全部报告回来,但是随着应用程序变得越来越复杂,预测所有错误变得越来越困难,特别是如果逻辑将根据数据而改变。但是,只要通过了好的数据,那应该就没问题了!


2
我曾经陷入同样的陷阱,认为我在复制逻辑。我没有意识到验证的目的是不同的,因此DRY原则并没有真正被违反。我想补充一点的是,视图模型更关注验证结果的呈现,而模型定义了验证规则。我看到的一个避免“重复逻辑”的技巧是,让视图模型简单地调用模型层中的验证逻辑。Mark Seemann在他的博客文章中这样做。 - Nicholas Miller

8
作为一般规则,我认为域模型是最重要的代码,因此管理其状态是神圣的。因此,我永远不会假设域模型处于有效状态,仅仅因为它被呈现层操作,而呈现层应该强制执行有效性。这意味着您的域层与呈现层紧密耦合。
最好从域模型向外思考(onion architecture)。所有这些背后的推理是,域模型最不可能随时间改变,并且作为应用程序的核心,隔离层之间的缺陷。
因此,从强制执行自己有效性的域模型开始,您将面临重复验证代码的问题。有一些方法可以避免这种情况。例如,您的视图模型可以尝试创建域对象,并将引发的任何异常转换为验证失败。验证器也可以被提取和重用。根据您的用例,您必须看到哪种方法最适合您。只需注意保持简单即可。也许,如果您的用例不太复杂,最可维护的方法可能是简单地复制验证。请记住,去重会增加复杂性。
我曾经看过一些代码库,其中只有领域层处理验证,也有一些代码库在领域层和表现层都处理验证。在这种情况下,我更倾向于简单地复制验证逻辑,因为我已经看到了将领域验证错误有意义地映射到上下文用户界面的难度。

对于领域模型验证,如何将失败验证的特定值对象或领域模型属性映射回客户端JSON中导致错误的字段? 将失败验证的特定值对象或领域模型属性映射回客户端JSON中导致错误的字段,应该怎样做? - user6233283

2
我认为客户端验证更像是在用户界面层面上对数据进行消毒。换句话说,检查例如输入数字的输入字段是否由用户提供数字。或者文本输入的长度是否符合最小长度要求。类似这样的东西。
在领域层面上,您应该检查业务领域规则。例如,如果用户正在输入有关新产品的详细信息,产品名称是否已经存在?或者,基于该用户的技能,检查用户在配置新用户时是否选择了有效的部门?这些只是空想的例子,但我希望它们能说明我的意思。

0

如果你的模型有多个客户端,那么你需要一个模型验证器。例如,如果ASP.NET MVC调用您的模型和WPF应用程序,在这种情况下在模型上具有验证逻辑是有意义的。但是在您只有一个客户端的情况下,这将是过度。


那么您的意思是,如果只有一个应用程序使用您的数据库,那么在视图模型上进行验证就足够了? - Brendan Vogt
1
我的意思是,如果您只从一个客户端使用存储库,那么显然 ViewModel 上的验证就足够了,因为没有其他方法可以访问存储库,只能通过调用验证逻辑的控制器来访问。 - Alexandr Mihalciuc
2
这会导致贫血的领域模型。你的领域模型需要保护其不变量。 - JefClaes
如果你遵循亚历山大的推理,那么也没有必要创建领域模型,因为它只是视图模型的副本,而一个视图匹配一个存储库。这很好,但那样做就不是DDD了。DDD对于短暂的应用程序来说过于复杂了。 - Lodewijk Bogaards

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接