ActiveRecord 对 OOP 设计思想的践踏
ActiveRecord 无疑是一种简单直接的处理问题的方式,更具体的说,是更适合处理业务逻辑较简单的所谓 CRUD 问题。
ActiveRecord 擅长处理可以将存储的数据映射为多个无关联的单表的问题,所谓简洁、优雅都说的是这一层。
如果问题复杂度到此止步,那么使用 ActiveRecord 可能会带来开发的经济性,但我看来经济性并非纯粹来源于 ActiveRecord,对数据库访问层的易用性封装可以达到同样的效果。
但当问题再复杂一些时,当遇到数据之间有复杂关系时,多数 ActiveRecord 实现就表现得力不从心,需要引入繁文缛节来描述这些数据关联,那些好的形容词(优雅、简洁)在这里已经不适合了,底层的细节开始侵入上层逻辑。
如果问题再复杂一些,需要设计抽象的有层次的对象结构时,ActiveRecord 的使用无疑是一场灾难了。
ActiveRecord 的主要问题
ActiveRecord 破坏了面向对象基本的封装特性
外界可以随意访问AR对象内部的数据成员,修改数据。但显然这些数据有合理的取值范围,有些还有状态,并不应该如此随意的被修改。
ActiveRecord 破坏了『单一职责原则』(SRP: Single Responsibility Principle)
一个类只允许一个改变它的理由。AR 将数据访问和业务逻辑混合在一起,导致一个对象承担了两个层次的职责。
ActiveRecord 破坏了『依赖导致原则』(Dependency Invert Principle)
软件设计和一切工作的宗旨是:永远是顶层设计决定下层实现,下层依赖上层,而不是反过来。
软件应该分层,每一层只考虑当前层的细节,而不是跨层,更不能因为底层数据库的实现影响上层逻辑,此乃工作中的大忌。
AR 的引入,直接导致上层逻辑直接依赖底层数据库的实现,对象只是数据库在对象领域的投影。
AR 的使用,使得对存储层的修改都几乎直接影响上层逻辑。
ActiveRecord 导致『面向数据库开发』(Database Oriented Development)
软件开发什么时候变成了『Database First』?
说到底,数据库只是业务数据的一种存储手段,是抽象的数据在具体存储层面的投影,面向数据库设计将极大的限制软件的抽象能力。
面向数据库的设计的软件很难修改,因为数据库很难修改,历史数据很难修改,这就很难做到对需求的及时响应。
但是需求是一直在变化的,组织结构、业务变化、甚至访问量规模的变化,都会引起最终的需求变化。
工程师没有任何理由假定软件一经交付,需求一程不变。更没有道理限定软件必须只能绑定在某一款类型的数据库上,这是不负责任的行为。
是否还有别的选择
Java 世界早早的意识到了 ActiveRecord 的弊端,转而使用了 DataMapper 的方式,隔离业务逻辑层和数据访问层的耦合,并且是依赖导致的:数据访问层依赖业务逻辑层,而不是反过来。
尽管在遇到简单问题时,ActiveRecord 或许会更有经济性,但它有隐性成本。对此,工程师头脑里一定要有清醒的认识,并时刻保持警惕,在业务复杂度增加到一定程度时,切换至 DataMapper 永远都是一个必选项,一次性投入的切换成本,是保证了以后没有技术债的积累。
参考文章
- Object Oriented Design: https://en.wikipedia.org/wiki/Object-oriented_design
- SOLID: https://en.wikipedia.org/wiki/SOLID
- Active Record: https://www.martinfowler.com/eaaCatalog/activeRecord.html
- Framework Design Guidelines: Data Source Architectural Patterns: https://www.informit.com/articles/article.aspx?p=1398618&seqNum=3
- Active record pattern: https://en.wikipedia.org/wiki/Active_record_pattern
- 批評 ACTIVE RECORD 的13個論點:最好用也最危險的 ANTI-PATTERN: https://blog.turn.tw/?p=2992
- ORM anti-patterns - Part 1: Active Record: https://www.mehdi-khalili.com/orm-anti-patterns-part-1-active-record
- Active Record vs Data Mapper, https://orkhan.gitbook.io/typeorm/docs/active-record-data-mapper