【问题标题】:Persistent ignorant Domain Layer持久无知的领域层
【发布时间】:2011-12-06 09:58:47
【问题描述】:

我的编程语言是 C#。我有一个关于我的域模型中的持久性无知的问题。

假设我有一个这样的人员类:

public class Person
{
    private string email;

    public string Email
    {
        get { return email; }
        set { email = value; }
    }

    public Person(string email)
    {
        this.email = email;
    }
}

现在,在我的域模型中,有一条规则是不能有两个人拥有相同的电子邮件地址。因此,当实例化一个新人时,这需要在更改电子邮件属性时进行验证。所以我想知道你将如何在你对持久性无知的域层中解决这样的验证。我目前所做的是使用工厂模式进行人员实例化,我在其中注入人员存储库。在那里我可以搜索具有相同电子邮件地址的其他人。像这样:

public class PersonFactory
{
    private static readonly IPersonRepository personRepository;

    public Person CreateNewPerson(string email)
    {
        Person personWithSameMail = personRepository.GetPersonByEmail(email);

        if (personWithSameMail != null)
            throw new ApplicationException("Email already exists.");

        return new Person(email);
    }

    public PersonFactory(IPersonRepository personRepository)
    {
        this.personRepository = personRepository;
    }
}

但使用此解决方案,更改人员电子邮件地址(可以是有效的商业案例)时的检查仍然不包括在内。此外,Person 类仍然公开了一个公共构造函数,并且通过绕过工厂,仍然可以使用重复的电子邮件地址。

任何优雅的解决方案?

附:致所有以数据为中心的人:不,我不想在数据访问层验证欺骗电子邮件;)

更新:

无论如何,这整个问题可能已经过时了。根据域模型上下文检查重复的电子邮件总是需要知道所有人的“东西”——一个根。 “某物”可以是地址簿,也可以是包含所有拥有电子邮件地址的人的整个世界。因此,也许我混淆了程序员对问题的技术优雅解决方案的需求,这只是因为缺乏完整且经过深思熟虑的域模型而存在。这里可以是这种情况吗?不只是一个人的电子邮件地址在太空中漂浮(嘿,太空将是我在这里的总根;))只是为了生存的乐趣?但是,如果这将是一个真实的商业案例,例如地址簿应用程序,那么地址簿将是人员的聚合根,因此可以检查其内部集合中的所有人员,以确保没有重复的电子邮件地址。

【问题讨论】:

  • 为什么不将构造函数设为内部并在更新时检查电子邮件地址?而且我认为将存储库提供给工厂并不是一个好的解决方案。致电工厂的服务功能应检查电子邮件地址是否已存在。
  • @WouterdeKort:我不同意您的评论:(1) 将 ctor 设为内部无济于事。它仍然允许创建具有重复邮件地址的用户。 (2) 使调用工厂的方法有效地删除该业务规则。现在由调用者执行此规则。这只是非常糟糕的 API 设计
  • @DanielHilgarth 他明确提到公共构造函数是一个问题。工厂应该构造一个具有所有依赖项和所需值的对象。将业务逻辑与工厂混合在一起我称之为糟糕的设计。
  • @WouterdeKort:我不同意。工厂是封装某种类型的所有对象需要满足的业务规则的理想场所

标签: c# domain-driven-design


【解决方案1】:

我不是 DDD 专家,但我的看法是这样的。

首先,对我来说,您的设计中最大的缺陷是允许实体上的设置器。这意味着您可以在聚合根不知道的情况下更改实体的状态。我会重构它,删除设置器,并在对象创建时通过构造函数设置它。

还有一点。个人实体不应该对电子邮件进行验证(以检查电子邮件是否已被使用的形式),因为如果她是聚合的根,它只能知道它自己的状态和相关的子实体。然后你应该从实体中引用存储库,这对我来说是糟糕的设计。这就是我认为你使用工厂的原因。

我认为验证应该首先在 UI 中进行,以便尽快失败。但这还不够。在保持您的域状态时,您应该强制执行 Person 具有唯一电子邮件的规则。我认为验证应该发生在名为 PersonRegistrationService 的域服务中,该服务将引用 IPersonRepository 并且在添加人员实体之前为了使其收藏持续存在,它会首先检查人员的电子邮件是否是唯一的。如果不是,我会通知用户一个错误并且不添加实体。

你怎么看?

【讨论】:

  • 从您的建议中我了解到,创建一个人应该是域内部的。然后,会有一个注册服务,它的行为类似于验证一个人是否允许在域中存在,对吗?因此,该服务将获取所有必需的初始人员属性作为参数,然后通过注入存储库,可以处理“具有给定数据的人员是否允许存在”?
  • 我认为服务首先是一种领域概念。我建议注册服务,因为凭空创建人对我来说没有意义。因此,该服务的职责将是接受参数(甚至是此时不属于域状态的新 Person 实体),与存储库验证电子邮件是否是唯一的,然后将人员添加到它的内部集合(将由 ORM 或其他持久化,但它不仅在这里很重要)。
  • 也许其他人对此有另一种看法,但通过域服务似乎是合理的。事实上它和你的工厂非常相似,但不同的是,服务具有域含义,并在成功验证后将人员添加到存储库(这是持久化的人员实体的集合)
  • 我同意@ThomasJaskula 服务应负责在创建和更新人员时检查业务规则。 Factory 不与您的存储库耦合,只是创建对象。
  • 我同意。无论如何,你永远不可能真正在域类中包含所有逻辑,我认为服务层是验证这种约束的完美场所。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多