天天看點

當 GraphQL 遇上圖資料庫,便有了更友善查詢資料的方式

作者:NebulaGraph
當 GraphQL 遇上圖資料庫,便有了更友善查詢資料的方式

人之初,性本鴿。

大家好,我叫儲惠龍(實名上網),你可以叫我小龍人,00 後一枚。目前從事後端開發工作。

今天給大家帶來一個簡單的為 NebulaGraph 提供 GraphQL 查詢支援的 DEMO,為什麼是簡單的,因為本來想完成更多工作再給大家介紹的,但是上個月太忙加上下個月更忙,但是我又很想白嫖一下 Nebula 官方的獎品,是以就趕緊端上來了。

當 GraphQL 遇上圖資料庫,便有了更友善查詢資料的方式

體驗 NebulaGraphQL

先上項目位址:https://github.com/Dragonchu/NebulaGraphQL

GraphQL 是什麼

先簡單介紹一下 GraphQL,https://graphql.cn/ 詳細的資訊官方介紹得都很清晰。說一下我的了解,GraphQL 并不是對标 Cypher 這種查詢語言,而是對标 REST 的一種 API 設計風格。

是以,嚴格意義上,不是說使用 GraphQL 查詢圖資料庫,而是使用一種 GraphQL 風格的 API 查詢圖資料庫,或者說是将 Cypher 封裝了一樣。這個本質工作和大家做應用開發時,基于 NebulaGraph 寫一些通過的 REST 接口是一樣的。

API 查詢示例

本文的測試資料集使用的 NebulaGraph 官方的 basketballplayer 資料集 https://docs.nebula-graph.io/2.0/basketballplayer-2.X.ngql

舉個例子,如果我想 “根據科比的名字得到科比的全部資訊”,可能會使用下面這樣的 nGQL 語句:

LOOKUP ON player WHERE player.name == "Kobe Bryant" YIELD id(vertex) as vertexId | FETCH PROP ON player $-.vertexId YIELD properties(vertex);
           

雖然說 nGQL 已經很友善閱讀了,但是如果讓一個完全 0 基礎的萌新來看也是看不懂的,并且這個語句的傳回值是不明确的,至少沒有辦法從查詢看到結果。而傳回值的解析一直也是很多人的痛苦。

那麼,來看看使用 GraphQL 查詢同一場景會是什麼情況。

查詢語句會是:

{
	players(name:"Kobe Bryant"){
		name
		age
	}
}
           

傳回結果是:

{
	players=[{name=Kobe Bryant, age=40}]
}
           

看看這優雅的查詢和傳回結果,想必我不多說,大家也都看得懂。這真的是

當 GraphQL 遇上圖資料庫,便有了更友善查詢資料的方式

其實上面說了那麼多,就是官方對 GraphQL 的總結:描述你的資料、請求你所要的資料、得到可預測的結果。

上述的查詢在 NebulaGraphQL 中已經實作了,同時還支援通過 VertexID 查詢資料(好吧,我也就實作了這兩種)。

NebulaGraphQL 簡單入門

NebulaGraphQL 是一個 Java 庫,旨在應用層提供使用 GraphQL 文法查詢 NebulaGraph 圖資料庫中資料的能力。

在項目中使用 NebulaGraphQL 非常簡單,因為 NebulaGraphQL 本身隻想做一個簡單的工具庫,未來如果想直接內建到 MVC 架構可能會再起一個 NebulaGraphQL-Spring 之類的項目(畫大餅中……)。是以 NebulaGraphQL 的使用和 nebula-java 是幾乎完全一緻的。

使用示例:

//建立一個config
GraphqlSessionPoolConfig graphqlSessionPoolConfig = new GraphqlSessionPoolConfig(
                Lists.newArrayList(graphdAddress),
                Lists.newArrayList(metadAddress),
                spaceName, username, password);
//建立一個連接配接池
GraphqlSessionPool pool = new GraphqlSessionPool(graphqlSessionPoolConfig);
//執行語句
ExecutionResult executionResult = pool.execute("{players(age:32){name\nage}}");
//擷取結果
System.out.println(executionResult.getData().toString());
           

其實 GraphSessionPool 内部就是使用的 nebula-java 的 SessionPool,所有配置都和使用官方提供的連接配接池一緻,唯一的差別是需要額外提供 metad 的連接配接位址。這是因為 NebulaGraphQL 是通過 metad 來自動建構 Schema 的,NebulaGraphQL 會在建立連接配接池時自動建立 Schema。

NebulaGraphQL 的實作

NebulaGraphQL 主要是基于 graphql-java 實作的。而使用 graphql-java,大家可以根據自己的項目定義自己的 GraphQL 的 Schema。不過,NebulaGraphQL 想盡可能地提供一些通用功能,并且一定是根據 NebulaGraph 的 Schema 自動建構的。

在建立 GraphqlSessionPool 時,NebulaGraphQL 通過連接配接 NebulaGraphQL 的 metad 将 NebulaGraph 中的中繼資料資訊構造成 GraphQL 的 Schema 資訊。這一部分是關鍵難題。目前,我僅僅做了如下的變換:

  1. 對于 NebulaGraph 中所有的 Tag,都會構造一個對應的 GraphQL 的可查詢對象。
  2. 每一個 Tag 都會有一個同名的根據 ID 擷取資訊的查詢。舉例來說,對于 player 這個 tag,會生成一個查詢 player,這個查詢的參數是 vertexID,會根據 vertexID 擷取到資訊。
  3. 每一個 Tag 都會有一個在名稱後加 -s 的查詢。舉例來說,對于 player 這個 tag,會生成一個查詢 players,這個查詢的參數是任意的屬性。如果 player 上有 age,name,country 這些屬性,在查詢參數中可以傳入這三種屬性的任意組合,NebulaGraphQL 查詢時會将這些參數進行 “與” AND 語義的構造,再擷取相關頂點。對于使用者沒有指定的參數,預設為 null(這是一個已知的問題,如果目的就是查 null 會有問題)。

測試資料集上自動生成的 GraphQL 的 Schema 示例:

type Query {
 player(
 "Vertex ID"
 ID: ID
): player
players(age: Int = null, name: String = null): [player]!
team(
"Vertex ID"
ID: ID
): team
 teams(name: String = null): [team]!
}
type player {
age: Int
name: String
}
type team {
name: String
}
           

簡單講解一下,Query 是 GraphQL 的查詢入口,其中的 player, players, team, teams 都是自動生成的查詢,可以當作查詢語句。

player 是根據 VertexID 查詢并傳回一個 player,player 後面沒有 ! 辨別符,說明可能查詢結果為空。players 查詢有兩個參數,對應着 player 這個 tag 的兩個屬性 age 和 name,這兩個參數的類型都從 NebulaGraph 中的資料類型映射到了 GraphQL 的資料類型,預設值都為 null,傳回值是一個清單。清單後的 !,說明一定傳回一個清單,但是其中的 player 後沒有 ! 辨別符,指的是可能傳回一個空清單。

使用 players 查詢,參數可以指定 age 或者 name,或者 age 和 name 一起指定。

下面的 player 和 team 兩個 type 就表示了這兩個對象有什麼屬性,可以在查詢時指定傳回的屬性,NebulaGraphQL 在傳回結果時就隻會提供查詢需要的屬性。

每一個 GraphQL 的查詢會有一個綁定的 DataFetcher 對象,該對象中實作的就是如何将 GraphQL 文法映射成 nGQL 語句,并執行插叙傳回結果。而傳回結果會映射到自定義對象上,這裡使用了我另一個小工具 NebulaResultBoot 将執行結果映射到自定的對象上後,我們就可以在未來實作應用層的緩存,當然這裡也有一個潛在的問題:每一次查詢都要求擷取到每一個點邊的所有屬性,這部分未來需要考慮優化。

當然,NebulaGraphQL 的目标不隻是簡單将 nGQL 映射到 GraphQL 這麼簡單,因為 GraphQL 除了查詢簡單這個很明朗的特點,還可以更輕松的支援權限管理,以及通過 DataLoader 機制在應用層實作一層緩存。不過,我這邊目前也沒有研究的很透徹,如果有大佬願意加入一起實作那就最好不過了。

為了友善大家貢獻(主要是我懶),NebulaGraphQL 的開發測試已經完成了一部分的容器化了,将代碼庫克隆到本地後,本地隻需要有 Docker,然後在倉庫根目錄下運作

docker-compose -f docker-compose.dev.yml up --build
           

就可以看到在自動跑測試了,會在本地啟動 NebulaGraph 叢集并自動插入測試的資料,後續會繼續優化這方面的流程。

小結

NebulaGraphQL 提供了更簡單的查詢語句,這個查詢語句的構造應該是讓前端直接提供的,GraphQL 的優勢之一就是可以讓前端選擇自己需要的資料進而避免 “接口地獄”,可能會有人認為這相當于讓前端直接通路資料庫了。是的,我覺得這個了解也确實沒問題,這也是有人反對 GraphQL 的理由,不過這裡就不繼續讨論了。

但是使用 GraphQL 有一個潛在優勢,也就是可以更輕松的将圖資料庫和關系型資料庫整合在一起。當然如果隻是使用圖資料庫的話,那使用 NebulaGraphQL 至少也能友善做一些簡單的資料查詢與測試。

當 GraphQL 遇上圖資料庫,便有了更友善查詢資料的方式

對于 NebulaGraphQL 在實際生産中的價值目前我也沒有體驗過,因為純屬興趣寫着玩兒,如果大家有想法,歡迎在評論區交流。

繼續閱讀