在上一章中,我们设置了我们的开发环境,我相信您一定是迫不及待地想知道 CouchDB 能为我们做些什么。在这一点上,我们将花整整一章的时间来研究 CouchDB。

具体而言,我们将:

  • 深入研究 CouchDB 的含义,了解它在数据库和文档中的外观
  • 了解如何通过 CouchDB 的 RESTful JSON API 与 CouchDB 交互
  • 使用 CouchDB 的内置管理控制台:Futon
  • 了解如何为 CouchDB 数据库添加安全性

什么是 CouchDB?

CouchDB 定义的第一句话(由定义)http://couchdb.apache.org/))如下:

CouchDB 是一个文档数据库服务器,可通过 RESTful JSON API 访问。

让我们仔细分析这个句子,充分理解它的意思。让我们从术语数据库服务器开始。

数据库服务器

CouchDB 采用了一个面向文档的数据库管理系统,它为没有模式、分组或层次结构的扁平文档集合提供服务。这是NoSQL引入的一个概念,它与关系数据库(如 MySQL)有很大的不同,您希望在关系数据库中看到表、关系和外键。每个开发人员都经历过这样一个项目,他们必须将关系数据库模式强制加入到一个真正不需要严格的表和复杂关系的项目中。这就是 CouchDB 做事情的不同之处;它将所有数据存储在一个没有设置模式的自包含对象中。下图将有助于说明这一点:

Database server

在前面的示例中,我们可能希望促进多个用户属于一对多组的能力。为了在关系数据库(如 MySQL)中处理此功能,我们将创建一个用户表、一个组表和一个名为 users_groups的链接表,允许您将多个用户映射到多个组。这种做法在大多数 web 应用中都很常见。

现在看看 CouchDB 文档。没有表或链接表,只有文档。这些文档包含与单个对象相关的所有数据。

笔记

这个图表非常简单。如果我们想围绕 CouchDB 中的组创建更多的逻辑,我们就必须创建组文档,在用户文档和组文档之间建立简单的关系。随着本书的深入,我们将讨论如何处理这种关系。

我们在本节中看到了很多术语文档。因此,让我们进一步深入了解什么是文档以及 CouchDB 如何使用它们。

文件

为了说明如何使用文档,首先假设您正在实际填写工作申请的纸质表单。此表单包含有关您、您的地址和过去地址的信息。它也有关于你过去工作、教育、证书等方面的信息。一个文档将以物理形式保存所有这些数据—所有这些数据都保存在一个地方,没有任何不必要的复杂性。

在 CouchDB 中,文档存储为包含键和值对的 JSON 对象。每个文档都为元数据保留了字段,如 id, revision、 deleted。除了保留字段之外,文档的模式少了 100%,这意味着每个文档都可以单独格式化和处理,并根据需要使用尽可能多的不同变体。

CouchDB 文档的示例

让我们来看一个 CouCHDB 文档对于博客文章的样子的例子:

{
"_id": "431f956fa44b3629ba924eab05000553",
"_rev": "1-c46916a8efe63fb8fec6d097007bd1c6",
"title": "Why I like Chicken",
"author": "Tim Juravich",
"tags": [
"Chicken",
"Grilled",
"Tasty"
],
"body": "I like chicken, especially when it's grilled."
}

提示

下载示例代码

您可以下载您在账户购买的所有 Packt 书籍的示例代码文件 http://www.packtpub.com 。如果您在其他地方购买了本书,您可以访问http://www.packtpub.com/support 并注册,将文件直接通过电子邮件发送给您。

JSON 格式

您可能会注意到的第一件事是文档的奇怪标记,即JavaScript 对象表示法(JSON)。JSON 是一种基于 JavaScript 语法的轻量级数据交换格式,具有极高的可移植性。CouchDB 使用 JSON 与它进行所有通信,因此通过本书的课程,您将非常熟悉它。

键值存储

您可能会注意到的下一件事是,本文档中有很多信息。有一些键-值对很容易理解,例如 "title", "author和 "body,但您也会注意到 "tags“是一个字符串数组。CouchDB 允许您在文档中直接嵌入任意数量的信息。对于习惯于规范化和结构化数据库的关系数据库用户来说,这可能是一个新概念。

保留字段

我们前面提到了保留字段。让我们看看您在前面的示例文档中看到的两个保留字段: _id和 _rev。

_id是文档的唯一标识符。这意味着 _id是强制性的,两个文档不能有相同的值。如果您在创建文档时未定义 _id,CouchDB 将为您选择一个唯一的。

_rev是文件的修订版本,是帮助驱动 CouchDB 版本控制系统的字段。每次保存文档时,都需要修订号,以便 CouchDB 知道文档的最新版本。这是必需的,因为 CouchDB 不使用锁定机制,这意味着如果两个人同时更新文档,那么首先保存更改的人将获胜。CouchDB 版本系统的一个独特之处是,每次保存文档时,原始文档都不会被覆盖,并且会使用新数据创建一个新文档,而 CouchDB 会将以前文档的原始形式备份存储在存档中。在压缩数据库或执行某些清理操作之前,旧版本将保持可用。

定义语句的最后一部分是 RESTful JSON API。那么,让我们接下来讨论这个问题。

RESTful JSON API

为了理解 REST,我们首先定义超文本传输协议(HTTP)。HTTP 是 Internet 的底层协议,它定义了消息的格式和传输方式,以及在使用各种方法时服务应如何响应。这些方法由四个主要动词组成,如 GET, PUT, POST和 DELETE。为了充分理解 HTTP 方法的功能,让我们首先定义 REST。

表示状态转移(REST)是一种无状态协议,通过 HTTP 方法访问可寻址资源。无状态表示每个请求包含完全理解和使用请求中数据所需的所有信息,可寻址资源表示可以通过 URL 访问对象。

这本身可能并不意味着什么,但通过将所有这些想法放在一起,它就成为了一个强大的概念。让我们通过两个例子来说明休息的力量:

|

资源

|

收到

|

放

|

邮递

|

删去

http://localhost/collection阅读一份 collection内所有项目的清单用另一个 collection更新集合新建一个 collection删除该 collection
http://localhost/collection/abc123阅读在 collection中的 abc123项的详细信息更新在 collection中的 abc123细节在 collection内创建新对象 abc123从 collection中删除 abc123

通过查看该表,您可以看到每个资源都以 URL 的形式存在。第一个资源为 collection,第二个资源为 abc123,位于 collection内。当您向这些资源传递不同的方法时,它们的响应都不同。这就是 REST 和 HTTP 协同工作的美妙之处。

注意我在表格中使用的粗体字:读取、更新、创建和删除。这些词本身实际上是另一个概念,当然,它有自己的术语;积垢。不讨人喜欢的术语 CRUD 代表创建、读取、更新和删除,REST 使用它来定义当 HTTP 方法与 URL 形式的资源组合时,定义的资源会发生什么。所以,如果你把所有这些都归结起来,你会得到下面的图表:

RESTful JSON APIRESTexamples

此图表示:

  • 为了创建资源,您可以使用POST或PUT方法
  • 为了读取资源,您需要使用获取方法
  • 为了更新资源,您需要使用PUT方法
  • 为了删除资源,您需要使用删除方法

正如您所看到的,CRUD 的这个概念非常清楚地表明,当您想要执行特定操作时,您需要使用什么方法。

现在,我们已经了解了 REST 的含义,让我们转到术语API,这意味着应用编程接口。虽然 API 有很多不同的用例和概念,但我们将使用 API 以编程方式与 CouchDB 交互。

现在我们已经定义了所有术语,RESTful JSON API 可以定义如下:我们可以通过向 CouchDB API 发出 HTTP 请求,并使用已定义的资源、HTTP 方法和任何其他数据来与 CouchDB 进行交互。结合所有这些东西意味着我们正在使用 REST。在 CouchDB 处理我们的 REST 请求之后,它将返回一个 JSON 格式的响应以及请求的结果。

当我们使用 CouchDB 的 RESTful JSON API 时,所有这些背景知识都将开始变得有意义,我们将逐一介绍每个 HTTP 方法。

我们将使用 curl(我们在上一章中学习了使用它)通过发出原始 HTTP 请求来探索每个 HTTP 方法。

行动时间-获取 CouchDB 中所有数据库的列表

我们在本书前面使用 curl语句 curl http://localhost:5984时,您已经看到了 GET请求。

这次,让我们发出一个 GET请求来访问 CouchDB 并获取服务器上所有数据库的列表。

  1. 在终端运行以下命令:
    curl -X GET http://localhost:5984/_all_dbs 

    
  1. 终端响应如下:
    ["_users"] 

    

刚才发生了什么事?

我们使用终端触发对 CouchDB 的 RESTful JSON API 的 GET请求。我们使用了一个选项: -X、 curl中的一个来定义 HTTP 方法。在本例中,我们使用了 GET. GET作为默认方法,因此从技术上讲,如果您愿意,您可以省略 -X。一旦 CouchDB 处理了请求,它就会发回 CouchDB 服务器中的数据库列表。目前,只有 _users数据库,这是 CouchDB 用于验证用户身份的默认数据库。

行动时间-在 CouchDB 中创建新数据库

在本练习中,我们将发出一个 PUT请求,它将在 CouchDB 中创建一个新的数据库。

  1. 在终端运行以下命令新建数据库:
    curl -X PUT http://localhost:5984/test-db 

    
  1. 终端响应如下:
    {"ok":true} 

    
  1. 在终端运行以下命令,尝试创建另一个同名数据库:
    curl -X PUT http://localhost:5984/test-db 

    
  1. 终端响应如下:
    {"error":"file_exists","reason":"The database could not be created, the file already exists."} 

    
  1. 好吧,那没用。因此,让我们尝试通过在终端中运行以下命令来创建一个具有不同名称的数据库:
    curl -X PUT http://localhost:5984/another-db 

    
  1. 终端响应如下:
    {"ok":true} 

    
  1. 让我们快速查看 test-db数据库的详细信息,查看更多详细信息。为此,在终端运行以下命令:
    curl -X GET http://localhost:5984/test-db 

    
  1. 终端会做出类似的响应(为了可读性,我重新格式化了我的):
    {
    "committed_update_seq": 1,
    "compact_running": false,
    "db_name": "test-db",
    "disk_format_version": 5,
    "disk_size": 4182,
    "doc_count": 0,
    "doc_del_count": 0,
    "instance_start_time": "1308863484343052",
    "purge_seq": 0,
    "update_seq": 1
    } 

    

刚才发生了什么事?

我们只是使用终端通过 CouchDB 的 RESTful JSON API,通过在 CouchDB 根 URL 末尾传递 test-db作为我们想要创建的数据库的名称,来触发一个 PUT方法来创建数据库。成功创建数据库后,我们收到一条消息,表示一切正常。

接下来,我们创建了一个 PUT请求来创建另一个同名 test-db的数据库。因为不能有多个同名数据库,所以我们收到了一条错误消息。

然后我们使用一个 PUT请求再次创建一个名为 another-db的新数据库。成功创建数据库后,我们收到一条消息,表示一切正常。

最后,我们向我们的 test-db数据库发出 GET请求,以了解数据库的更多信息。确切知道这些统计数据的含义并不重要,但这是了解数据库概况的有用方法。

值得注意的是,在最后的 GET请求中调用的 URL 与我们第一次创建数据库时调用的 URL 相同。唯一的区别是我们将 HTTP 方法从 PUT更改为 GET。这就是行动中的休息!

行动时间-删除 CouchDB 中的数据库

在本练习中,我们将调用一个 DELETE请求来删除 another-db数据库。

  1. 在终端运行以下命令删除 another-db:
    curl -X DELETE http://localhost:5984/another-db 

    
  1. 终端响应如下:
    {"ok":true} 

    

刚才发生了什么事?

我们使用了终端来触发一个 DELETE方法来实现 CouchDB 的 RESTful JSON API。我们在根 URL 的末尾传递了要删除的数据库的名称 another-db。成功删除数据库后,我们收到一条消息,表示一切正常。

行动时间-创建 CouchDB 文档

在本练习中,我们将通过启动 POST调用来创建文档。你会注意到我们的 curl语句会变得更复杂一些。

  1. 在终端运行以下命令,在 test-db数据库中创建文档:
    curl -X POST -H "Content-Type:application/json" -d '{"type": "customer", "name":"Tim Juravich", "location":"Seattle, WA"}' http://localhost:5984/test-db 

    
  1. 终端将以类似于以下内容的方式响应:
    {"ok":true,"id":"39b1fe3cdcc7e7006694df91fb002082","rev":"1-8cf37e845c61cc239f0e98f8b7f56311"} 

    
  1. 让我们从 CouchDB 检索新创建的文档。首先将终端最后一次响应时返回的 ID 复制到剪贴板;我的是 39b1fe3cdcc7e7006694df91fb002082,但你的将不同。然后在终端中运行此命令,将您的 ID 粘贴在 URL 的末尾:
    curl -X GET http://localhost:5984/test-db/41198fc6e20d867525a8faeb7a000015 | python -mjson.tool 

    
  1. 终端将以类似于以下内容的方式响应:
    {
    "_id": "41198fc6e20d867525a8faeb7a000015",
    "_rev": "1-4cee6ca6966fcf1f8ea7980ba3b1805e",
    "location": "Seattle, WA",
    "name": "Tim Juravich",
    "type:": "customer"
    } 

    

刚才发生了什么事?

我们使用终端触发对 CouchDB 的 RESTful JSON API 的 POST调用。这一次,我们的 curl声明获得了一些以前从未使用过的选项。 -H选项使我们能够为 POST方法设置 HTTP 请求的头。我们需要将 content-type设置为 JSON,以便 CouchDB 的 RESTful API 知道将采用什么格式。我们还使用了一个新选项, -d选项,它代表数据。data 选项允许我们以字符串的形式传递数据以及我们的 curl语句。

创建文档后,我们通过向 http://localhost:5984/test-db/41198fc6e20d867525a8faeb7a000015提交 GET请求,将其检索到终端。作为响应,我们收到了一个包含文档所有数据的 JSON 对象。在这个请求的最后,我们做了一些不同的事情。我们添加了 python mjson.tool,这是 Python 中的内置组件,使我们能够很好地格式化 JSON 响应,以便我们能够更好地理解它们。当我们开始查看更复杂的文档时,这将派上用场。

注

我没有在书的前面提到您需要安装 Python,因为这是一个很好的特性。如果由于缺少 Python 而收到错误,您可以通过以下方式安装:http://python.org/download/ 。

我知道这有点让人厌烦,但是 curl将是我们的 PHP 代码用来与 CouchDB 对话的主要方法,所以我们熟悉它的工作原理是很重要的。幸运的是,通过名为Futon的工具,可以更轻松地访问和管理您的数据。

蒲团

CouchDB 附带一个内置的基于 web 的管理控制台,名为 Futon。Futon 允许您在一个简单的界面中管理数据库、用户和文档。Futon 最棒的部分是它已经安装好了,可以使用了,因为它附带了 CouchDB。

让我们来看看:

  1. 打开浏览器。
  2. Go to http://localhost:5984/_utils/.

Futon

这是 Futon 的概览页面。在这个页面上,您可以看到 CouchDB 安装中的所有数据库以及创建新数据库的能力。您应该看到我们在前面的步骤中创建的数据库test-db,您也可以看到默认情况下 CouchDB 安装中的_users数据库。

如果你往窗口的右侧看,你会看到工具。我们将在本书后面介绍复制子时使用此选项。

  1. Let's dig further into our database test-db by clicking on the link for test-db in the database list on the Overview page.

Futon

您看到的页面是数据库详细信息。在此页面中,您可以看到我们数据库中所有文档的列表,以及您可以在所选数据库上执行的一些操作,如新建文档、安全、压缩&清理…、删除数据库、搜索、等。值得注意的是,Futon 只是一个助手,所有这些功能都可以通过curl获得。

  1. Let's dig deeper into Futon by clicking on on a document, and you'll be forwarded to the document detail.

Futon

这些数据看起来应该很熟悉!我们所有的键都列在左边,值列在右边。

行动时间-更新 Futon 中的文件

使用 Futon,您可以轻松更新此文档的值。让我们看一个简单的例子。

  1. 确保已在浏览器中打开文档。
  2. 注意文档中的 _rev值。
  3. 双击位置字段的值:华盛顿州西雅图,并将其更改为纽约州纽约市。
  4. 点击页面顶部的保存文档。
  5. 检查以确保文档中的 _rev值已更改,并且纽约现在是位置值。

刚才发生了什么事?

您刚才使用 Futon 更改文档中某个字段的值,然后保存更改以更新文档。当文档刷新时,您应该注意到 _rev字段已更改,并且您对该字段的更改已更新。

您可能还注意到以前的版本现在看起来可以点击了。点击它,看看会发生什么。Futon 显示文档的旧版本,位置上有华盛顿州西雅图,而新值为纽约州纽约市。

现在您将看到 CouchDB 的修订完全生效。如果您愿意,您可以使用上一版本和下一版本链接循环浏览文档的所有版本。

注

关于 CouchDB 的修订系统,我们需要注意两件重要的事情:

不能更新文档的旧版本;如果试图保存文档的旧版本,CouchDB 将返回文档更新冲突错误。这是因为文档的唯一真实版本是最新版本。

您的修订历史记录只是暂时的。如果您的数据库记录了每一次更改,它将开始变得非常臃肿。因此,CouchDB 有一个名为压缩的特性,它将删除任何旧版本。

行动时间-在 Futon 中创建文档

我们已经完成了现有文件的更新。让我们在 Futon 中从头开始创建一个文档。

  1. 点击标题中的数据库名称 test-db,进入数据库概览。
  2. 点击新增单据。
  3. 将创建一个空白文档,并准备将其放入新字段。请注意, _id已经为我们设置好了(但如果需要,我们可以更改它)。
  4. 点击添加字段创建一个新字段,并将其命名为 location。
  5. 双击标签null旁边的值,输入当前位置。
  6. 点击添加字段创建一个新字段,并将其命名为 name。
  7. 双击标签 null旁边的值,输入您的姓名。
  8. 点击页面顶部的保存文档。
  9. 文档已保存。请注意,它现在设置了一个 _rev值。

刚才发生了什么事?

您刚刚使用 Futon 从头开始创建了一个文档。首次创建文档时,CouchDB 为您创建了一个唯一的 ID,以将其设置为 _id字段的值。接下来,您添加了 name字段,并输入您的姓名作为其值。最后,保存它以创建新文档。我们已经讨论了文档如何具有完全不同的字段,但这是我们第一次真正做到这一点!

安全

到目前为止,我们已经创建、读取、更新和删除了文档和数据库,我们在没有任何安全措施的情况下完成了所有这些工作。当您的 CouchDB 实例上没有任何管理员时,它被称为管理方,这意味着 CouchDB 将处理任何人的任何请求。

行动时间-将 CouchDB 带出管理团队

当您在本地编程时,CouchDB 不安全并不是坏事,但是如果您意外地在一个可公开访问的服务器上有一个不安全的数据库,那么它可能是灾难性的。现在让我们简单地添加安全性,以确保您知道将来如何进行此操作。

  1. 打开蒲团进入概览,看右下角。您将看到这样的文本:
    Welcome to Admin Party!
    Everyone is admin. Fix this. 

    
  1. 点击修复此链接。
  2. A new window will pop up, prompting you Create Server Admin.

Time for action — taking CouchDB out of Admin Party

  1. 输入要用于管理员帐户的用户名和密码,然后单击创建。

刚才发生了什么事?

您刚刚使用 Futon 将服务器管理员添加到 CouchDB 安装中。创建服务器管理员弹出窗口说,一旦添加了服务器管理员,您就可以创建和销毁数据库,并执行其他管理功能。所有其他用户(包括匿名用户)仍然可以读取和写入数据库。考虑到这一点,我们还需要在数据库中添加一些安全性。

行动时间-匿名访问用户数据库

让我们来做一个快速练习,向 _users数据库调用 curl语句,看看为什么保护我们的数据很重要。

  1. 打开终端。
  2. 运行以下命令,将 your_username替换为刚才创建的服务器管理员的用户名。
    curl localhost:5984/_users/org.couchdb.user:your_username | python -mjson.tool 

    
  1. 终端的响应类似于:
    {
    "_id": "org.couchdb.user:your_username",
    "_rev": "1-b9af54a7cdc392c2c298591f0dcd81f3",
    "name": "your_username",
    "password_sha": "3bc7d6d86da6lfed6d4d82e1e4d1c3ca587aecc8",
    "roles": [],
    "salt": "9812acc4866acdec35c903f0cc072c1d",
    "type": "user"
    } 

    

刚才发生了什么事?

您使用终端创建 curl请求,以读取包含服务器管理员数据的文档。数据库中的密码是加密的,但有可能有人仍然会取消密码加密,或使用用户的用户名对其进行攻击。考虑到这一点,让我们保护数据库,以便只有管理员才能访问此数据库。

行动时间-保护用户数据库

让我们保护 _users数据库,以便只有服务器管理员可以读取、写入和编辑系统中的其他用户。

  1. 打开蒲团进入概览。
  2. 点击 _users数据库。
  3. Click on Security at the top of the screen.

Time for action — securing the _users database

  1. Change the values of Roles for both Admins and Readers to ["admins"], so it looks as follows:

Time for action — securing the _users database

刚才发生了什么事?

您刚刚将 _users数据库的管理员和读者角色更改为 ["admins"],这样只有管理员可以阅读或更改设计文档和读者列表。我们制作了角色 ["admins"]的格式,因为它接受数组形式的角色。

是时候采取行动了——检查数据库是否安全

您的 _users数据库应该是安全的,这样只有管理员才能读取或更改数据库的结构。让我们快速测试一下:

  1. 打开终端。
  2. 通过运行以下命令,尝试再次读取用户文档。同样,用您刚刚创建的服务管理员的用户名替换 your_username:
    curl localhost:5984/_users/org.couchdb.user:your_username 

    
  1. 终端响应如下:
    {"error":"unauthorized","reason":"You are not authorized to access this db."} 

    

刚才发生了什么事?

当 CouchDB 实例脱离管理方模式时,身份验证模块介入以确保匿名用户无法读取数据库。

注

接下来我们将为数据库添加更多的安全性,但这是为数据库添加安全性的最简单方法之一。

如果您再次使用命令行,您将受到对 _users数据库执行任何操作的限制,但您也会注意到 test-db数据库的操作与以前一样,完美!这正是我们想要的。您可能会问,既然启用了安全性,如何通过命令行访问 _users数据库?您必须通过向 RESTful JSON API 传递凭据来表明您是管理员。

行动时间-访问启用安全性的数据库

让我们通过在请求中传递用户名和密码来快速访问启用了安全性的数据库。

  1. 打开终端。
  2. 通过运行以下命令查看保存在 _users数据库中的所有文档。将 username和 password替换为管理员的用户名和密码。
    curl username:password@localhost:5984/_users/_all_docs 

    
  1. 终端将使用您在添加身份验证之前看到的相同数据进行响应。
    {
    "_id": "org.couchdb.user:your_username",
    "_rev": "1-b9af54a7cdc392c2c298591f0dcd81f3",
    "name": "your_username",
    "password_sha": "3bc7d6d86da6lfed6d4d82e1e4d1c3ca587aecc8",
    "roles": [],
    "salt": "9812acc4866acdec35c903f0cc072c1d",
    "type": "user"
    } 

    

刚才发生了什么事?

您刚刚向 _users数据库发出 GET请求,并使用我们之前创建的服务器管理员的用户名和密码对我们进行身份验证。一旦通过身份验证,我们就能够正常访问数据。如果你想在一个安全的数据库上执行任何操作,你只需要在你想使用的资源的 URL 前面加上 username:password@。

突击测验

  1. 根据,CouchDB 定义的第一句话是什么 http://couchdb.apache.org/ ?
  2. HTTP 使用的四个动词是什么?每个动词如何与 CRUD 匹配?
  3. 访问 Futon 的 URL 是什么?
  4. “管理方”一词对 CouchDB 意味着什么,您如何让 CouchDB 脱离这种模式?
  5. 如何通过命令行对用户进行安全数据库的身份验证?

总结

在本章中,我们学习了很多关于 CouchDB 的知识。让我们快速回顾一下

  • 我们通过查看数据库、文档和 RESTful JSON API 来定义 CouchDB
  • 我们将 CouchDB 与传统的关系数据库(如 MySQL)进行了比较
  • 我们使用curl语句与 CouchDB 的 RESTful JSON API 交互
  • 我们使用 Futon 创建和修改文档
  • 我们学习了如何为数据库添加安全性,并测试了其有效性

准备好!在下一章中,我们将开始构建 PHP 框架,这将是我们在本书其余部分中开发的平台。