GunDB:一个去中心化的图数据库

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

最近我一直在使用GunDB,想和你分享我目前学到的一些东西。GunDB不仅仅是一个图形数据库(Graph DB)。它更是一个项目组,旨在简化扩展,提高数据安全性,节约成本,并赋予应用程序开发人员更多的能力。

在这篇文章中,我将完全把Gun作为一个数据库来探讨,并在接下来的文章中随着我的进展和学习,探索其他方面。

所有文章中的源码在这里可以找到

简介

一般来说,数据库是一个软件,安装在你的电脑或远程服务器上,用来存储数据。这些数据可以存储在磁盘上或内存中。

数据库有不同的类型:关系型(Relational DB)、面向文档型(Document DB)、键值型(Key-value DB)或图形型(Graph DB)。下面是一些例子:

  • 关系型:MySql, PostgreSQL, SQL Server
  • 面向文档的:MongoDB, CouchDB
  • 键值型:Redis, LevelDB
  • 基于图形:Neo4j、OrientDB

与其他数据库不同,Gun没有二进制文件需要安装,Gun是用JavaScript编写的,这意味着你可以在任何运行JavaScript的地方使用它。开始使用Gun就像下载一个JavaScrip文件或用npm安装一个插件一样容易。

开始使用

开始使用Gun的最简单方法是将其作为一个单一的JavaScript文件下载。你可以从以下网址下载最新的简化版本: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>

然后你可以在浏览器中打开Html文件,并使用控制台consoleGun互动。在大多数浏览器中,你可以在页面上点击右键,在弹出菜单中选择检查元素来打开控制台。下面例子可以用它来创建一个汽车记录并打印它的制造商。

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

在上面的代码中,首先,我们创建一个Gun的实例。然后,使用123为键key的get方法来引用一个空节点。接下来,我们使用put方法和一个普通的JavaScript对象添加一些数据。最后,我们使用相同的key来获取记录(节点),并使用once方法读出值。注意,普通对象会自动转换为Gun节点。我将在后面的"基础知识"部分更详细地解释每个方法。

下面是一个示意图,帮助你更好地理解正在发生的事情。

[图片上传失败...(image-96c992-1651928820273)]

现在让我们用Node来实验Gun。首先,创建一个目录并使用npm安装Gun

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

创建一个main.js文件如下:

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

然后通过node main.js执行该文件,你应该能看到控制台中输出Toyota。另外,你也可以在NodeREPL中使用Gun,首先在终端中调用node,然后运行以下程序。

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

然后就可以与db对象进行交互,如果想关闭REPL,按Ctrl+c两次。

Gun的基础知识

在以下章节中,我将向你展示Gun的基础知识,并探索其创建Create、读取Read、更新Update和删除Delete记录(即CRUD)的基本方法。我还将向你展示如何创建集合Sets关系Relationships。我还将简要地提到订阅记录subscribing以获得更新的不同方法。请注意,我将交替使用记录record节点node这个术语,因为Gun是一个图数据库,记录被表示为节点node

CRUD

创建 Create

示例代码:

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

在上面的代码中,我们使用get方法来创建一个使用8899为key的节点的引用。然后,我们使用put方法,用一个普通的JavaScript对象将数据添加到该节点。普通对象会自动转换为Gun节点

注意,如果给定的key已经存在,添加的数据可能会覆盖现有的数据。我将在"更新"部分更详细地介绍更新。下图展示了数据库中的给定键是如何指向一个节点的。

[图片上传失败...(image-e33be2-1651928820273)]

关于Key的说明:你应该总是使用唯一的键。你可能想对通用节点使用uuids,并为索引目的使用与可读字符串相结合的Hash字符串。在使用Gun时,命名是非常重要的,因为所有数据都存在于全局空间。你可能需要这个Reticle扩展,以帮助你对你的键进行命名。

读取 Read

我们可以使用get方法来查询一个给定Key的节点。然后我们可以使用ononce来订阅它。使用on,你可以在更新发生时获得更新,但once只发出一次当前值。

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

你可以不断地调用get。如果引用不存在,它们会被创建。否则,将返回给定路径上的值。让我们来看看一个例子。下面我们将创建一个名为node1的节点,它有一些属性。

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

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

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

执行以上代码,会形成如下的数据链:
[图片上传失败...(image-140be0-1651928820273)]

为了获取node1.doc1.sub_doc,可以使用一连串的get获得:

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

注意:当你使用put时,如果没有明确指定键,就会自动生成一个键。此外,db对象会保存一个对该键的引用。例如,当我们做node1.get("doc1").put时,一个具有唯一键的新节点在幕后被生成。我们可以看到,如果我们记录node1.doc1的值并查看内部属性_,如下图:

[图片上传失败...(image-37df8d-1651928820273)]

现在,如果你知道这个节点的唯一键,你可以直接从db对象中访问它所指向的节点:

db.get('unique_key')...

为了更好的理解上述节点的关系,见下图:
[图片上传失败...(image-7b95fa-1651928820273)]

注意其他两个自动生成的唯一键是如何从db中直接指向新创建的节点的。

更新 Update

示例代码:

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

请注意,所有的更新都是部分更新。在上面的代码中,只有name字段被更新。只要你有一个对节点的引用,你就可以简单地使用put来更新值。让我们看一下另一个例子:

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);

在上面的代码中,我们创建了两个节点:n1n2n1节点有属性name,propdoc1doc1属性定义了一个子对象,它被自动转化为一个节点,并被一个自动生成的键所引用。

然后我们创建n2节点,该节点有两个属性namedoc2,与n1相似。最后我们在n1上创建一个名为related_to的属性,指向n2。下图展示了这些关系:

[图片上传失败...(image-f57de-1651928820273)]

现在我们开始更新数据:

  • 更新n1.doc1.prop的数据
n1.get('doc1').put({
  prop: 'other value'
});
  • 更新n1.related_to
n1.get('related_to').put(
    db.get('9185').put({ 
        new_prop: 'some value',
        }
    )
);

在上面的代码中,我们通过创建一个新的节点完全改变了n1所指向的对象。注意,n2并没有改变,我们只是更新了related_to指针。

  • 更新n2,该节点被n1引用
n1.get('related_to').put({
  new_stuff: 'some value',
  other_stuff: 'some value',
})

在上面的代码中,new_stuffother_stuff将被添加到n2上已有的内容。如果一个属性已经存在,它将被覆盖,否则新的属性将被创建。

删除 Delete

Gun中,删除的工作方式有一点不同,可以通过将一个指针设置为null来使它无法被发现,而不是消除一条记录,如:

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

在上面的代码中,我们使用get来找到8809键的引用。然后,将其设置为null。只要有一个节点或属性的引用,就可以用put来把它们设置为null

以下是直接从StackOverflow中得到的对Delete操作的简要解释。

GUN中的删除工作就像Mac OSXWindowsLinuxnulling告诉每台机器"把这些数据放到垃圾箱/回收站"。这一点很有用,因为它可以让你改变你对删除东西的看法,所以如果你想的话,你可以在以后恢复它。(恢复被删除的内容/文件的情况很多,但大多数人都没有想到)。

集合 Sets

Gun允许对多条记录进行分组,并将它们加入一个集合。Gun的集合,是一个具有唯一无序项的数学集合。假设我们有两个节点,我们想为它们创建一个组。首先,我们创建组节点(一个集合),然后我们使用集合方法将其他节点或普通对象添加到其中。

注意,如果是普通对象,就像更新操作一样,将被自动转换为Gun节点。

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

group有两组记录,分别为n1和n2。

也可以用如下方式实现:

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

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

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

上述代码执行后,数据存储如下图:
[图片上传失败...(image-f62f39-1651928820273)]

关系 Relationship

对现实世界进行建模,就是要确定互相的关系并在数据库中实现它们。图形数据库最擅长于表达关系(Relationship)。在本节中,我将向你展示如何创建节点之间的关系。

正如我们之前看到的,创建关系的最简单方法是使用以下模式。

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

或在制作节点时明确地创建一个关系。

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

在上面的代码中,related_toGun自动变成了一个节点,并且引用被存储在node1中。然后你可以用node.get('related_to')访问这个链接的节点。

现在,如果你想给一个关系添加属性,你可以创建一个中间节点,并在中间节点内添加关系的属性和链接。

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

下图显示了数据关系:
[图片上传失败...(image-c25c14-1651928820273)]

正如你在上图中看到的,related_to节点通过中间节点的node属性指向node2。然后你可以用node1.get('relate_to').get('node')访问node2

订阅 Subscribing

Gun节点的行为类似于可观察物,这意味着它们会随着时间的推移而发出数值。你可以使用ononce来订阅枪节点。使用on,你可以在发生时获得更新,除非你取消订阅。once方法只检索当前的值,不订阅未来的更新。

遍历记录

给定一组记录,你可以使用map来遍历它们

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

上面的代码将显示myset中的每条记录一次。它也将获得随着时间推移而增加的记录,但只有一次。

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

  • myset.map().on(cb):订阅每条记录的变化,并在未来添加更多记录时订阅myset
  • myset.map().once(cb):获取每条记录一次,包括随着时间推移添加的记录。
  • myset.once().map().on(cb):获取一次记录列表,但订阅每个myset上的变化,但不订阅以后添加的记录。
  • myset.once().map().once(cb):获取一次记录列表,只获取myset中的每条记录一次,而不是后来添加的记录。

结论

GunDB正在改变我们思考数据库的方式,并且正在慢慢地将我们过渡到一个新的范式。Gun以及它的相关项目,有很多方面与经典的集中式模型非常不同。如果你刚刚开始学习Gun,你可能会发现它具有挑战性。首先,因为Gun是一个年轻的项目,你应该期待API的变化。其次,你可能会发现你很难理解文档的内容。

我希望这些系列的文章可以帮助你(和我)更好地理解GunDB,并作为先前存在的指南的补充。

你可以访问所有的官方文档和指南:https://gun.eco/docs

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容