【问题标题】:Is It Incorrect to Make Domain Objects Aware of The Data Access Layer?让域对象知道数据访问层是否不正确?
【发布时间】:2009-01-22 04:04:22
【问题描述】:

我目前正在重写一个应用程序以使用从域层完全抽象数据库的数据映射器。但是,我现在想知道处理域对象之间关系的更好方法是:

  1. 直接在域对象内从相关数据映射器调用必要的 find() 方法
  2. 将关系逻辑写入本机数据映射器(这是示例在 PoEAA 中倾向于执行的操作),然后在域对象中调用本机数据映射器函数。

在我看来,为了保留“胖模型,瘦控制器”的口头禅,域对象必须了解数据映射器(无论是它们自己的还是它们可以访问其他映射器)系统)。此外,选项 2 似乎不必要地使数据访问层复杂化,因为它跨多个数据映射器创建表访问逻辑,而不是将其限制在单个数据映射器中。

那么,让域对象知道相关的数据映射器并直接从域对象调用数据映射器函数是不正确的吗?

更新:这是我可以设想的处理域对象之间关系问题的仅有的两个解决方案。欢迎任何展示更好方法的示例。

【问题讨论】:

    标签: design-patterns orm domain-driven-design poeaa


    【解决方案1】:

    恐怕你稍微误解了存储库模式的意图。

    存储库的行为类似于特定域对象的内存集合,通常是聚合根:

    interface EmployeeRepository
    {
        List<Employee> retrieveBy(Criteria someCriteria);
        void store(Employee someEmployee);
        int countOf(Criteria someCriteria);
        // convenience methods
        Employee retrieveById(String id);
        Employee retrieveBySSN(String ssn);
    }
    

    此代码的客户端不知道集合是否在内存中,就像您在进行单元测试时那样,或者在某些情况下与 ORM 映射器通信,或者在其他情况下调用存储过程,或者维护缓存以用于某些领域对象。

    这仍然不能回答您的问题。事实上,您可能让域对象具有委派给正确存储库的 save() 和 load() 方法。我认为这不是正确的方法,因为持久性几乎从来都不是业务领域的一部分,它为您的领域对象提供了不止一个改变的理由。

    查看this related question 了解更多信息。

    回应一些关于这个答案的cmets:

    有效的批评。不过,我还是 然后对如何获得单身感到困惑 域对象或集合 相关领域对象在 现有域对象的上下文。 – 加布里埃尔1836

    假设一个员工有很多技能。我认为员工存储库调用技能存储库没有任何问题:

    // assume EmployeeRepository talks to a DB via sprocs
    public Employee retrieveById(String id)
    {
        ResultSet employeeResultSet = this.database.callSproc("PROC_GetEmployeeById", 
            new Object[] { id });
    
        List<Skill> skills = 
            new SkillRepository().retrieveBy(new EqualsCriteria("EmployeeId", id));
    
        Employee reconstructed = new EmployeeFactory().createNew().
                                      fromResultSet(employeeResultSet).
                                      withSkills(skills).
                                      build();
    
        return reconstructed;    
    }
    

    另一种方法是不调用技能存储库,而是让员工存储库调用(在此示例中)存储过程以加载技能的结果集,然后委托给技能工厂以获取技能列表。

    我不能调用存储库吗 以及它是否向 数据映射器或加载对象 内存是它的关注点,不是吗? – 加布里埃尔1836

    完全正确。我通常以这种方式在单元测试中模拟整个数据层。

    【讨论】:

    • 一个有效的批评。但是,我仍然对如何在现有域对象的上下文中获取单个域对象或相关域对象的集合感到困惑。
    • 我不能调用存储库,它是否会调用数据映射器或将对象加载到内存中,不是吗?
    【解决方案2】:

    是的。问问自己为什么域对象会知道这样的事情?甚至不是为什么,而是如何?您要将 DAL 注入到您的域对象中吗?

    该域应该遵循 SRP 只是生活其他一切。当您遍历您的域时,您应该不知道这些属性是通过延迟加载填充的,还是从实例化中补充的。

    我编写了一个包含 DAL 对象的域模型,维护起来简直就是一场噩梦。然后我学习了 NHibernate,我的域由我想要封装的 POCO 和它们各自的业务逻辑组成。

    [编辑]

    这里有更多信息。如果我试图解释它,我只会让自己尴尬。我只能以用户的身份谈论实现。这是一篇关于Domain Model management 的精彩文章。你感兴趣的是拦截器和mixin的实现。

    使用这些工具,您可以编写如下的员工类:

    public class Employee
    {
        public Employee()
        {
            Skills = new List<Skill>();
        }
    
        public string Name { get; set; }
        public IList<Skill> Skills { get; private set; }
    
        public void AddSkill(Skill skill)
        {
            // Perform Domain specific validation here...
    
            Skills.Add(skill);
        }
    }
    

    如您所见,我的数据访问需求不会强加给我的域设计。

    【讨论】:

    • 您能否提供一个示例,说明如何处理加载现有域对象中的其他域对象?例如,您如何为员工加载技能集合?
    • 您希望存储库对象执行此操作...域模型应该不了解持久性,但是应该有另一个知道持久性的对象来处理它。
    • 感谢您的更新。 Fowler 的外键映射示例代码看起来非常熟悉。 :-)
    • 你完全正确!从 DAL 方面来看,它是 FK 映射。从使用方面来看,它被称为域遍历。取决于您正在使用的域对象的哪一端。
    • 但是,如何从 AddSkill 获得“SELECT * FROM Skills WHERE empID = x”?这就是我卡住的地方。 Fowler 的简单示例将查询逻辑放在 Employee Mapper 中,这似乎也不是一个好主意……
    【解决方案3】:

    在进一步阅读并寻找合适的模式后,我偶然发现了the Repository Pattern

    据我所知,这正是设想的解决方案,它允许像 Person 这样的域对象正确地将查询委托给适当的数据映射器,同时让域对象和数据映射器完全抽象。

    【讨论】:

      【解决方案4】:

      我不同意,我认为域对象可以通过抽象工厂访问存储库。

      public class Client
      {
        public void ChangeZipCode(string zipCode)
        {
          // This method access memory or database depending on the factory context
          bool zipCodeExists = RepositoryFactory.Get<IZipCode>().Exists(zipCode);
          this.zipCode = zipCode;
        }
      }
      

      通过使用此模式,无需在您的代码中注入存储库接口 但只有存储库工厂。

      public abstract class RepositoryFactory
      {
        // Class operations
        private static _globalInstance;
        public static CreateGlobalInstance(RepositoryFactory factory)
        {
          _glocalInstance = factory;
        }
        public static I Get<I>()
        {
          return _globalInstance.Get<I>();
        }
        /////////////////////
      
        ///// this operation should be inherited by:
        ///// * NHibernateRepositoryFactory //
        ///// * Linq2SqlRepositoryFactory ////
        ///// * NUnitRepositoryFactory ///////      
        ///// it depends in your context ////////////////////////
        public abstract I GetRepository<I>();
      }
      

      多年来我一直这样做,在我的单元测试中没有任何问题。

      因此,只有在 RepositoryFactory 这个类中才需要依赖注入。

      【讨论】:

        猜你喜欢
        • 2015-04-27
        • 1970-01-01
        • 2011-09-02
        • 2020-10-07
        • 2014-08-05
        • 1970-01-01
        • 2016-04-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多