Design patterns 让域对象知道数据访问层是否不正确?

Design patterns 让域对象知道数据访问层是否不正确?,design-patterns,orm,domain-driven-design,poeaa,Design Patterns,Orm,Domain Driven Design,Poeaa,我目前正在重写一个应用程序,使用数据映射器从域层完全抽象数据库。但是,我现在想知道处理域对象之间关系的更好方法是什么: 直接从域对象内的相关数据映射器调用必要的find()方法 将关系逻辑写入本机数据映射器(PoEAA中的示例通常会这样做),然后在域对象内调用本机数据映射器函数 或者在我看来,为了保留“胖模型,瘦控制器”的咒语,域对象必须知道数据映射器(无论是它们自己的映射器还是它们可以访问系统中的其他映射器)。此外,选项2似乎不必要地使数据访问层复杂化,因为它跨多个数据映射器创建表访问逻辑,而

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

  • 直接从域对象内的相关数据映射器调用必要的find()方法
  • 将关系逻辑写入本机数据映射器(PoEAA中的示例通常会这样做),然后在域对象内调用本机数据映射器函数
  • 或者在我看来,为了保留“胖模型,瘦控制器”的咒语,域对象必须知道数据映射器(无论是它们自己的映射器还是它们可以访问系统中的其他映射器)。此外,选项2似乎不必要地使数据访问层复杂化,因为它跨多个数据映射器创建表访问逻辑,而不是将其限制在单个数据映射器中

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


    更新:这是我能想到的处理域对象之间关系问题的唯一两个解决方案。任何显示更好方法的示例都是受欢迎的。

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

    域应该遵循SRP,只需生活在其他方面。当您遍历您的域时,您应该不知道这些属性是通过延迟加载填充的,还是通过实例化生成的

    我已经编写了一个域模型,其中包含DAL对象,维护起来真是一场噩梦。然后我了解了NHibernate和我的域由POCO和它们各自的业务逻辑组成,我想封装它们

    [编辑]

    这里有更多信息。如果我试图解释的话,我只会让自己难堪。我只能以用户的身份谈论实现。这里有一篇关于这个问题的好文章。您感兴趣的是拦截器和mixin的实现

    使用这些工具,您可以编写employee类,如下所示:

    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);
        }
    }
    
    公共类员工
    {
    公职人员()
    {
    技能=新列表();
    }
    公共字符串名称{get;set;}
    公共IList技能{get;private set;}
    公共技能(技能)
    {
    //在此处执行特定于域的验证。。。
    技能。添加(技能);
    }
    }
    

    正如您所看到的,我的数据访问需求不会影响我的域设计。

    在进一步阅读并搜索合适的模式后,我偶然发现了


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

    恐怕您对存储库模式的意图有点误解

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

    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);
    }
    
    interface EmployeeRepository
    {
    列表检索者(条件someCriteria);
    无效存储(员工或员工);
    int countOf(标准someCriteria);
    //便利方法
    员工检索id(字符串id);
    员工检索BYSN(字符串ssn);
    }
    
    这段代码的客户机不知道集合是否在内存中,就像单元测试一样,或者在某些情况下与ORM映射器交谈,或者在其他情况下调用存储过程,或者为某些域对象维护缓存

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

    看看有没有更多的闲话

    针对对该答案的一些评论:


    合理的批评。不过,我还是很担心 然后困惑于如何获得一个单一的 域对象或 在 现有域对象的上下文。 -加布里埃尔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;    
    }
    
    //假设EmployeeRepository通过存储过程与数据库对话
    公共雇员检索id(字符串id)
    {
    ResultSet employeeResultSet=this.database.callSproc(“PROC_GetEmployeeById”,
    新对象[]{id});
    列出技能=
    new SkillRepository().retrieveBy(新的EqualsCriteria(“EmployeeId”,id));
    Employee=new EmployeeFactory().createNew()。
    fromResultSet(employeeResultSet)。
    技术(技能)。
    build();
    回归重建;
    }
    
    另一种方法不是调用技能库,而是让员工库调用存储过程(在本例中)来加载技能的结果集,然后委托给技能工厂以获取技能列表


    我不能打电话到存储库吗 以及是否向 数据映射器或加载对象 它关注的是记忆,不是吗 加布里埃尔1836


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

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

    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;
      }
    }
    
    公共类客户端
    {
    公共void ChangeZipCode(字符串zipCode)
    {
    //此方法根据工厂上下文访问内存或数据库
    bool zipCodeExists=RepositoryFactory.Get().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>();
    }