GunDB:一個(gè)去中心化的圖數(shù)據(jù)庫(kù)

原文鏈接:GunDB, a Graph Database in JavaScript

最近我一直在使用GunDB,想和你分享我目前學(xué)到的一些東西。GunDB不僅僅是一個(gè)圖形數(shù)據(jù)庫(kù)(Graph DB)。它更是一個(gè)項(xiàng)目組,旨在簡(jiǎn)化擴(kuò)展,提高數(shù)據(jù)安全性,節(jié)約成本,并賦予應(yīng)用程序開(kāi)發(fā)人員更多的能力。

在這篇文章中,我將完全把Gun作為一個(gè)數(shù)據(jù)庫(kù)來(lái)探討,并在接下來(lái)的文章中隨著我的進(jìn)展和學(xué)習(xí),探索其他方面。

所有文章中的源碼在這里可以找到。

簡(jiǎn)介

一般來(lái)說(shuō),數(shù)據(jù)庫(kù)是一個(gè)軟件,安裝在你的電腦或遠(yuǎn)程服務(wù)器上,用來(lái)存儲(chǔ)數(shù)據(jù)。這些數(shù)據(jù)可以存儲(chǔ)在磁盤(pán)上或內(nèi)存中。

數(shù)據(jù)庫(kù)有不同的類(lèi)型:關(guān)系型(Relational DB)、面向文檔型(Document DB)、鍵值型(Key-value DB)或圖形型(Graph DB)。下面是一些例子:

  • 關(guān)系型:MySql, PostgreSQL, SQL Server
  • 面向文檔的:MongoDB, CouchDB
  • 鍵值型:Redis, LevelDB
  • 基于圖形:Neo4j、OrientDB

與其他數(shù)據(jù)庫(kù)不同,Gun沒(méi)有二進(jìn)制文件需要安裝,Gun是用JavaScript編寫(xiě)的,這意味著你可以在任何運(yùn)行JavaScript的地方使用它。開(kāi)始使用Gun就像下載一個(gè)JavaScrip文件或用npm安裝一個(gè)插件一樣容易。

開(kāi)始使用

開(kāi)始使用Gun的最簡(jiǎn)單方法是將其作為一個(gè)單一的JavaScript文件下載。你可以從以下網(wǎng)址下載最新的簡(jiǎn)化版本:https://rawgit.com/amark/gun/master/gun.min.js

按下面的方式加載到html文件中:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <script src="gun.min.js"></script> <!-- import Gun -->
  <title>Gun Basics</title>
</head>
<body>
</body>
</html>

然后你可以在瀏覽器中打開(kāi)Html文件,并使用控制臺(tái)consoleGun互動(dòng)。在大多數(shù)瀏覽器中,你可以在頁(yè)面上點(diǎn)擊右鍵,在彈出菜單中選擇檢查元素來(lái)打開(kāi)控制臺(tái)。下面例子可以用它來(lái)創(chuàng)建一個(gè)汽車(chē)記錄并打印它的制造商。

const db = window.Gun();
const car = db.get("123").put({
  make: "Toyota",
  model: "Camry",
});
car.once(v => console.log(v.make)); // --> Toyota

在上面的代碼中,首先,我們創(chuàng)建一個(gè)Gun的實(shí)例。然后,使用123為鍵key的get方法來(lái)引用一個(gè)空節(jié)點(diǎn)。接下來(lái),我們使用put方法和一個(gè)普通的JavaScript對(duì)象添加一些數(shù)據(jù)。最后,我們使用相同的key來(lái)獲取記錄(節(jié)點(diǎn)),并使用once方法讀出值。注意,普通對(duì)象會(huì)自動(dòng)轉(zhuǎn)換為Gun節(jié)點(diǎn)。我將在后面的"基礎(chǔ)知識(shí)"部分更詳細(xì)地解釋每個(gè)方法。

下面是一個(gè)示意圖,幫助你更好地理解正在發(fā)生的事情。

[圖片上傳失敗...(image-96c992-1651928820273)]

現(xiàn)在讓我們用Node來(lái)實(shí)驗(yàn)Gun。首先,創(chuàng)建一個(gè)目錄并使用npm安裝Gun

cd ~ && mkdir gun-demo && cd gun-demo
npm init -y
npm i gun -S

創(chuàng)建一個(gè)main.js文件如下:

const db = require('gun')();
const car = db.get("123").put({
  make: "Toyota",
  model: "Camry",
});
car.once(v => console.log(v.make)); // --> Toyota

然后通過(guò)node main.js執(zhí)行該文件,你應(yīng)該能看到控制臺(tái)中輸出Toyota。另外,你也可以在NodeREPL中使用Gun,首先在終端中調(diào)用node,然后運(yùn)行以下程序。

const db = require('gun')();

然后就可以與db對(duì)象進(jìn)行交互,如果想關(guān)閉REPL,按Ctrl+c兩次。

Gun的基礎(chǔ)知識(shí)

在以下章節(jié)中,我將向你展示Gun的基礎(chǔ)知識(shí),并探索其創(chuàng)建Create、讀取Read、更新Update和刪除Delete記錄(即CRUD)的基本方法。我還將向你展示如何創(chuàng)建集合Sets關(guān)系Relationships。我還將簡(jiǎn)要地提到訂閱記錄subscribing以獲得更新的不同方法。請(qǐng)注意,我將交替使用記錄record節(jié)點(diǎn)node這個(gè)術(shù)語(yǔ),因?yàn)?code>Gun是一個(gè)圖數(shù)據(jù)庫(kù),記錄被表示為節(jié)點(diǎn)node

CRUD

創(chuàng)建 Create

示例代碼:

const entry = db.get('8899').put({
  uuid: '8899',
  some_prop: 'some value',
});

在上面的代碼中,我們使用get方法來(lái)創(chuàng)建一個(gè)使用8899為key的節(jié)點(diǎn)的引用。然后,我們使用put方法,用一個(gè)普通的JavaScript對(duì)象將數(shù)據(jù)添加到該節(jié)點(diǎn)。普通對(duì)象會(huì)自動(dòng)轉(zhuǎn)換為Gun節(jié)點(diǎn)

注意,如果給定的key已經(jīng)存在,添加的數(shù)據(jù)可能會(huì)覆蓋現(xiàn)有的數(shù)據(jù)。我將在"更新"部分更詳細(xì)地介紹更新。下圖展示了數(shù)據(jù)庫(kù)中的給定鍵是如何指向一個(gè)節(jié)點(diǎn)的。

[圖片上傳失敗...(image-e33be2-1651928820273)]

關(guān)于Key的說(shuō)明:你應(yīng)該總是使用唯一的鍵。你可能想對(duì)通用節(jié)點(diǎn)使用uuids,并為索引目的使用與可讀字符串相結(jié)合的Hash字符串。在使用Gun時(shí),命名是非常重要的,因?yàn)樗袛?shù)據(jù)都存在于全局空間。你可能需要這個(gè)Reticle擴(kuò)展,以幫助你對(duì)你的鍵進(jìn)行命名。

讀取 Read

我們可以使用get方法來(lái)查詢(xún)一個(gè)給定Key的節(jié)點(diǎn)。然后我們可以使用ononce來(lái)訂閱它。使用on,你可以在更新發(fā)生時(shí)獲得更新,但once只發(fā)出一次當(dāng)前值。

const node = db.get("1122").once(v => console.log(v));

你可以不斷地調(diào)用get。如果引用不存在,它們會(huì)被創(chuàng)建。否則,將返回給定路徑上的值。讓我們來(lái)看看一個(gè)例子。下面我們將創(chuàng)建一個(gè)名為node1的節(jié)點(diǎn),它有一些屬性。

const node1 = db.get("3344").put({
  name: "node1"
});

node1.get("doc1").put({
  name: "doc1",
});

node1.get("doc1").get("sub_doc").put({
  name: 'sub_doc',
});

執(zhí)行以上代碼,會(huì)形成如下的數(shù)據(jù)鏈:
[圖片上傳失敗...(image-140be0-1651928820273)]

為了獲取node1.doc1.sub_doc,可以使用一連串的get獲得:

node1.get('doc1').get('sub_doc').once(v => console.log(v));

注意:當(dāng)你使用put時(shí),如果沒(méi)有明確指定鍵,就會(huì)自動(dòng)生成一個(gè)鍵。此外,db對(duì)象會(huì)保存一個(gè)對(duì)該鍵的引用。例如,當(dāng)我們做node1.get("doc1").put時(shí),一個(gè)具有唯一鍵的新節(jié)點(diǎn)在幕后被生成。我們可以看到,如果我們記錄node1.doc1的值并查看內(nèi)部屬性_,如下圖:

[圖片上傳失敗...(image-37df8d-1651928820273)]

現(xiàn)在,如果你知道這個(gè)節(jié)點(diǎn)的唯一鍵,你可以直接從db對(duì)象中訪問(wèn)它所指向的節(jié)點(diǎn):

db.get('unique_key')...

為了更好的理解上述節(jié)點(diǎn)的關(guān)系,見(jiàn)下圖:
[圖片上傳失敗...(image-7b95fa-1651928820273)]

注意其他兩個(gè)自動(dòng)生成的唯一鍵是如何從db中直接指向新創(chuàng)建的節(jié)點(diǎn)的。

更新 Update

示例代碼:

db.get('9871').put({
  name: 'Tom',
});

請(qǐng)注意,所有的更新都是部分更新。在上面的代碼中,只有name字段被更新。只要你有一個(gè)對(duì)節(jié)點(diǎn)的引用,你就可以簡(jiǎn)單地使用put來(lái)更新值。讓我們看一下另一個(gè)例子:

const n1 = db.get('5416')
  .put({
    name: 'n1',
    prop: '...',
    doc1: {
      prop: '...',
    },
  });

const n2 = db.get('8899')
  .put({
    name: 'n2',
    doc2: {
      prop: '...',
    }
  });

n1.get('related_to').put(n2);

在上面的代碼中,我們創(chuàng)建了兩個(gè)節(jié)點(diǎn):n1n2n1節(jié)點(diǎn)有屬性name,propdoc1。doc1屬性定義了一個(gè)子對(duì)象,它被自動(dòng)轉(zhuǎn)化為一個(gè)節(jié)點(diǎn),并被一個(gè)自動(dòng)生成的鍵所引用。

然后我們創(chuàng)建n2節(jié)點(diǎn),該節(jié)點(diǎn)有兩個(gè)屬性namedoc2,與n1相似。最后我們?cè)?code>n1上創(chuàng)建一個(gè)名為related_to的屬性,指向n2。下圖展示了這些關(guān)系:

[圖片上傳失敗...(image-f57de-1651928820273)]

現(xiàn)在我們開(kāi)始更新數(shù)據(jù):

  • 更新n1.doc1.prop的數(shù)據(jù)
n1.get('doc1').put({
  prop: 'other value'
});
  • 更新n1.related_to
n1.get('related_to').put(
    db.get('9185').put({ 
        new_prop: 'some value',
        }
    )
);

在上面的代碼中,我們通過(guò)創(chuàng)建一個(gè)新的節(jié)點(diǎn)完全改變了n1所指向的對(duì)象。注意,n2并沒(méi)有改變,我們只是更新了related_to指針。

  • 更新n2,該節(jié)點(diǎn)被n1引用
n1.get('related_to').put({
  new_stuff: 'some value',
  other_stuff: 'some value',
})

在上面的代碼中,new_stuffother_stuff將被添加到n2上已有的內(nèi)容。如果一個(gè)屬性已經(jīng)存在,它將被覆蓋,否則新的屬性將被創(chuàng)建。

刪除 Delete

Gun中,刪除的工作方式有一點(diǎn)不同,可以通過(guò)將一個(gè)指針設(shè)置為null來(lái)使它無(wú)法被發(fā)現(xiàn),而不是消除一條記錄,如:

db.get('8809').put(null);

在上面的代碼中,我們使用get來(lái)找到8809鍵的引用。然后,將其設(shè)置為null。只要有一個(gè)節(jié)點(diǎn)或?qū)傩缘囊茫涂梢杂?code>put來(lái)把它們?cè)O(shè)置為null。

以下是直接從StackOverflow中得到的對(duì)Delete操作的簡(jiǎn)要解釋。

GUN中的刪除工作就像Mac OSXWindowsLinux。nulling告訴每臺(tái)機(jī)器"把這些數(shù)據(jù)放到垃圾箱/回收站"。這一點(diǎn)很有用,因?yàn)樗梢宰屇愀淖兡銓?duì)刪除東西的看法,所以如果你想的話(huà),你可以在以后恢復(fù)它。(恢復(fù)被刪除的內(nèi)容/文件的情況很多,但大多數(shù)人都沒(méi)有想到)。

集合 Sets

Gun允許對(duì)多條記錄進(jìn)行分組,并將它們加入一個(gè)集合。Gun的集合,是一個(gè)具有唯一無(wú)序項(xiàng)的數(shù)學(xué)集合。假設(shè)我們有兩個(gè)節(jié)點(diǎn),我們想為它們創(chuàng)建一個(gè)組。首先,我們創(chuàng)建組節(jié)點(diǎn)(一個(gè)集合),然后我們使用集合方法將其他節(jié)點(diǎn)或普通對(duì)象添加到其中。

注意,如果是普通對(duì)象,就像更新操作一樣,將被自動(dòng)轉(zhuǎn)換為Gun節(jié)點(diǎn)。

const group = db.get('8871'); // create a group node
group.set(n1);
group.set(n2);

group有兩組記錄,分別為n1和n2。

也可以用如下方式實(shí)現(xiàn):

const group = db.get('8871');

group.set({
  title: 'hello'
});

group.set({
  title: 'world'
});

上述代碼執(zhí)行后,數(shù)據(jù)存儲(chǔ)如下圖:
[圖片上傳失敗...(image-f62f39-1651928820273)]

關(guān)系 Relationship

對(duì)現(xiàn)實(shí)世界進(jìn)行建模,就是要確定互相的關(guān)系并在數(shù)據(jù)庫(kù)中實(shí)現(xiàn)它們。圖形數(shù)據(jù)庫(kù)最擅長(zhǎng)于表達(dá)關(guān)系(Relationship)。在本節(jié)中,我將向你展示如何創(chuàng)建節(jié)點(diǎn)之間的關(guān)系。

正如我們之前看到的,創(chuàng)建關(guān)系的最簡(jiǎn)單方法是使用以下模式。

node1.get('related_to').put(node2)

或在制作節(jié)點(diǎn)時(shí)明確地創(chuàng)建一個(gè)關(guān)系。

const node1 = db.get('8891').put({
  uuid: '8891',
  name: 'node1',
  related_to: {
    uuid: '9911',
    name: 'node2',
  },
});

在上面的代碼中,related_toGun自動(dòng)變成了一個(gè)節(jié)點(diǎn),并且引用被存儲(chǔ)在node1中。然后你可以用node.get('related_to')訪問(wèn)這個(gè)鏈接的節(jié)點(diǎn)。

現(xiàn)在,如果你想給一個(gè)關(guān)系添加屬性,你可以創(chuàng)建一個(gè)中間節(jié)點(diǎn),并在中間節(jié)點(diǎn)內(nèi)添加關(guān)系的屬性和鏈接。

node1.get('related_to').put({
  property: "value",
  property2: "value",
});
node1.get('related_to').get('node').put(node2);

下圖顯示了數(shù)據(jù)關(guān)系:
[圖片上傳失敗...(image-c25c14-1651928820273)]

正如你在上圖中看到的,related_to節(jié)點(diǎn)通過(guò)中間節(jié)點(diǎn)的node屬性指向node2。然后你可以用node1.get('relate_to').get('node')訪問(wèn)node2。

訂閱 Subscribing

Gun節(jié)點(diǎn)的行為類(lèi)似于可觀察物,這意味著它們會(huì)隨著時(shí)間的推移而發(fā)出數(shù)值。你可以使用ononce來(lái)訂閱槍節(jié)點(diǎn)。使用on,你可以在發(fā)生時(shí)獲得更新,除非你取消訂閱。once方法只檢索當(dāng)前的值,不訂閱未來(lái)的更新。

遍歷記錄

給定一組記錄,你可以使用map來(lái)遍歷它們

myset.map().once(v => console.log(v));

上面的代碼將顯示myset中的每條記錄一次。它也將獲得隨著時(shí)間推移而增加的記錄,但只有一次。

這里有更多你可以使用的模式(直接取自文檔)。

  • myset.map().on(cb):訂閱每條記錄的變化,并在未來(lái)添加更多記錄時(shí)訂閱myset
  • myset.map().once(cb):獲取每條記錄一次,包括隨著時(shí)間推移添加的記錄。
  • myset.once().map().on(cb):獲取一次記錄列表,但訂閱每個(gè)myset上的變化,但不訂閱以后添加的記錄。
  • myset.once().map().once(cb):獲取一次記錄列表,只獲取myset中的每條記錄一次,而不是后來(lái)添加的記錄。

結(jié)論

GunDB正在改變我們思考數(shù)據(jù)庫(kù)的方式,并且正在慢慢地將我們過(guò)渡到一個(gè)新的范式。Gun以及它的相關(guān)項(xiàng)目,有很多方面與經(jīng)典的集中式模型非常不同。如果你剛剛開(kāi)始學(xué)習(xí)Gun,你可能會(huì)發(fā)現(xiàn)它具有挑戰(zhàn)性。首先,因?yàn)镚un是一個(gè)年輕的項(xiàng)目,你應(yīng)該期待API的變化。其次,你可能會(huì)發(fā)現(xiàn)你很難理解文檔的內(nèi)容。

我希望這些系列的文章可以幫助你(和我)更好地理解GunDB,并作為先前存在的指南的補(bǔ)充。

你可以訪問(wèn)所有的官方文檔和指南:https://gun.eco/docs

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容