对于业务逻辑(域)和数据库访问逻辑使用单独的层我是相当新的,但在处理过程中我遇到了一个问题,我仍然觉得我没有找到一个很好的解。
澄清我现有的解决方案使用Data Mappers直接处理数据库交互。但是,正如我进一步调查此问题,许多人建议Domain层不应直接与实际执行数据库交互的Data Mapper通信。这就是我将Repository对象放在Domain和必要的Data Mappers之间的原因,但这并不是很自然或正确。那么真正的问题是自然存在什么层来处理域和数据映射器之间的通信?任何关于如何构建它的例子都将受到赞赏。
例如:
答案 0 :(得分:4)
域模型与其实现之间存在区别。仅仅因为您的模型显示关系Person ---> Campaign ---> Event
并不意味着您必须以这种方式实现它。 IOW,您的模型以面向对象的方式显示您的分析和设计,但您在OOP中实现该模型,该模型在代码中复制该模型的能力有限。
请考虑以下事项。
Person
并非由Campaign
的所有权定义,因此广告系列可能会被排除在知识责任之外。另一方面,Campaign
由Event
定义,作为其执行的一部分,因此在广告系列中包含一系列事件是公平的。我要说的是,每个班级都应该有足够的行为和知识才能使其完整。
对于域和持久层之间的通信,将它们视为两个非常不同的系统,而不关心另一个。他们每个人都知道它的责任是什么以及它做出了什么声明。例如,持久层知道如何保存传递给它的数据并宣布数据已保存。但是,持久层不一定需要理解域对象。同样,域层可以理解Person
,Campaign
和Event
,但对持久性一无所知。
上述含义是域层本身需要是一个整体,不应该依赖于其数据的持久层。但是,仍然需要提供数据来履行其职责。该数据可以来自用户界面或数据库,并通过知道域和持久层的第三方传递给它。
所以,在代码中(伪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
facade对象,并将自身注册为侦听器。然后它调用实例化CreatePerson
的{{1}}方法,然后宣布已完成此操作,并传递新实例。控制器在Person
实现中响应此公告,其中它生成持久层的外观并再次将自身注册为侦听器。然后,它会在完成后调用PersonCreated
方法,即SaveData
。然后,该方法的实现将在UI上显示数据。
正如您所看到的,域层和持久层每个都只知道自己,并不关心另一个的责任。它是应用程序逻辑,在这里表现为控制器,将两者连接在一起。
回到你的具体问题,你可以在持久性上有一个方法DataSaved
,它会宣告FindPerson
。控制器的响应是调用持久层来检索有关广告系列和事件的数据,然后使用该数据调用域图层来构建PersonFound(int id)
。
很抱歉答案很长......
答案 1 :(得分:2)
加布里埃尔,这被称为“impedance matching problem”。有许多解决方案,从J2EE实体bean等重量级应用程序到Ruby ActiveRecord,再到简单的手动连接编码。
好吧,如果没有更多信息,很难确切地知道如何攻击它,但这是基本方法。
这些架构问题中的任何一个都是由性能等非功能性需求驱动的;此外,这里存在正确性问题,因为您希望确保以正确的顺序完成更新。因此,您需要考虑工作负载,即实际应用程序中的使用模式。考虑到这一点,您基本上会遇到一些问题:首先,应用程序中的基本数据类型可能无法正确映射到数据库(例如,代码中表示的VARCHAR属性是什么?),其次是域模型可能无法完全映射到您的数据库模型。
您希望让数据库和dmain模型解决,以便域对象的一个实例恰好是数据库模型中表的一行;在大型应用程序中,由于性能限制或预先存在的数据库模型所施加的约束,您很少这样做。
现在,如果您完全控制数据库模型,它会稍微简化一些事情,因为这样您就可以使数据库模型更接近域。这可能意味着数据库模型有些非规范化,但如果是这样,您可以(根据您的数据库)处理带有视图的数据库,或者只是没有完全规范化的数据库。规范化是一种有用的理论构造,但这并不意味着你不能在真实系统中放松它。
如果您不完全控制您的数据库模型,那么您需要一层对象来进行映射。在实现它时,你有很多选择可供选择:你可以在数据库中构建视图或非规范化表,你可以构建中间对象,或者你可以做两者中的一些,或者甚至可以有两个步骤(即,访问denormalizaed表的中间对象。)
但是,在这一点上,你遇到了“不要重复自己”和“做最简单的事情”的问题。想想最有可能改变的是什么?你的域名模特?如果你有一个强大的领域模型,那就不太可能了 - 业务变化相对较少。数据库中数据的确切表示?更常见的一点。或者,最常见的是确切的使用模式(比如发现需要处理并发更新)。因此,当您考虑这一点时,您需要做些什么才能使尽可能简单的方法来处理最常见的更改。
我意识到这并没有给你非常精确的指示,但我认为我们不能在不了解你的应用程序的情况下提供精确的指示。但是,当你正在处理或多或少的工作时,我也会产生这样的印象:你正在想知道处理这个问题的“正确”方法是什么。所以,我最后会问“你现在对什么不满意?”和“你想怎么解决这个问题?”
答案 2 :(得分:0)
许多系统使用独立的数据层来处理数据库的持久性。这种层的组织有几种模型。有些使用类似工厂的实现,有些使用一对一映射,每个域类有一个数据层类。
数据层的模型通常取决于样式和偏好。重要的是将持久层与域层分离。我相信有一些工具可以帮助你生成这一层,但我的PHP知识很薄,所以我不能专门为PHP命名。
答案 3 :(得分:0)
我会看一下PHPCake和Symfony使用的数据抽象层。