美文网首页
GunDB:一个去中心化的图数据库

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

作者: JackyGu | 来源:发表于2022-05-07 21:07 被阅读0次

    原文链接: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

    相关文章

      网友评论

          本文标题:GunDB:一个去中心化的图数据库

          本文链接:https://www.haomeiwen.com/subject/ataxurtx.html