【问题标题】:How To Handle Communication Between the Domain and Database Layers?如何处理域层和数据库层之间的通信?
【发布时间】:2009-02-14 16:12:02
【问题描述】:

对于为业务逻辑(域)和数据库访问逻辑使用单独的层,我还很陌生,但是在解决问题的过程中,我遇到了一个问题,我仍然觉得我还没有找到一个很好的解决方案解决方案。

澄清 我现有的解决方案使用数据映射器直接处理数据库交互。然而,当我进一步调查这个问题时,许多人建议域层不应该直接与数据映射器进行通信,也不应该包含实际执行数据库交互的数据映射器。这就是为什么我将存储库对象放置在域和必要的数据映射器之间,但这感觉不太自然或正确。所以真正的问题是自然存在什么层来处理域和数据映射器之间的通信?任何有关如何构建它的示例将不胜感激。

例如:

  • 如何在另一个域对象的上下文中正确处理检索域对象的集合?
  • 如何根据对另一个对象执行的操作强制插入单个域对象或对象集合。我目前面临的情况是,当一个人附加到一个活动时,我需要插入所有需要为该活动执行的人的事件。

【问题讨论】:

    标签: php oop dns database-abstraction


    【解决方案1】:

    领域模型和它的实现是有区别的。仅仅因为您的模型显示了关系Person ---> Campaign ---> Event 并不意味着您必须以这种方式实现它。 IOW,您的模型以面向对象的方式展示了您的分析和设计,但您在 OOP 中实现该模型,它在代码中复制该模型的能力受到限制。

    考虑以下内容。

    Person 不是由其对Campaign 的所有权定义的,因此可以将活动排除在其知识责任之外。另一方面,Campaign 由作为其执行的一部分发生的Events 定义,因此在活动中拥有一系列事件是公平的。我要说的是,每个班级都应该有足够的行为和知识来使其完整。

    关于域和持久层之间的通信,将它们视为两个非常不同的系统,彼此不关心。他们每个人都知道自己的职责和发布的公告。例如,持久层知道如何持久化传递给它的数据并宣布数据已保存。但是,持久层不一定需要了解领域对象。同样,领域层理解PersonCampaignEvent,但对持久性一无所知。

    上面的意思是领域层需要自己是一个整体,不应该依赖持久层来获取数据。但是,它仍然需要提供数据以履行其职责。该数据可以来自用户界面或数据库,并通过了解域和持久层的第三方传递给它。

    所以,在代码中(伪 C#)...

    namespace DomainLayer
    {
        interface IDomainListener 
        {
            void PersonCreated(Person person);
        }
    
        class Person
        {
            private string name;
    
            public Person(string name)
            {
                this.name = name;
            }
    
            public string Name
            {
                get { return name; }
            }
        }
    
        class Domain 
        {
            private IDomainListener listener;
    
            public Domain(IDomainListener listener) {
                this.listener = listener;
            }
    
            public void CreatePerson(string name) {
                Person person = new Person(name);
                listener.PersonCreated(person);
            }
        }
    }
    
    
    namespace PersistenceLayer
    {
        interface IPersistenceListener
        {
            void PersonDataSaved(int id, object data);
        }
    
        class Persistence
        {
            private IPersistenceListener listener;
    
            public Persistence(IPersistenceListener listener) 
            {
                this.listener = listener;
            }
    
            public void SaveData(object data)
            {
                int id = ...; // save data and return identifier
                listener.DataSaved(id, data);
            }
        }
    }
    
    namespace MyApplication
    {
        class MyController : IDomainListener, IPersistenceListener
        {
            public void CreatePersonButton_Clicked()
            {
                Domain domain = new Domain(this);
                domain.CreatePerson(NameTextbox.Text);
            }
    
            public void PersonCreated(Person person)
            {
                Persistence persistence = new Persistence(this);
                persistence.SavePersonData(person.Name);
            }
    
            public void DataSaved(int id, object data)
            {
                // display data on UI
            }
        }   
    }
    

    如您所见,命名空间代表不同的层。 XYZListener 接口定义由XYZ 层发出的公告。对这些公告感兴趣并将对其作出响应的任何其他层都需要实现这些接口,我们的MyApplication 层也是如此。

    当点击“创建按钮”时,控制器为域层创建Domain外观对象并将自己注册为监听器。然后它调用CreatePerson 方法,该方法实例化Person,然后宣布这已经完成,并传递新实例。控制器在PersonCreated 实现中响应此公告,其中它产生持久层的外观并再次将自己注册为侦听器。然后它调用SaveData 方法,该方法在完成时通知DataSaved。然后,该方法的实现会在 UI 上显示数据。

    如你所见,领域层和持久层都只知道自己,不关心对方的职责。将两者连接在一起的是应用程序逻辑,这里表现为控制器。

    回到你的具体问题,你可以有一个关于持久性的方法FindPerson,它将宣布PersonFound(int id)。控制器的响应是调用持久层来检索有关活动和事件的数据,然后使用该数据调用域层来构建Person

    抱歉,回答太长了……

    【讨论】:

      【解决方案2】:

      加布里埃尔,这被称为“impedance matching problem”。有很多解决方案,从 J2EE 实体 bean 等重量级解决方案到 Ruby ActiveRecord,再到简单的手动连接编码。

      更新

      好的,如果没有更多信息,很难确切了解如何攻击它,但这是基本方法。

      任何此类架构问题都是由性能等非功能性需求驱动的;此外,这里还有一个正确性问题,即您要确保以正确的顺序完成更新。因此,您需要考虑工作负载,即实际应用程序中的使用模式。考虑到这一点,您基本上有几个问题:首先,您的应用程序中的基本数据类型可能无法正确映射到数据库(例如,您的代码中表示的 VARCHAR 属性是什么?),其次是您的域模型可能无法完全映射到您的数据库模型。

      您想要的是让数据库和 dmain 模型一起工作,以便域对象的一个​​实例恰好是您的数据库模型中的表中的一行;在大型应用程序中,由于性能限制或预先存在的数据库模型施加的限制,您很少能做到这一点。

      现在,如果您完全控制您的数据库模型,它会稍微简化一些事情,因为您可以使您的数据库模型更接近域。这可能意味着数据库模型有些非规范化,但如果是这样,您可以(取决于您的数据库)使用视图来处理它,或者只是没有完全规范化的数据库。归一化是一种有用的理论结构,但这并不意味着您不能在实际系统中放松它。

      如果您完全控制您的数据库模型,那么您需要一层对象来进行映射。在实现它时,您有很多选项可供选择:您可以在数据库中构建视图或非规范化表,您可以构建中间对象,或者您可以两者都做,甚至两者都有几个步骤(即,访问非规范化表的中间对象。)

      不过,到那时,您会遇到“不要重复自己”和“做最简单的可能行得通的事情”的问题。想想最有可能改变的是什么?你的领域模型?如果你有一个强大的领域模型,那就不太可能——业务变化相对较少。数据库中数据的准确表示?比较常见一点。或者,最常见的是,确切的使用模式(比如发现需要处理并发更新。)因此,当您考虑这一点时,您需要做些什么来尽可能轻松地处理最常见的更改。

      我知道这并没有给你非常精确的指示,但我认为我们不能在不了解你的应用程序的情况下提供精确的指示。但是我也有一种印象,你想知道处理这个问题的“正确”方式是什么,而你已经在使用或多或少可以完成工作的东西。所以,我最后会问“你现在对什么不满意?”和“你想怎么解决这个问题?”

      【讨论】:

      • 我正在使用 Fowler 的数据映射器,上面有一个域层。然而,从其他事情来看,我发现大多数人似乎不鼓励将域层耦合到特定的数据映射器。你会推荐什么。
      • 我过去使用过 ActiveRecord 和 Row Gateway 模式,但在重构现有设计时,我发现有必要实现映射以处理来自数据库的更复杂的模型。
      【解决方案3】:

      许多系统采用独立的数据层来处理与数据库之间的持久性。这种层的组织有几种模型。有些使用类似工厂的实现,有些使用一对一的映射,每个域类有一个数据层类。

      数据层的模型通常取决于风格和偏好。重要的是将持久层与域层分开。我相信有一些工具可以帮助你生成这个层,但是我的 PHP 知识很薄,所以我不能专门为 PHP 命名。

      【讨论】:

      【解决方案4】:

      我会看看 PHPCake 和 Symfony 使用的数据抽象层。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-25
        • 2021-02-11
        • 1970-01-01
        • 1970-01-01
        • 2020-07-20
        • 1970-01-01
        相关资源
        最近更新 更多