接下来要这篇文章要来谈谈很常听到的『 Active Record 』。
根据 《 Patterns of Enterprise Application Architecture - Martin Fowler 》书中的定义如下 :
An object carries both data and behavior.
对,基本上就是在 DataSource 层里,然後每个实体代表一个 table row,且实体内包含资料与行为 ( domain 逻辑 ) ,这和上一章的 RowDataGateway 很像,都是它缺了行为。下图就是书中的范例物件 UML 图。没看错。
图片来源: 《 Patterns of Enterprise Application Architecture - Martin Fowler 》
我觉得不是,我觉得作者本意不是,不然为什麽还要有 Domain 层呢 ? 并且还将 Active Record 放在 DataSource 层,我觉得他应该是指将一些简单的 domain logic 放在 active record 中,例如下述范例中的 isPass。
class StudentTest{
id: string
stuentId: string
score: number
isPass(): boolean{
return score >= 60;
}
}
作者只建议在以下的情境下使用 :
并且他还要说到一句话,它事实上没有以上的情况不要用 :
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 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
// 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。
>>: Day18:今天来聊一下使用Microsoft 365 Defender 缓和incidents
先来自我介绍一下好了,我是刚转职踏入软件业的全端菜鸟工程师,也是第一次参加铁人赛。 相信许多还在努力...
D10: 简单的练习UVA(11805) #include <stdio.h> #inc...
上传档案後制作超连结下载档案 - 用innerHTML制作超连结 code 最後就是如何显示map...
在过去撰写的程序都是以单页的形式呈现, 但实际上架的APP多不只一页, 那要如何从A页跳至B页? 这...
Tailwindcss 使用 normalize.css 来当作初始化样式,和 Bootstrap...