作者:mobiledu2502929493 | 来源:互联网 | 2023-08-27 10:12
ActiveRecord和DataMapper是两种最流行的模式,用于把应用对象映射到数据库。选择哪种方式取决于项目的大小和复杂性,相对于预期开发时间的代码质量和花费,以及许多其他
Active Record和Data Mapper是两种最流行的模式,用于把应用对象映射到数据库。选择哪种方式取决于项目的大小和复杂性,相对于预期开发时间的代码质量和花费,以及许多其他方面。今天我会对二者都进行阐述。
意外断电,内存溢出,进程中断,由于避免重复的SQL代码而节约的时间和成本。有许多原因会促使你去将对象存储到关系数据库中。我想你猜到接下来要描述的了……
对的-你需要一种对象关系映射用于从数据库获取所需的数据,最可能的情况是,你将选用其中一种,Active Record或者Data MApper。
我们的应用功能基本都是CRUD-或基于领域模型的。大多数是兼而有之。为什么我们只能选用一种而不是都采用两种呢?在这篇文章中我将尝试去回答这个问题以及其他相关问题
让我们先快速回忆下ORM是什么。
ORM – when to use it?
在编程中使用面向对象范式带来一种解决方案,可以让你将对象抽象映射到数据库中数据。这被称为ORM。
ORM背后的通用思想
ORM是一种特别的数据映射层,提供了从应用到数据库的数据访问,这意味着它简化了应用到数据库的访问。这个解决方案的忠实支持者和批评者是一样多的。选择使用或不使用取决于应用的驱动。主要的驱动因素包括:
- performance, 性能
- scalability, 可扩展性
- maintainability, 可维护性
- productivity.生产率
把ORM使用好需要掌握许多的知识,但如果你选择不使用它,你需要给相同的问题提供另外一种解决方案。在你下定决心之前,你需要考虑替代方案将要花费的时间和精力。
在我看来,ORM是一种神奇的工具。它大大提高了你的生产效率,你不需要写复杂的映射过程,并且代码将会变得更易维护。最好的使用方式是当你需要模型的读写分离时。
ORM在写的模型中工作的很好,但是在读的模型中只有一些案例中才比较好。
查询构造器是ORM的核心组件之一,对构造经过优化和易读的查询是非常有帮助的。这也是为什么它在读模型中有用处。同时,你必须注意到这时的读模型不能用到域对象。这是属于写模型的部分功能。按这个思路,你会使用更多的getter并使用不必要的对象关联影响性能,这是非常低效的。
在读模型中使用ORM实体使得可以修改查询结果的状态,这是违背读写分离的原则(CQS)。原则规定,问问题不能引起结果发生变化。
正如之前我说的,有两种主要的方案来实现对象关系映射。
Active Record模式
在Active Record 模式中,模型对应数据库中一张表或一张视图。这种机制是非常简单易懂的。每个类实体对应表格中一条数据。
简单方案通常都有许多限制。AR模式适用于简单模型,即那些没包含复杂业务逻辑的。他是简单CRUD应用的理想工具。伴随越来越多复杂模型的引入,事情会变得越来越难处理。在这种情况下,很难去扩展这样的一个应用。
Data Mapper模式
在Data Mapper模式中,模型就是一个模型类。这个类用于展示业务逻辑,同时包含了行为和数据。这一层代码把内存对象和数据库隔离。我们不知道它是怎么样,以及在哪里存在的。
因此,你有了一个独立的层,用于在代码和数据库之间传数据。并且还必须注意隔离他们。为此,Data Mapper提供了灵活性以及更加重要的独立性
学习更多:微服务中的设计模式
- 面向CTO的微服务中的设计模式。API网关、前台的后端等
使用Active Record模式的后果
使用任意一个这些模式都会有类似效果。
步入过程式编程领域
Active Record遵循数据库优先原则。你创建一个数据库,然后再在代码中模型化它。使用Active Record会引导你进入贫领域模型(Anemic Domain Model)? 清醒点吧,这并不是进步。这意味着你的实体将不包含逻辑。你将会把所以的逻辑放到services层(不要是控制器层,我希望)。总之,model将会是一个被其他类控制的数据袋子。这有什么坏处呢?这可能导致进入过程式编程,而不是面向对象模式。
无封装
需要额外说明的是,模型并不能确保它的状态正确性,因为可能由于错误的使用getters和setteres方法修改了它。这种改变没有封装性,而这是OOP编程的基础思想。我这里不是要说getters和setters方法是错误的,而是要表明,它们应该展示由业务规则产生的行为。
No SRP
你的代码将强依赖数据库。这也违背了单一责任原则(SRP)。怎么会呢?因为模型中包含了数据库交互逻辑。这不是最好的设计,但他确实加速了开发
可测试性问题
最后一个进入我脑子的问题是低可测试性(poor testability)。这种模型是最难测试的,并且不是在所有案例中要求的。
你是否像DTOs那样测试简单对象?那正是我所想的。这里你可以不需要去做这个。然而,这并不意味着你可以跳过所有测试。在服务中逻辑是无处不在的。因此,你需要更多的集成测试(测试中的钻石)
使用Data Mapper模式的后果
分层隔离
Data Mapper遵循代码优先原则。你可以推迟保存数据直到最后,这样可以专注在代码上。model不知道其他层的任何事,它是非常单一的。一个好的模型设计应该是无数据库关联的。持久层是另一个层,领域不应该依赖他。
我的观点是,分离出这个层是Data Mapper最大的优点。另一方面,没有层分离是Active Recode最弱的地方。
我并不是说所有的应用都使用只包含行为的富领域模型。而是,我相信Data Mapper也会是简单模型的好设计。你可以像Active Record那样设计使用它,同时你又获得了层分离的能力。当然,这不是没有任何代价的-这样的方式更复杂并且有更高进入屏障。
提高可测试性
使用Data Mapper不能确保会得到一个设计良好的模型。事实上,我看到了不少使用Data Mapper设计的坏模型(其中一些是我写的)最重要的一点,它不会干扰创建封装良好的模型。由于这一点,你可以通过单元测试获得更好的可测试性(测试中的金字塔)
PHP最流行的几种实现
处于PHP生态中的人应该知道Eloqueent和Doctrine。如果你不熟悉它们,当前你应该知道的是它们都属于ORMs。Eloquent是基于Active Record模式,Doctrine是基于Data Mapper模式的。
我相信这是领域模型该有的样子-存粹,没有任何的依赖性。一个模型不应该关心它该如何持久化。由于这一点,它必须能正常工作,不要去考虑它是存储于RDBS,NoSQL还是任何其它地方。哪种模式能够支持这一点呢?Active Record无法应对这个问题,Data Mapper也许是那个解决方案.
Eloquent中领域模型的实现
如果你使用的是Eloquent,你马上会对Illuminate\Database\Eloquent\Model增加一个依赖。当然了,还有一个隐藏的数据库连接。你没有定义清晰的字段。至少不是非常明确的,因为你可以使用PHPDoc这样的解决方案去优化它。在Laravel框架中呈现的又是什么样子呢?它将会使用一个简单的Employee模型和一个service类(例如:Employer),类中有一个dismiss()方法,用于设置适当的一个值。
Doctrine中领域模型的实现
如果你使用的是Doctrine,你完全可以使用这种模式(领域模型)。我喜欢这个实现。为实现这个方式,你需要在扩展文件中使用XML映射。这个在Symfony中如何展现? 在Employee实例中调用dismiss()方法,保存时使用EntityManager或者EmployeeRepository。模型是干净存粹的。然而Doctrine中有一个扩展依赖是不可避免的:Doctrine\Common\Collections\ArrayCollection.
我希望这个在将来可以得到修正。就现在而言,我接受这种独立的包形式并且被很好的封装,将来可以被很容易的替换掉。客户端代码不应该使用它,也不知道领域模型正在使用它。
存储库模式
让Active Record更优雅的一个建议是使用仓储模式,这是遵循依赖反转原则的一种实现。它将帮助你实现分层结构。在将来,你可以很容易的用另外一种工具替换Eloquent。
另外,这将成为一个常规操作。有时候你希望保存你的数据在另外一个存储库中,比如NoSQL。不管怎样,仓储模式允许你在没有数据库连接的情况下编写内存中的假实现。
我知道,这看起来像一个毫无用处的层次,但实际上它不是。仓储层帮助你避免了重复的查询。非常酷的是,当你需要改变一些字段时,你可以在一个地方很容易的找到它们。
独立于ORM的模型
我曾经见过的一种可能性实现是分别为领域和数据库创建模型-不管实现方式是用Doctrine(Data Mapper)还是Eloquent(Active Record)。
但是,在我观点里,这又是增加了一个层,一个无用的层。这个方案还有另外一个我不喜欢的点是,它需要从一个对象转移到另外一个对象。所有领域模型中的字段必须要有getters。你也应该只在第一次初始化时使用构造函数创建一个对象-这是方案中很难做到的一个点。这对Eloquent来说是好的解决方案,但是Doctrine不需要这种独立的两个模型来实现逻辑分层。
Active Record vs Data Mapper – 哪种是正确的选择?
以下是对比,总结如下:
- Data Mapper模式可以让你不出问题的情况下创建简单CRUD应用,同时可以在更复杂的应用中使用。
- 乍一看,Data Mapper似乎是一个更好的解决方案。或者说得更准确些–更有质量。另一方面,质量并不总是最重要的东西。我知道–这听起来是反直觉的。 质量与交付时间和成本都有关系。你总是可以做得更好,但你需要在时间和成本方面定义一个可接受的质量水平,并为之努力
- 在为Active Record辩护上,我见过许多项目,它们应该是简单的应用程序,具有直接的实际业务领域,但它们的实现方式使它们难以理解。Data Mapper模式使得在网络应用中更容易坚持良好的编程实践、干净的代码和架构,但它也更加复杂。如果你不能正确使用它,Active Record的实现可能会变成简单项目的更好选择。
- 解决复杂的问题很重要,也很有意义,但你也应该努力限制新问题的产生。这就是为什么我喜欢模块化,因为你可以在不同的模块中使用各种解决方案,而不会造成不必要的混乱。此外,为一个给定的问题寻找解决方案会变得更简单。
程序员喜欢假装他们只处理复杂的问题。事实是,他们工作的很大一部分是解决典型的挑战,如创建简单的CRUD应用程序。这就是为什么我不喜欢说某个东西本身就是一个糟糕的解决方案(反模式)。
没有什么总是好的或者坏的-需要一个适当的背景。一个程序员的工作是简化解决方案,让代码能够被其他的程序员更好的阅读和管理。
老实说,我几乎总是使用Data Mapper,它适用于简单和复杂的应用。然而,如果我使用Active Record,那将是用于逻辑相对简单的应用程序。
翻译自:tsh.io/blog/active-record-vs-data-...