連接 AI,NebulaGraph Python ORM 項目 Carina 簡化 Web 開發

作者: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 NGBatisgraph-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 個部分組成:settingsnGQL 層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 用戶一起交流圖數據庫技術和應用技能,留下「你的名片」一起玩耍呢~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章