集册 Node.js(版本:v10 LTS)教程 迁移到 Cloudant 数据库即服务

迁移到 Cloudant 数据库即服务

Xebcnor     最近更新时间:2019-10-10 12:26:35

737

在 Node.js 学习路径的前几个单元中,您使用以下两个本地数据库安装设置了"购物清单"应用程序:SQLite3 和 MongoDB。在当前的技术环境下,了解如何针对云端扩展 Node.js 应用程序也很重要。大多数企业都转而采用云存储,因为服务器托管成本高昂,而且可能会导致可用性问题。通过使用云计算,您只要为所需的云服务买单,因此就不会因无力承受的昂贵硬件而望洋兴叹。

在 Node.js 学习路径的最后一个单元中,了解如何将"购物清单"应用程序的数据迁移到 IBM Cloud 的非关系型数据库即服务(DBaaS)产品 Cloudant。"购物清单"应用程序将继续在您的计算机上本地运行,但您将使用开源 Cloudant 驱动程序(@cloudant/cloudant) 来访问和处理数据。

Cloudant

Cloudant 是一个以文档为导向的分布式非关系型数据库,它是 IBM Cloud 的一部分。它由 2008 年开始的 MIT 项目 衍生而来,并且基于 Apache CouchDB 2.0。虽然 Cloudant 可作为独立安装使用,但它已经发展成为数据库即服务产品。

与 SQLite3(如第 6 单元中所介绍)或 MongoDB(如第 12 单元中所介绍)不同,Cloudant 不使用表或集合来存储数据,而是将所有文档都存储在数据库中。

作为开发者,您可以确定如何对数据进行分区以优化文档搜索和检索。例如,在本单元中,我们将使用 type   属性来区分以下两种不同类型的文档:itemshoppingList

务必查看 Cloudant 文档,了解有关 Cloudant 的更多信息。

结合使用 Cloudant 和 Node.js

在处理示例应用程序时,您需要参考以下工具的文档:

  • Cloudant:IBM   的数据库即服务云产品。

  • Cloudant Node.js     Client(GitHub):Cloudant 的官方 Node 驱动程序。

  • 用于 Node.js 的 Nano CouchDB     驱动程序(GitHub):Apache     couchdb-nano:Node Cloudant 驱动程序的核心技术。

前提条件

您需要一个 IBM Cloud 帐户才能跟随本单元进行操作。如果您已拥有 IBM Cloud 帐户,即表明已完成所有设置。如果您还没有此帐户,可以注册免费帐户。(单击 Create your Lite account 按钮。)

您还需要本单元的源代码,可从 GitHub   获取此源代码。

设置 Cloudant

在本节中,您会将 Cloudant 设置为"购物清单"应用程序的数据库服务。只需执行以下步骤。

1. 登录到 IBM Cloud

转至 IBM Cloud 页面进行登录。

如果您还没有 IBM Cloud 帐户,可转至 IBM Cloud Lite   注册页面获取免费的 Lite 帐户。这个过程很快,因此您可以立即登录并准备好进行编码。

虽然 Lite 帐户受限制,但是您在登录后可以浏览 IBM Cloud 中可用的功能,并运行本单元的所有代码。

2. 创建 Cloudant 数据库服务

在登录到 IBM Cloud 帐户后,您将进入 IBM Cloud 仪表板。

从左上角的汉堡菜单中选择 Infrastructure。在下一个页面上,单击 storage 图标,然后在之后的页面上,单击类别列表中的 Databases。从 Databases 页面上的数据库列表中选择 Cloudant。

接下来,您将创建 Cloudant 服务。为您的服务命名(例如 Node-LP-Cloudant),选择 Use both legacy   credentials and IAM 作为认证方法,然后单击 Create 按钮,如图 1 所示。

图 1. 创建 Cloudant 服务

图 1. 创建 Cloudant 服务

在创建 Cloudant 服务后,您将进入仪表板,在那里您将看到该服务:

图 2. 显示新创建的 Cloudant 服务的 IBM Cloud 仪表板

图 2. 显示新创建的 Cloudant 服务的 IBM Cloud 仪表板

3. 创建凭证

您需要使用凭证才能远程连接到该 Cloudant 服务。要创建凭证,可选择您的 Cloudant 服务,然后从下一个屏幕的左侧菜单中选择 Service   credentials。选择 New credential (+) 按钮,如图 3 所示。

图 3. Cloudant 仪表板中的 Service credentials 屏幕

图 3. Cloudant 仪表板中的 Service credentials 屏幕

在 Add new credential 对话框中,为新凭证指定名称,选择 Auto Generate 作为服务 ID,然后单击   Add

图 4. Add new credential 对话框

图 4. Add new credential 对话框

在创建凭证后,该凭证将显示在服务凭证列表中。选择 View credentials,然后将凭证复制到剪贴板,如图 5 所示。

图 5. 复制的凭证

图 5. 复制的凭证

您需要使用复制的凭证来创建本地配置文件,这允许 Node.js 应用程序与 Cloudant 服务进行交互。

4. 创建服务凭证配置文件 (vcap-local.json)

在编辑器(我使用的是 VSCode)中,创建一个新文件,并在其中粘贴您刚从服务凭证屏幕复制的凭证。您会注意到,它是一个 JSON 对象(包括周围的花括号)。

接下来,复制以下代码块并将其粘贴到编辑器中。

清单 1. vcap-local.json 模板

1
2
3
4
5
6
7
8
9
10
{
    "services": {
        "cloudantNoSQLDB": {
            "credentials":
            PASTE CREDENTIAL INFO HERE
            
            "label": "cloudantNoSQLDB"
        }
    }
}

使用您刚刚粘贴到编辑器中的凭证替换 PASTE CREDENTIAL INFO HERE。应包括花括号,以确保 JSON 对象的格式正确。

将该文件作为 vcap-local.json 保存到 Unit-14/config 目录中。该文件应该类似于清单 2。(我对自己的凭证作了模糊处理。)

清单 2. vcap-local.json 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
    "services": {
        "cloudantNoSQLDB": {
            "credentials": {
                "apikey": "YOUR_API_KEY_HERE",
                "host": "YOUR_USERNAME_HERE.cloudant.com",
                "iam_apikey_description": "Auto generated apikey during resource-key operation for Instance - crn:v1:bluemix:public:cloudantnosqldb:us-south:a/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx::",
                "iam_apikey_name": "auto-generated-apikey-xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
                "iam_role_crn": "crn:v1:bluemix:public:iam::::serviceRole:Manager",
                "iam_serviceid_crn": "crn:v1:bluemix:public:iam-identity::a/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
                "password": "YOUR_PASSWORD_HERE",
                "port": 443,
                "url": "https://YOUR_USERNAME_HERE:YOUR_PASSWORD_HERE@YOUR_HOST_HERE.cloudant.com",
                "username": "YOUR_USERNAME_HERE"
            },
            "label": "cloudantNoSQLDB"
        }
    }
}

5. 创建数据库

使用汉堡菜单浏览至 IBM Cloud 仪表板,然后选择您的 Cloudant 服务。选择 Launch Cloudant Dashboard,您将看到如图 6 所示的仪表板。

图 6. Cloudant 数据库仪表板

图 6. Cloudant 数据库仪表板

单击图 6 的左侧菜单中的数据库图标,然后选择 Create Database。将该数据库命名为"shopping_list",然后单击   Create。您会看到如图 7 所示的屏幕。

图 7. 新创建的 shopping_list 数据库

图 7. 新创建的 shopping_list 数据库

现在,您已准备好将应用程序数据加载到数据库中。

6. 将应用程序数据加载到 Cloudant 中

我在自己的 GitHub 存储库中提供了一个名为 load-db-cloudant.js 的数据加载程序,您可以使用 npm run   load-db 脚本来运行该程序。输出如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Ix:~/src/projects/IBM-Developer/Node.js/Course/Unit-14 sperry$ npm run load-db
 
> unit-14@1.0.0 load-db /Users/sperry/home/development/projects/IBM-Developer/Node.js/Course/Unit-14
> node ./utils/load-cloudant
 
[2018-08-17T23:20:07.351] [INFO] default - 1534566007350:INFO: Connect success! Connected to DB: shopping_list
[2018-08-17T23:20:07.355] [INFO] default - 1534566007355:INFO: mainline(): Initializing Cloudant...Done.
[2018-08-17T23:20:07.359] [INFO] default - 1534566007359:INFO: mainline(): Script start at: 8/17/2018, 11:20:07 PM
[2018-08-17T23:20:07.359] [INFO] default - 1534566007359:INFO: mainline(): Loading data for item...
[2018-08-17T23:20:07.359] [INFO] default - 1534566007359:INFO: loadData(): Loading data files...
[2018-08-17T23:20:07.360] [INFO] default - 1534566007360:INFO: loadData():readableStream.on(open): Opened file: ../data/Grocery_UPC_Database.csv
[2018-08-17T23:20:07.389] [DEBUG] default - 1534566007389:DEBUG: Creating documents: 1-10000
[2018-08-17T23:20:10.506] [DEBUG] default - 1534566010506:DEBUG: Creating documents: 10001-20000
[2018-08-17T23:20:13.591] [DEBUG] default - 1534566013591:DEBUG: Creating documents: 20001-30000
[2018-08-17T23:20:16.693] [DEBUG] default - 1534566016693:DEBUG: Creating documents: 30001-40000
[2018-08-17T23:20:19.804] [DEBUG] default - 1534566019804:DEBUG: Creating documents: 40001-50000
[2018-08-17T23:20:22.902] [DEBUG] default - 1534566022902:DEBUG: Creating documents: 50001-60000
[2018-08-17T23:20:26.009] [DEBUG] default - 1534566026009:DEBUG: Creating documents: 60001-70000
[2018-08-17T23:20:29.136] [DEBUG] default - 1534566029136:DEBUG: Creating documents: 70001-80000
[2018-08-17T23:20:32.253] [DEBUG] default - 1534566032253:DEBUG: Creating documents: 80001-90000
[2018-08-17T23:20:35.335] [DEBUG] default - 1534566035335:DEBUG: Creating documents: 90001-100000
[2018-08-17T23:20:38.433] [DEBUG] default - 1534566038433:DEBUG: Creating documents: 100001-110000
[2018-08-17T23:20:41.511] [INFO] default - 1534566041511:INFO: loadData():readableStream.on(close): Closed file: ../data/Grocery_UPC_Database.csv
[2018-08-17T23:20:41.511] [DEBUG] default - 1534566041511:DEBUG: Creating documents: 110001-110436
[2018-08-17T23:20:44.515] [INFO] default - 1534566044515:INFO: mainline(): Loading item data, done.
[2018-08-17T23:20:44.515] [INFO] default - 1534566044515:INFO: Total item documents loaded: 110436
[2018-08-17T23:20:44.515] [INFO] default - 1534566044515:INFO: mainline(): Script finished at: 8/17/2018, 11:20:44 PM
Ix:~/src/projects/IBM-Developer/Node.js/Course/Unit-14 sperry$

7. 验证是否已加载数据

shopping_list 数据库包含 110,436 个项。您应该会在 shopping_list 仪表板的 # of Docs 中看到此数字,如图 8 所示。

图 8. 成功运行 load-db-cloudant 数据加载程序后的 shopping_list 数据库

图 8. 成功运行 load-db-cloudant 数据加载程序后的 shopping_list 数据库

要查询数据,请选择 shopping_list 数据库,然后单击左侧菜单中的 Query。使用以下查询替换查询窗口的内容。

清单 3. 用于执行正则表达式搜索的 Cloudant 查询 JSON 格式

1
2
3
4
5
6
7
8
9
10
11
12
{
   "selector": {
      "itemDescription": {
         "$regex": ".*Free Range.*"
      }
   },
   "fields": [
      "_id",
      "_rev",
      "itemDescription"
   ]
}

此查询正在寻找其 itemDescription 属性包含短语"Free Range."的文档。 此查询应生成 15 个结果,如图 9 所示。

图 9. Cloudant 查询结果

图 9. Cloudant 查询结果

您已经设置了 Cloudant 数据库,验证了数据,并且一切正常。接下来,您将运行"购物清单"应用程序。

8. 运行“购物清单”应用程序

除了在云中托管之外,您在前几个单元中运行的"购物清单"应用程序与此"购物清单"应用程序之间的唯一区别是记录系统。UI 的外观是相同的。

要运行该应用程序,请转至终端窗口并运行 npm start,或运行 npm run start-dev 以使用   nodemon

将浏览器转至 http://localhost:3000 以访问该应用程序的主页。

Cloudant Node API

Node.js 的官方 Cloudant 库 @cloudant/cloudant 是 Apache CouchDB 的一个分支的包装器。该分支称为 cloudant-nano,由 IBM Cloudant 团队维护。

在本节中,我将介绍如何使用 Cloudant 的 Node API 调用与"购物清单"应用程序中的 DAO 模块进行交互。首先连接到 IBM Cloud 中的 Cloudant 服务。

连接到 Cloudant

要将"购物清单"应用程序与 Cloudant 相连,首先需要创建一个新的 Cloudant 对象,并向此对象传递清单 1 中的   vcap-local.json 文件内的凭证。然后,调用 use() 函数以告知驱动程序要使用的数据库:

清单 4. 使用 @cloudant/cloudant Node 驱动程序连接到 Cloudant(来自     ./utils/utils.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const Cloudant = require('@cloudant/cloudant');
 
const vcap = require('../config/vcap-local.json');
 
function dbCloudantConnect() {
    return new Promise((resolve, reject) => {
        Cloudant({  // eslint-disable-line
            url: vcap.services.cloudantNoSQLDB.credentials.url
        }, ((err, cloudant) => {
            if (err) {
                logger.error('Connect failure: ' + err.message + ' for Cloudant DB: ' +
                    appSettings.cloudant_db_name);
                reject(err);
            } else {
                let db = cloudant.use(appSettings.cloudant_db_name);
                logger.info('Connect success! Connected to DB: ' + appSettings.cloudant_db_name);
                resolve(db);
            }
        }));
    });
}

dbCloudantConnect() 函数将返回一个承诺。如果履行了该承诺,那么它将包含所需的数据库对象,以便使用 API。如果驳回了该承诺,那么将记录一个非空的 Error 对象,并使用该对象来驳回该承诺。

我已经编写了 DAO 实现,以便使用 IIFE (Immediately Invoked Function Expression) 来调用   dbCloudantConnect()。在模块加载时执行此调用。

清单 5. 调用 dbCloudantConnect() 以连接到 Cloudant(来自     ./models/lists-dao-cloudant.js

1
2
3
4
5
6
7
8
9
10
11
12
13
let db;
 
// Initialize the DB when this module is loaded
(function getDbConnection() {
    logger.info('Initializing Cloudant connection...', 'items-dao-cloudant.getDbConnection()');
    utils.dbCloudantConnect().then((database) => {
        logger.info('Cloudant connection initialized.', 'items-dao-cloudant.getDbConnection()');
        db = database;
    }).catch((err) => {
        logger.error('Error while initializing DB: ' + err.message, 'items-dao-cloudant.getDbConnection()');
        throw err;
    });
})();

如果履行了该承诺,那么会将数据库对象传递给 then() 进行保存。该模块中的 DAO 函数将使用 db 对象进行 Cloudant Node API 函数调用。

如果 getDbConnection() 成功运行(即没有 throw 一个 Error),即表明可以使用该连接。

创建新的购物清单

要创建新的购物清单,可以调用 lists-dao-cloudant.js 中的 create 函数并传递购物清单的描述,如清单 6 所示。

清单 6. 调用create()以创建新的购物清单(来自./models/lists-dao-cloudant.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function create(description) {
    return new Promise((resolve, reject) => {
        let listId = uuidv4();
        let whenCreated = Date.now();
        let list = {
            _id: listId,
            id: listId,
            type: 'shoppingList',
            items: [],
            description: description,
            whenCreated: whenCreated,
            whenUpdated: null
        };
        db.insert(list, (err, result) => {
            if (err) {
                logger.error('Error occurred: ' + err.message, 'create()');
                reject(err);
            } else {
                resolve({ data: { createdId: result.id, createdRevId: result.rev }, statusCode: 201 });
            }
        });
    });
}

首先,该函数会创建一个名为 list 的新 JSON 文档对象,用于表示购物清单。随后,该函数会填充 list 对象(包括   type),该对象会设置为 shoppingList 以区别于 item(回想一下, Cloudant 中没有集合的概念),然后调用 db.insert() 来创建文档。

创建文档后,调用者会收到一个 JSON 响应,其中包含新文档 _id(作为 createdId)和   _rev(作为 createdRevId)。

如果无法创建文档,那么将驳回该承诺。

读取购物清单

要通过 _id 属性来访存单个购物清单,可以调用 lists-dao-cloudant.js 中的   findById() 函数,并传递要检索的文档 _id 的值:

清单 7. 调用 findById() 以通过 _id 属性来检索单个购物清单(来自     ./models/lists-dao-cloudant.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function findById(id) {
    return new Promise((resolve, reject) => {
        db.get(id, (err, document) => {
            if (err) {
                if (err.message == 'missing') {
                    logger.warn(`Document id ${id} does not exist.`, 'findById()');
                    resolve({ data: {}, statusCode: 404 });
                } else {
                    logger.error('Error occurred: ' + err.message, 'findById()');
                    reject(err);
                }
            } else {
                resolve({ data: JSON.stringify(document), statusCode: 200 });
            }
        });
    });
}

该函数会调用 db.get(),传递要访存的文档 _id。如果文档 _id 不存在,那么将返回一个空的 JSON 对象以及 HTTP 状态码 404 (NOT FOUND)。否则,将向调用者返回该文档。

更新购物清单

要更新购物清单,可以调用 lists-dao-cloudant.js 中的 update() 函数,传递购物清单的文档   _id 以及用于更新的新 description 值。

清单 8. 调用 update() 以更新单个购物清单(来自     ./models/lists-dao-cloudant.js

展开阅读全文