30-17 之 DataSource Layer - Active Record

接下来要这篇文章要来谈谈很常听到的『 Active Record 』。

什麽是 Active Record ?

根据 《 Patterns of Enterprise Application Architecture - Martin Fowler 》书中的定义如下 :

An object carries both data and behavior.

对,基本上就是在 DataSource 层里,然後每个实体代表一个 table row,且实体内包含资料与行为 ( domain 逻辑 ) ,这和上一章的 RowDataGateway 很像,都是它缺了行为。下图就是书中的范例物件 UML 图。没看错。

https://ithelp.ithome.com.tw/upload/images/20211002/20089358mhKDPOUmN6.png
图片来源: 《 Patterns of Enterprise Application Architecture - Martin Fowler 》

是不是所有的 domain 逻辑都会丢在 Active Record 呢 ? 那要在什麽时後用呢 ?

我觉得不是,我觉得作者本意不是,不然为什麽还要有 Domain 层呢 ? 并且还将 Active Record 放在 DataSource 层,我觉得他应该是指将一些简单的 domain logic 放在 active record 中,例如下述范例中的 isPass。

class StudentTest{
	id: string
	stuentId: string
	score: number

	isPass(): boolean{
     return score >= 60;
  }
}

作者只建议在以下的情境下使用 :

  • 业务逻辑简单,并且与 Table 相近。
  • 如果 domain 层是使用 Transaction Script 模式,就是一个不错的选择。

并且他还要说到一句话,它事实上没有以上的情况不要用 :

Another argument against Active Record is the fact that it couples the object design to the database design. This makes it more difficult to refactor either design as a project goes forward

简单的说就是会让物件设计与资料库设计绑在一起。

一些常见语言有使用 Active Record 的地方

目前我常听到的,基本上有使用 Active Record Pattern 来实作的有以下几个 :

书中我觉得怪怪的地方

还记不记得上一章节的 RowDataGateway,它每个实体都代表资料库中的一行,而 query 因为有时会回传多个,所以他拉出一个叫『 Finder 』的东东,但在 Active Record 范例中我到没看到这个东西呢… 那他多笔资料的 query 也是写在 Active Record 吗 ?

然後看一下实作的 framework 例如 Laravel ,看起来就是丢在里面。

$flight = App\Flight::where('active', 1)->first();
$flights = App\Flight::find([1, 2, 3]);

战争

事实上有不说少了在抱怨这个模式,请参阅以下的文章。

范例

这个范例与 RowDataGateway 相同,都是以 Person 为范例,然後这里有个重点要注意一下 :

这个地方 Active Record 的类别,我是以 Model 为命名,主要的原因是不少实作 Active Record 的语言,都是以 Model 为命名。

所以我现在暂时的将我对 Model 这个词的定义为 :

以 Active Record 为概念的类别称为 Model

https://ithelp.ithome.com.tw/upload/images/20211002/20089358QLRWMxXnnl.png

// DataSouree Layer
const mockDbData = [
    {
        id: 1,
        username: 'mark',
        age: 19,
        company: 'hahow'
    },
    {
        id: 2,
        username: 'ian',
        age: 19,
        company: 'amazon'
    }
]
function executeSql(sql: string){
    return mockDbData
}

class PersonModel{
    id: number 
    username: string
    age: number
    company: string
    constructor(id: string,username: string, age: number, company: string)
    constructor(username: string, age: number, company: string)
    update(): void{
        console.log(`UPDATE person SET username=${this.username}, age=${this.age}, company=${this.company} WHERE id=${this.id}`)
    }
    insert(): void{
        console.log(`INSERT INTO person (username, age, company) VALUES(${this.username}, ${this.age}, ${this.company})`)
    }
    // Domain Logical
    isAdult(): boolean{
        return this.age >= 18
    }
		// Domain Logical
    isVIP(): boolean{
        return ['HAHOW','GOOGLE','KKBOX'].includes(this.company)
    }

    static findByCompany(company: string): PersonModel[]{
        const result: PersonModel[] = []
        const sql = `SELECT * FROM person WHERE company=${company}`
        const resultSet = executeSql(sql)
        for (const data of resultSet) {
            result.push(new PersonModel(data.id, data,username, data.age, data.company))
        }
        return result 
    }
}
// Domain Layer
class PersonTableModule{
    register(username: string, age: number , company: string){
        // 假设有个业务需求为公司有限定人数 limit = 10
        const personsInCompany: PersonModel[] = PersonModel.findByCompany(company) 
        if(personsInCompany.length >= 10) throw Error('公司人数已达限制')

        const person: PersonModel = new PersonModel(username, age, company)
        if(!person.isAdult()) throw Error('要成年才能注册喔')
        person.insert()
    }
}

小总结

这个知识点可以用来解释什麽现象

这个模式基本上就是以 row 为单位,来进行资料操作与业务,这个在简单的业务专案使用真的快速放便,但是我觉得这应该是架构师最难抓的,要如何判断未来会不会复杂,会不会 overdesign,不知道有没有什麽标准呢 ?

这个知识点可以和以前的什麽知识连结呢 ?

首先来谈谈 Domain Model,它是一个 Domain Layer 的模式,一个实体就是一笔资料,然後他里面有很多业务逻辑与资料,只是他与 Active Record 的差别我觉得在於,Domain Model 应该是没有限定一个 table,而是以 domain 为主,这我个人的看法 ~ 不一定对。

而 RowDataGateway 就是一个实体代表一个 row,但与 Active Record 不同处在於,它里面没有业务逻辑,而且好像有读写分离,整理上我个人是比较喜欢 RowDataGateway。

最後还有一个连结,那就是 ORM,ORM 本质上是与资料库的对应实体,并且应该是不包含业务逻辑,所以两者应该是不相同,但我觉得在 Active Record 中可以使用 ORM。

我要如何运用这个知识点 ?

  • 现在看到 Active Record 终於可以明确知道它的定义罗 ~ 这样在看一些文章时,总算可以判断对方写的有没有问题。
  • Active Record 我个人是比较不建议用,但我可以反思,为什麽当初会有它产生呢 ? 弹性、快速 ? 我觉得也有可能是那个时代的软件开发模式说不定就是以 Table 来开始发想 Domain 呢,我猜的。

参考资料


<<:  [Day 17] 定义资料 — 讲清楚很难吗?

>>:  Day18:今天来聊一下使用Microsoft 365 Defender 缓和incidents

Day 01:前言

先来自我介绍一下好了,我是刚转职踏入软件业的全端菜鸟工程师,也是第一次参加铁人赛。 相信许多还在努力...

D10. 学习基础C、C++语言

D10: 简单的练习UVA(11805) #include <stdio.h> #inc...

用Firebase Web的小功能分享 (3)

上传档案後制作超连结下载档案 - 用innerHTML制作超连结 code 最後就是如何显示map...

Day-29 跳页

在过去撰写的程序都是以单页的形式呈现, 但实际上架的APP多不只一页, 那要如何从A页跳至B页? 这...

Day 5 - 断点设定

Tailwindcss 使用 normalize.css 来当作初始化样式,和 Bootstrap...