作者:Steam & Hao
本文整理自社群第 7 期會議中 13‘21″ 到 44’11″ 的 Python ORM 的分享,影片見 https://www.bilibili.com/video/BV1s8411N7Cw
在做業務開發時,NebulaGraph Python ORM 專案作者:Sword Elucidator(下文簡稱:Hao)發現圖資料庫在某些場景下有比較不錯的應用實踐,而 NebulaGraph 是他覺得不錯、較為先進的一款圖資料庫產品。在 Hao 的開發過程中,他發現:雖然圖資料庫被應用在多個業務場景中,但對於像是 App 開發之類的 ISO/OSI 高層實踐的話,nebula-python 之類的客戶端就略顯笨重。
而 ORM 作為一個能簡化 CURD 操作、免去繁瑣的查詢語句編寫的存在,是被廣大的 Web 開發者所熟知。但是,目前 NebulaGraph 社群有 Golang 版本的 ORM norm、Java ORM NGBatis 和 graph-ocean 唯獨沒有 Hao 所熟悉的 Python 語言的 ORM。
於是,做一個 NebulaGraph Python ORM 的想法便誕生了。
NebulaGraph Python ORM
Nebula Carina 名字的由來
NebulaGraph Python ORM,又名 nebula-carina,雖然目前只是一個雛形,但是已經基本上具備了一個 ORM 的基礎功能。在命名 Python ORM 專案之時,Hao 先想到了 nebula-model,見名便知這是一個 ORM,搞了一些封裝。但它不夠優雅(cool),所以 nebula-carina 便誕生了。
Carina 船底座,/kəˈriːnə/,意為龍骨,是南半球可見最大的星雲。而一個元件能成為一個 Nebula(星雲)還挺酷的。
Python ORM 功能設計
Nebula Carina 是用 Python 開發的針對 NebulaGraph + Python 的 ORM 框架。在設計上沒有侷限於 Web 框架,因此可以被應用在 Django、FastAPI 和 Flask 等主流框架上。
目前,Nebula Carina 包含了常規的 schema 定義、物件管理器 object manager(雛形)、Model Builder(雛形),以及常見的圖語言、MATCH 語句封裝。雛形的意思是,這些功能具備了,但是暫時只有一、兩個方法在裡面,歡迎閱讀本文的你一起來完善。除了基礎功能之外,Nebula Carina 還支援了簡單的 migration 功能,能夠自動計算 schema model 結構與 DB schema 的差異,並同步 schema 到當前 space。但相較於其他成熟的 ORM 專案,例如:Django ORM,Nebula Carina 缺少可回溯性及樹狀結構來支援 migration 包含依賴、merge 資料。所以,Nebula Carina 未來考慮設計和支援包含依賴關係的 migration 系統。如果你對此有興趣的話,歡迎來專案:https://github.com/nebula-contrib/nebula-carina issue 區交流下。
Python ORM 的神奇之處
上面簡單說了下 Nebula Carina 是什麼,有什麼功能。在這裡,我們來解決下“為什麼要用 Nebula Carina”的問題。
Nebula Carina 首要應對的問題是快速解決輕量級 App 開發的常規需求,雖然 NebulaGraph 具有極好的效能,諸如美團、快手等大企業都在使用。但大企業和小公司不同,大企業用圖資料庫會用非常重,像美團就直接開發了個圖平臺對接集團上百的業務線。而小公司的輕量級應用來說,它需要一個快速地生成簡潔 schema。小公司的 Web 開發人員能非常容易地定義常用的、供於業務邏輯使用的 schema,再一鍵快速將 schema 同步到 space,而不需要去寫些 SQL(這裡指的是查詢語句)來處理這些事項。此外,應對小公司的輕量級 App 開發需求,還需要支援 JSON 序列化和逆序列化來簡化介面,不需要在介面處封裝各類東西。最後,也是最重要的,為什麼不用 Golang 之類的語言 ORM。Nebula Carina 採用了易於使用的 Python Data Model。Python 使用人員可以方便地用 Python 來呼叫、控制程式,像是列印,或者是在 Python Model 裡面將 Dictionary 展開時擁有的 fields 都可以符合標準 Python 規範進行使用。
此外,除了適用於任何的 Python Web Framework,Nebula Carina 也適用於裸 Python 開發,可與 AI 行業快速整合。畢竟像是 Machine Learning 之類的,十有八九是 Python 語言搞的,Nebula Carina 就可以輕鬆應用在 GNN、NLP 這些用圖資料比較多的技術領域。
總之,Nebula Carina 讓 Python 開發者使用 NebulaGraph 時能把更多精力運用在業務/模型上,而非繁瑣的資料庫操作。
Python ORM 設計實現
目前,Carina 的實現比較簡單粗暴,由 4 個部分組成:settings、nGQL 層、model 層和其他。
settings,搞環境變數。
nGQL 層,有 connection、query、record、schema 和 statement:
- query,主要是 condition / match / … 語句封裝
- record,主要是 vertex / edge 語句封裝
- schema,封裝了 Data Types、schema 語句、space 語句封裝
- statements,主要是 Order By、Limit、Edge 定義、Edge Value、TTL、Alter 等 statements 的語句封裝,statement 的意思是 state 某類行為;
model 層,主要是呼叫 nGQL 層的封裝的 class 和當中的方法,來解決一些具體上層的問題。它包括 nebula-python 的 vertex / edge 轉成 Carina 的 vertex model / edge model 的 protocol,以及 field 和 model(schema model & data model)的封裝,同絕大數程式語言的 ORM 類似,定義成某類語言常見的 class 進行封裝,參見下方 Figure 類的示例說明;
class Figure(models.TagModel):
name: str = _(data_types.FixedString(30), ..., )
age: int = _(data_types.Int16, ..., )
valid_until: int = _(data_types.Int64, None, )
hp: int = _(data_types.Int16, 100, )
style: str = _(data_types.FixedString(10), 'rap', )
is_virtual: bool = _(data_types.Bool, True)
created_on: datetime = _(data_types.Datetime, data_types.Datetime.auto)
some_dt: datetime = _(data_types.Datetime, datetime(2022, 1, 1))
上述示例程式碼,用 Figure class 繼承 TagModel,在當中定義 tag 所需的這些 field,比如:name、age…Carina 就是採用這種方式來處理 NebulaGraph 中 Schema 結構;
model 層中的 model builder 則是位於 nGQL 和純 model 層之間的橋樑。它可以用來描述高層和低層之間的某種行為,比如說,下面的程式碼就定義了一個全域性 MATCH 語句,而所有的 MATCH 語句都會走這樣一個函式同 nGQL 層互動:
def match(
pattern: str, to_model_dict: dict[str, Type[NebulaConvertableProtocol]],
*, distinct_field: str = None,
condition: Condition = None, order_by: OrderBy = None, limit: Limit = None
) -> Iterable[SingleMatchResult]: # should be model
output = ', '.join(
("DISTINCT " if key == distinct_field else "") + key
for key in to_model_dict.keys()
)
results = match(pattern, output, condition, order_by, limit)
return (
SingleMatchResult({
key: to_model_dict[key].from_nebula_db_cls(value.value)
for key, value in zip(results.keys(), row.values) if key in to_model_dict
}) for row in results.rows()
)
而 model 層的 object manager 會根據應用場景,基於 schema 為出發點,對 model builder 具體 match 語句進行操作,對這些操作行為搞了個高階封裝;migrations 則負責封裝 schema model 的變更並同步給資料庫;
在其他模組,則是 Django 適配的 apps 和 setting。因為要支援 Django,它的思路同 FastAPI 不同,所以需要做適配來讓 Carina 無縫銜接 Django;
Nebula Carina 使用
下面舉些例子來讓大家瞭解下 Carina 的使用,主要還是摘錄自 Carina 的 README:https://github.com/nebula-contrib/nebula-carina
安裝 Nebula Carina
一句命令搞定
pip install nebula-carina
如果你用的是 Django,那麼需要將 nebula_carina 新增到 INSTALLED_APPS,像是這樣:
INSTALLED_APPS = [
...
'nebula_carina',
...
]
再在 settings.py
檔案中設定 CARINA_SETTINGS,主要配置一些同 NebulaGraph 有關的資訊。像是這樣:
CARINA_SETTINGS = {
"auto_create_default_space_with_vid_desc": "FIXED_STRING(20)", #建立預設圖空間
"default_space": "main", #圖空間名
"max_connection_pool_size": 10, #連線數大小
"model_paths": ["nebula.carina"], #model 路徑
"user_name": "root", #登陸 NebulaGraph 的使用者名稱
"password": "1234", #登陸 NebulaGraph 的密碼
"servers": ["192.168.31.248:9669"], # NebulaGraph graphd 服務所在伺服器資訊,可配置多個
"timezone_name": "UTC", #伺服器所用時區
}
目前 Carina 只有支援上述資訊,後續會再增加其他欄位。
如果你用的是 FastAPI 之類的,用環境變數即可,具體的話可以參考專案文件:https://github.com/nebula-contrib/nebula-carina#by-environment-variables。
圖空間建立
你可以透過下面 Python 語句來建立 Space,當然你也可以像上面 CARINA_SETTINGS 一樣,用 "auto_create_default_space_with_vid_desc": "FIXED_STRING(20)"
自動建立一個預設圖空間。
from nebula_carina.ngql.schema.space import create_space, show_spaces, VidTypeEnum
main_space_name = "main"
if main_space_name not in show_spaces():
create_space(main_space_name, (VidTypeEnum.FIXED_STRING, 20))
點邊 schema 定義
同點 vertex 不同,一條邊只有一個 edgetype,而一個點可以擁有多個 tag。所以在 Carina 中,Model 層的封裝,models.py
檔案裡引入了 VirtualCharacter
的概念,在 VirtualCharacter
類裡,定義這個點擁有那些 tag。
class VirtualCharacter(models.VertexModel):
figure: Figure
source: Source
一個 figure
就是一個 tag,source
是另外一個 tag 的名字。這裡 Figure 和 Source 都是具體的某個 tag 在 Carina 中的對映類名,在示例中,它就叫 Figure、Source。
點邊資料操作
上文提過 VirtualCharacter
的概念,在 Data Model Mathod 裡,像下面這種程式碼:
VirtualCharacter(
vid='char_test1', figure=Figure(
name='test1', age=100, is_virtual=False, some_dt=datetime(2021, 3, 3, 0, 0, 0, 12)
), source=Source(name='movie1')
).save()
是定義了一個 VID(唯一標識)為 char_test1 的點,它擁有個名為 Figure 的 tag,這個 tag 中有 name、age、is_virtual 之類的屬性。此外,它還有一個 tag Source,Source tag 的屬性 name 是 movie1。而 .save() 則是儲存這段程式碼。
同點類似,邊的定義是這樣的:
EdgeModel(src_vid='char_test1', dst_vid='char_test2', ranking=0, edge_type=Love(way='gun', times=40)).save()
這個語句主要表達了一條邊的起點是 char_test1,終點是 char_test2,邊的 rank 是 0,型別是 Love。而 Love 邊型別有 2 個屬性 way 和 times,? 也許這是一對相殺相愛的戀人,滾了 40 次。
Nebula Carina 再升級
因為個人能力有限,在這裡希望藉助大家的力量。對 Nebula Carina 的未來規劃,主要集中在這些方面
- connection pool (v3.3.0)
- Indexes
- Go / Fetch / Lookup statements封裝
- Bulk操作封裝
- Generic Vertex Model
- advanced migrations
…
nebula-python 在 v3.3.0 中對 connection pool 做了原生支援,希望在未來 Carina 能結合這塊內容更加完善。
再者就是索引,上面其實提到過,Carina 目前就封裝了 MATCH 語句,後續將會對 LOOKUP、GO、FETCH 之類的 statement 字句進行封裝。
然後是 Bulk 操作的封裝,可以處理一次性建立大量資料。
Generic Vertex Model 則是再抽象 vertex,使用者不需要告訴程式它想得到什麼樣的 vertex,它的結構是如何的。直接透過虛擬結構進行定義,像是上面提到的 Figuer 和 Source,現在我不定義了,Generic Vertex Model 可以把這塊抽象好,自己就搞定了。
最後,之前也提到過的 advanced migrations,樹狀的 migration 可以搞定依賴關係。
以上,便是 Hao 貢獻的 NebulaGraph Python ORM 的簡單介紹。如果你有改進、最佳化它的 idea,歡迎來 Carina issue 和 pr 區交流喲 https://github.com/nebula-contrib/nebula-carina/issues/new~
謝謝你讀完本文 (///▽///)
NebulaGraph Desktop,Windows 和 macOS 使用者安裝圖資料庫的綠色通道,10s 拉起搞定海量資料的圖服務。通道傳送門:http://c.nxw.so/c0svX
想看原始碼的小夥伴可以前往 GitHub 閱讀、使用、(^з^)-☆ star 它 -> GitHub;和其他的 NebulaGraph 使用者一起交流圖資料庫技術和應用技能,留下「你的名片」一起玩耍呢~