未來(lái)幾周,一個(gè)新的開(kāi)源對(duì)象關(guān)系數(shù)據(jù)庫(kù)EdgeDB將發(fā)布其首個(gè)公開(kāi)預(yù)覽版。先讓我們了解一下構(gòu)建EdgeDB的原因。
動(dòng)機(jī)
數(shù)據(jù)庫(kù)一直是,并將永遠(yuǎn)是任何技術(shù)堆棧的決定性部分。在過(guò)去的十年中,數(shù)據(jù)庫(kù)市場(chǎng)發(fā)生了很多變化。在10年前,沒(méi)有MongoDB,沒(méi)有云數(shù)據(jù)庫(kù),甚至云本身也是一個(gè)相對(duì)較新的概念。今天,有大量的數(shù)據(jù)庫(kù)解決方案使用非關(guān)系數(shù)據(jù)模型,承諾使數(shù)據(jù)可擴(kuò)展和對(duì)開(kāi)發(fā)人員友好。
但顯然后半部分變得難以捉摸。NoSQL數(shù)據(jù)庫(kù)的文檔使初始無(wú)模式原型設(shè)計(jì)階段變得簡(jiǎn)單,但是隨著時(shí)間的推移往往會(huì)增加技術(shù)負(fù)累。保持總體數(shù)據(jù)和模式一致性以及對(duì)無(wú)模式數(shù)據(jù)執(zhí)行復(fù)雜分析要比傳統(tǒng)RDBMS難得多。
如果有人為他們的新項(xiàng)目選擇數(shù)據(jù)庫(kù)解決方案,他們很可能會(huì)選擇一個(gè)關(guān)系數(shù)據(jù)庫(kù)。因?yàn)榭深A(yù)測(cè)的性能,強(qiáng)大的查詢語(yǔ)言以及一致性保證是所有RDBMS在市場(chǎng)上占主導(dǎo)地位的原因,并且在很大程度上不受質(zhì)疑。
然而,關(guān)系數(shù)據(jù)庫(kù)是建立在一個(gè)數(shù)十年前的模型之上,并且越來(lái)越不適合迅速轉(zhuǎn)變的軟件開(kāi)發(fā)領(lǐng)域。當(dāng)然,PostgreSQL正在不斷地進(jìn)行改進(jìn),提供諸如高級(jí)JSON支持,查詢并行化和JIT編譯器等功能,以進(jìn)一步提高復(fù)雜查詢的性能。但是,應(yīng)用程序與關(guān)系數(shù)據(jù)庫(kù)的接口方式?jīng)]有發(fā)生有意義的變化。我們?nèi)匀皇褂镁徛腛RM,與模式遷移較勁,并編寫(xiě)糟糕的臨時(shí)SQL查詢。
對(duì)象關(guān)系
大多數(shù)軟件工程師不會(huì)考慮表格。我們的編程語(yǔ)言是圍繞類型和對(duì)象等更高級(jí)別的抽象構(gòu)建的。如果你有兩個(gè)對(duì)象A和B,并且想引用另一個(gè)對(duì)象,那么你只需寫(xiě)Ab = B。為了在關(guān)系數(shù)據(jù)庫(kù)中做同樣的事情,你需要處理外鍵,連接,有時(shí)還需要處理中間表,本質(zhì)上直接在低得多的抽象層次上工作。
關(guān)系數(shù)據(jù)庫(kù)和現(xiàn)代編程語(yǔ)言之間的這種不一致已為人所知,甚至有一個(gè)名稱:面向?qū)ο缶幊膛c關(guān)系型數(shù)據(jù)庫(kù)間的不一致(object-relational impedance mismatch)。這就是ORM如此受歡迎的原因,以及為什么對(duì)GraphQL等技術(shù)有如此多的興趣。這些解決方案的問(wèn)題在于他們?cè)噲D彌合與應(yīng)用方面的差距,要么犧牲底層數(shù)據(jù)庫(kù)的表現(xiàn)力,要么實(shí)施過(guò)度復(fù)雜的映射,這在很大程度上破壞了目的。雖然業(yè)界似乎癡迷于解決規(guī)模問(wèn)題,但并沒(méi)有認(rèn)真嘗試解決數(shù)據(jù)庫(kù)方面的對(duì)象關(guān)系不匹配問(wèn)題。
EdgeDB
EdgeDB是下一代對(duì)象關(guān)系數(shù)據(jù)庫(kù)。它實(shí)現(xiàn)了一個(gè)對(duì)象圖模型,而不是關(guān)系模型。在這個(gè)模型中,數(shù)據(jù)被描述和存儲(chǔ)為強(qiáng)類型對(duì)象和關(guān)系,或者它們之間的鏈接。對(duì)象和鏈接可以保存屬性:一組命名的標(biāo)量值。這個(gè)模型有時(shí)被稱為屬性圖模型。
EdgeDB不是圖數(shù)據(jù)庫(kù):數(shù)據(jù)使用關(guān)系數(shù)據(jù)庫(kù)技術(shù)存儲(chǔ)和查詢,并且需要嚴(yán)格的模式。
EdgeDB不是一個(gè)文檔數(shù)據(jù)庫(kù),但插入,修改和查詢層次化的文檔類數(shù)據(jù)是微不足道的。
EdgeDB不是傳統(tǒng)的對(duì)象數(shù)據(jù)庫(kù)。盡管“object-relational”中有“object”這個(gè)詞,但它不是OOP持久性或封裝的實(shí)現(xiàn)。它具有表達(dá)式查詢語(yǔ)言EdgeQL,其目標(biāo)是匹配并超越現(xiàn)代SQL功能,如子查詢,高級(jí)聚合和窗口函數(shù)。
EdgeDB基于PostgreSQL,并繼承了它的所有優(yōu)勢(shì):可靠性,ACID合規(guī)性和性能。
一個(gè)例子
為了讓我們對(duì)EdgeDB有一個(gè)小小的了解,我們來(lái)定義一個(gè)簡(jiǎn)單的模式,用我們的模式DSL來(lái)描述一個(gè)基本的類似GitHub的應(yīng)用程序:
# Define a string enumerated type for
# pull request status.
scalar type pr_status extending str:
constraint enum('Open', 'Closed', 'Merged')
# Pull request object type definition.
type PullRequest:
required property title -> str
required property status -> pr_status:
default := 'Open'
# Pull request "author" as a to-one
# link to a User object.
required link author -> User
# Many-to-many relationship with
# different User objects.
link assignees -> User:
cardinality := '**'
type User:
required property name -> str
link followees -> User:
cardinality := '**'
現(xiàn)在讓我們來(lái)看看EdgeQL中的一個(gè)簡(jiǎn)單查詢:
SELECT User {
id,
name,
followees: {
id,
name
}
}
FILTER
User.name = 'Alice';
查詢將返回名稱為“Alice”的所有用戶以及她關(guān)注的所有用戶的列表。數(shù)據(jù)可以以客戶端編程語(yǔ)言中的豐富對(duì)象或JSON的形式返回。以下是可比較的SQL查詢的樣子:
SELECT
users.id,
users.name,
array_agg(followees.id) AS followee_ids,
array_agg(followees.name) AS followee_names
FROM
users
LEFT JOIN user_followees ON
user_followees.user_id = users.id
LEFT JOIN users AS followees ON
followees.id = user_followees.followee_id
WHERE
users.name = 'Alice'
GROUP BY
users.id, users.name;
請(qǐng)注意,此SQL查詢效率不高。有經(jīng)驗(yàn)的開(kāi)發(fā)人員會(huì)重寫(xiě)它以使用子查詢。另外,要將查詢的結(jié)果作為JSON,需要更多的努力。
下面是一個(gè)稍微更高級(jí)的EdgeQL查詢示例,它顯示了聚合和反向鏈接導(dǎo)航。
SELECT User {
name,
# Count the number of users who follow this user
# by traversing the "followees" link *backwards*,
# as denoted by '.<'
followed_by_count := count(User.<followees),
# Count the number of users that this user is
# following by traversing the "followees" link
# *forwards*, as denoted by '.>' or simply '.'
follow_count := count(User.followees),
# Get a set of open pull requests that this
# user is the assignee of.
open_prs := User.<assignee[IS PullRequest] {
title
} FILTER .status = 'Open'
};
示例JSON輸出:
[
{
"name": "Alice",
"followed_by_count": 101,
"follow_count": 45,
"open_prs": [
{
"title": "Implement support for window functions."
},
...
]
},
...
]