【问题标题】:How to stay DRY with validations in Domain objects and services vs validations in UI layer如何通过域对象和服务中的验证与 UI 层中的验证保持 DRY
【发布时间】:2011-09-02 22:33:52
【问题描述】:

我已经搜索过这个问题的答案,甚至问了几个问题,但还没有真正找到正确的答案。如何将我的 POCO 域对象和服务中的验证方法暴露给 UI 层?目前我正在使用网络表单。

例如,我有以下域对象:

class Person
{
    public string Name { get; set; }
    public string Email { get; set; }

    public bool IsValidEmail(string email) {}
    public bool IsValidName(string name) {}

    public bool IsValidPerson()
    {
        if (IsValidEmail(Email) && IsValidName(Name)) { return true; }
        return false;
    }
}

和域服务:

class PersonService
{

    private Person person;
    private PersonRepository pRepo;

    public PersonService()
    {
        person = new Person();
        pRepo = new PersonRepository();
    }

    public AddPerson(Person p)
    {
        if (p.IsValidEmail(p.Email) && p.IsValidName(p.Name) && !DoesEmailExistInDatabase(p.Email))
        { pRepo.Save(p); }
        else
        { throw new ArgumentException(); }
    }

    public GetPersonByEmail(string email)
    {
        if (person.IsValidEmail(Email))
        { pRepo.GetByEmail(email)); }
        else
        { throw new ArgumentException(); }
    }

    public bool DoesEmailExistInDatabase(string email) { //code if exists.. }
}

和 UI/代码隐藏层:

通过电子邮件获取联系人

string emailInput = EmailTextBox.Text;

PersonService pService = new PersonService();
Person p = new Person();

if(p.IsValidEmail(emailInput))
{
    Person myPerson = pService.GetPersonByEmail(emailInput);
}
else
{
    //give user error here...
}
  1. 为域对象中可能需要验证的每个属性创建单独的验证方法是否正确?

  2. 域对象和服务中的这些方法是否应该是静态的,这样我就不必创建人员实例来进行验证?

  3. 我是否应该在 Service 中公开来自 Person 域对象的验证,以便用户不需要知道在哪里寻找它们(因为我将一些服务和 POCO 中的一些确实是实施问题)?

4.有没有更好的办法?

【问题讨论】:

    标签: c# asp.net validation architecture domain-driven-design


    【解决方案1】:

    关于 #1 - 是的(这是一种有效的方法),假设域对象最适合“知道”正确的输入是什么。

    关于 #2 - 是的。

    关于 #3 - 这样做没有害处,但是,如果您不相信类外部的东西能够/负责实际验证,为什么还要相信它来调用验证?

    我会在设置值时强制执行验证,一旦对象中有“好数据”,以后就不需要验证它了。这导致第 4 点...

    Re #4 - 以某种方式提供/公开验证的好处是系统的其他部分可以使用它;经典示例是在 UI 中,您可以通过在输入或提交时验证输入来提供更好的用户体验。

    另一种验证方法是确定好的数据是什么样的(在整体视图中) - 并为作为(单独的)公共域级别“服务”存在的数据定义一堆规则。验证每个域对象内的输入是好的,因为您可以随着各个域对象随着时间的推移而更改特定规则(您限制了孤立更改的影响) - 缺点是您会重复很多规则。

    一个公共服务会解决这个问题,一个服务会说“这就是一个有效的电子邮件地址的样子”,任何你的域对象都会听从服务来告诉他们什么是好的电子邮件地址。

    这种方法的“诀窍”是要小心您如何命名验证方法 - 不要太含糊或模棱两可。例如,您可能会发现大多数具有电子邮件属性的域对象使用一种“主要”电子邮件验证方法ValidateGenericEmail(),但您经常会遇到其他对象是具有特殊规则ValidateCorporateEmail() 的特殊情况的情况。没关系,将它们添加到验证服务中,因为这是域层中管理这些规则的中心位置。

    然后,您的域对象仍然可以执行您之前需要它们执行的所有操作 - 除非您已将规则提取到单独的公共位置。

    【讨论】:

    • 感谢您的反馈。当你说“我会在设置值时强制验证,一旦你在对象中有“好的数据”,以后就不需要验证它了。” - 在 UI 中,我通常会在甚至之前验证输入参数创建对象作为输入甚至可能不是有效的类型。此外,我在与 Repo 对话的服务层中进行了验证,因为他们需要查询不允许直接从 POCO 中允许的数据库。我的主要想法是给消费者一个单一的验证点。
    • @Developr - “在 UI 中,我通常会在创建对象之前验证输入参数”,这很公平。我通常这样做的方式是使用属性设置器本身作为验证的主要点,因为没有其他方法可以传递信息(同样适用于构造函数 - 这些将立即使用公开可用的“设置”值setter - 触发验证)。
    • 我开始在设置器上添加验证,但我决定放弃它,因为在对象被持久化之前,它应该能够处于无效状态。此外,对于更复杂的对象,在设置器上抛出异常可能会导致必须以特定顺序设置项目以避免异常的问题。
    【解决方案2】:

    我只会在表示层上保持验证并从中清除域模型。所以域模型假设数据已经被验证(并且它被验证了,不是吗?)。或者您有其他数据来源吗? 它会让你的领域模型更加纯粹,你会看到它的核心。但是通过使用保护子句检查构造函数参数来在对象创建时强制执行一些约束是不错的。

    【讨论】:

    • @xelibrion - 如果您需要与不同页面上的域对象进行交互,那么您必须再次重复验证代码。至少如果您对对象进行基本验证,您可以从一个共同的地方调用它们。这样,如果以后规则发生变化,您很可能只需更新方法。
    • 将验证责任交给 viewmodel 怎么样?您不应该直接在您的页面上使用您的域对象。您应该将其转换为视图模型并通过服务与域对象进行交互
    • @xelibrion - 如果我想在后面的代码中创建一个新的人对象怎么办:Person p = new Person(); ,然后我可以完成属性并调用 Service.Save(p); ——这不对吗?
    • “我只会在表示层上保持验证并从中清除域模型” 无意冒犯,但这不是自相矛盾吗?除了对技术上错误输入的简单检查之外,没有领域知识就无法检查错误业务输入。除非您有 UI 调用服务进行验证。
    • @Developr 是的,我认为这是不对的。让我们想象一下,您将想要发布 WCF 服务,该服务将提供完全相同的功能,或者将您的应用程序移动到 ASP.NET MVC,它提供开箱即用的验证功能。然后,您将不得不复制此代码。我会将已经验证的 PersonViewModel(只是 DTO,对域一无所知)传递给 Service.Save() 方法。
    猜你喜欢
    • 2011-07-03
    • 2011-06-06
    • 1970-01-01
    • 2010-11-01
    • 2011-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多