Java 开发 2.0: 通过 CouchDB 和 Groovy 的 RESTClient 实现 REST

REST 式概念和面向文档的数据库实战

在过去数年中,开源世界的创新发展将 Java™ 开发人员的生产力提升了一个层次。免费的工具、框架和解决方案弥补了曾经一度匮乏的空缺。其中,被一些人认为是 Web 2.0 数据库的 Apache CouchDB 是非常有潜力的。全面掌握 CouchDB 并不困难,使用它就像使用 Web 浏览器一样简单。本期 Java 开发 2.0 专栏将介绍 CouchDB 并向您展示如何发挥 Groovy 的 RESTClient 的强大功能

Andrew Glover, 作家和开发人员

Andrew GloverAndrew Glover 是具有行为驱动开发、持续集成和敏捷软件开发激情的开发人员、作家、演说家和企业家。他是 easyb 行为驱动开发(Behavior-Driven Development,BDD)框架的创建者和三本书的合著者:持续集成Groovy 在行动Java 测试模式。您可以通过他的博客与他保持一致并在 Twitter(http://twitter.com/aglover)上关注他。



2009 年 12 月 28 日

目前为止,本专栏 系列 已经探究了 Google 和 Amazon 平台中的云计算。虽然它们在实现和结构上存在不同,但两种平台都支持快速和可扩展的部署方式。它们可以快速经济地组装、测试、运行和维护 Java 应用程序,这无疑是前所未有的。但是,云并不是影响如今 Java 开发速度的唯一因素。开源解决方案还可以帮助您迅速组装软件应用程序,因为您不再需要编写大量代码。手动编写对象关系映射(ORM)、日志或测试框架的时代已经一去不复返。这些问题已经随时间逐渐得到了解决,然后又再次出现在了开源领域中 — 再次面临这些问题 — 但这些解决方案几乎始终要比您自己的好。

关于本系列

从 Java 技术首次亮相以来,Java 开发的格局已经发生了巨大的变化。得益于成熟的开源框架和可靠的租用式部署基础设施,现在已经可以迅速经济地装配、测试、运行和维护 Java 应用程序了。在本系列中,Andrew Glover 将探索使这种全新开发范例成为可能的各种技术和工具。

在 Java 开发的整个过程中,开源创新简化了应用程序的装配过程。全新的开源数据库 Apache CouchDB(截至撰写本文时的发行版为 0.10.0) 也不例外。搭建好 CouchDB 环境之后可以轻松地使用它。您只需要操作它的 HTTP 连接即可;既不需要 JDBC 驱动程序,也不需要第三方控制管理平台。在本文中,我将向您介绍 CouchDB 并展示如何使用它提升开发速度。考虑到安装的简便性,您将使用 Amazon 的 EC2 平台。并且,您将通过一个方便易用的 Groovy 模块来与它通信。

面对文档的数据库

关系数据库基本上主导了数据库市场。但其他类似的数据库 — 包括 面向对象面向文档 数据库,两者在面向关系的世界中极为不同 — 也不时发挥着举足轻重的作用。CouchDB 是一种面向文档的数据库。它是无模式的,并且允许您以 JavaScript Object Notation (JSON) 字符串的形式来存储文档。

JSON

JSON 是一种轻量级的数据交换格式,同时也是 Web 应用程序的替代格式。它类似于 XML,但远没有它那么详细。得益于其轻量级特性,它正成为 Web 的 通用语。参见 参考资料,获取关于 JSON 的更多信息。

试想一个违规停车罚单。该罚单将涵盖以下项目:

  • 违规的日期
  • 时间
  • 位置
  • 车辆的描述
  • 牌照信息
  • 违规情况

罚单上收集的格式和数据因管辖权而异。即使对于单一管辖权限内的标准停车罚单来说,它们的内容也很有可能存在不同。举例来说,警官在开罚单时可以不填写时间,或者可以省略车型,而只填写牌照的详细信息。地点可以是两条街道的组合(比如 Fourth 和 Lexington 的交汇处),或者是某个固定地址(比如说 19993 Main Street)。但所采集信息的语义大抵相似。

罚单的数据点可以在关系数据库中建模,但详细信息却有点麻烦。举例来说,如何有效在关系数据库中捕获某个交汇点呢?并且在没有街道交汇的情况下,数据库是否会使用空字段来表示第二个地址呢(假定建模方式为在各列中捕获不同的街道名)?

在这些情况下,关系数据库的抽象化程度可能 会稍高一些。所需的信息已经采用了文档(罚单)的形式。为什么不将数据建模为文档呢?这样可以不用死守严格的关系模式,而只需要大致遵循高级模式的语义。这正是 CouchDB 的用武之地。它允许您以灵活的方式来对这些域类型进行建模 — 成果是一个完备的文档,它没有模式,而是使用与其他文档大致相似的蓝图。

MapReduce

Google 独创的 MapReduce 是一个用于处理海量数据集的概念框架(参见 参考资料)。它是一种高度优化的应用大量计算机的分布式问题解决机制。MapReduce 包含两个函数:map 和 reduce。map 函数用于接受大量输入,并将它们分割为较小的部分(同时将这些数据传递给其他进程)。reduce 函数的作用是将来自 map 的所有单独输出整合为一个最终的输出。

借助 CouchDB,您可以搜索文档、文档属性甚至在关系世界中关联文档。您的实现方式是使用视图,而不是 SQL。从本质上说,视图是您采用 MapReduce 样式(在 JavaScript 中)编写的函数;也就是说,您最终只需要编写一个 map 函数和一个 reduce 函数。这些函数将共同过滤或提取文档数据,或者有效利用它们之间的关系。事实上,CouchDB 具备足够的灵活性,只要底层文档没有发生发化,它就只需要运行这些函数一次,从而加快视图处理过程。

CouchDB 最有意思的地方是它的设计方式。CouchDB 体现了 Web 本身的基本(也是极为成功的)概念。它公开了一组全面的 REST 式 API,允许创建、查询、更新和删除文档、视图和数据库。这使得 CouchDB 的使用变得非常简单。您不需要借助其他驱动程序或平台来开始开发:一个浏览器便能完成所有工作。也就是说,丰富的库使 CouchDB 的使用变得非常简单 — 但从内部来看,它们仅仅是通过 HTTP 来利用 REST 式概念。

与 Web 的本质特性相类似,CouchDB 在设计时融入了大量可扩展因素。它是使用并发编程语言 Erlang 编写的,它支持绑定分布式、容错、不间断应用程序(参见 参考资料)。该语言(现已开源可用)是由 Ericsson 开发的,并在电信环境中得到了广泛应用。


安装 CouchDB,云风格

CouchDB 的安装方法因操作系统而异。如果使用的是 Windows®,则需要安装 Microsoft C 编译器 Cygwin,以及其他一些依赖项。如果使用的是 Mac,则需要使用 Macports。但是,如果使用的是 Linux® 平台,比如说 Ubuntu,则安装方法就不是那么简单了。但并非所有人都安装了 Ubuntu 实例吧。您是这样吗?

当然,您可以方便的获取一个 Ubuntu 实例!Amazon 的 EC2 是一种相对较为经济的、随需应变地 使用 Ubuntu 的一种方法。因此,只需要施展少许 EC2 魔法,您就可以迅速搭建好 CouchDB 环境;完成后,您可以关闭它(可以这样说)。

首先,您需要找到一个充当基本实例的 EC2 AMI。我最终决定使用 AMI ami-ccf615a5(一个 Ubuntu 9.04 实例),它是截至撰写本文时的最新版本。(当您阅读本文时,很有可能已经出现了 9.10 版本的 AMI)。使用 Eclipse 或者 AWS Management Console 启动一个 ami-ccf615a5 实例。确保设置了允许通过 SSH 访问的安全策略。(虽然 CouchDB 使用 HTTP,但考虑到简单性,您将通过一个 SSH 通道来与它通信)。您还需要使用一个值对。(如果需要指导,请参见本系列的前两篇文章 “您也可以租用 EC2” 和 “使用方便的 EC2”。)

启动了 Ubuntu 9.04 的 EC2 实例之后,您需要为它设置 ssh。(记住,该实例大约要 1 分钟时间才能完全启动,因此请耐心等待。)举例来说,我可以打开一个终端并使用 ssh 设置新创建的实例,如下所示:

aglover#> ssh -i .ec2/agkey.pem root@ec2-174-129-157-167.compute-1.amazonaws.com

我的 AMI 的 DNS 名称是 ec2-174-129-157-167.compute-1.amazonaws.com,并且我引用的值对的名称是 agkey。您的 DNS 名称和值对肯定会不一样。

在云 Ubuntu 实例的命令提示中,输入:

apt-get update

然后输入:

aptitude install couchdb

这些命令会自动安装 CouchDB。但是,注意它们不会安装最新版本。如果需要最新版本,则需要通过源代码来安装 CouchDB(参见 参考资料)。

命令执行结束后,您可以通过发出 ps -eaf 命令来检查 CouchDB 是否已经正常运行。通过将 ps 输出传递给 egrep,查看在路径中使用 couchdb 的进程。您应该能看到如清单 1 所示的输出:

清单 1. CouchDB 正在运行中(各行经过分段以适应页面宽度)
couchdb   1820     1  0 00:54 ?        00:00:00 /bin/sh -e /usr/bin/couchdb 
   -c /etc/couchdb/couch.ini -b -r 5 -p /var/run/couchdb.pid -o /
couchdb   1827  1820  0 00:54 ?        00:00:00 /bin/sh -e /usr/bin/couchdb 
   -c /etc/couchdb/couch.ini -b -r 5 -p /var/run/couchdb.pid -o /
couchdb   1828  1827  0 00:54 ?        00:00:00 /usr/lib/erlang/erts-5.6.5/bin/beam 
   -Bd -- -root /usr/lib/erlang -progname erl -- -home /v
couchdb   1836  1828  0 00:54 ?        00:00:00 heart -pid 1828 -ht 11

接下来,回到本地机器,设置一个 SSH 通道来允许访问在云上运行的 CouchDB 实例,就像是在自己的机器上一样。为此,在本地机器上打开一个新的终端会话,然后输入:

ssh -i your key -L 5498:localhost:5984 root@your AMI DNS

最后,在本地机器上打开一个浏览器。在地址栏中,输入 http://127.0.0.1:5498/。您应该可以看到一个漂亮的 JSON 欢迎消息,如下所示:

{"couchdb":"Welcome","version":"0.8.0-incubating"}

现在,看上去一切运转正常,接下来可以开始应用 CouchDB 了。


采用 REST 风格使用 Groovy 的 RESTClient

REST

在表示状态传输(REST)设计风格中,松散耦合的 Web 应用程序将依赖于指定资源 — 比如采用统一资源定位符(URL)、统一资源标识符(URI)和统一资源名称(URN)的形式 — 而不是消息。REST 明智地采用了 Web 中经过验证并 获得了成功的基础设施 — HTTP。也就是说,REST 将利用 HTTP 协议的各个方面,比如 GET 和 POST 请求。这些请求将很好地满足业务应用需求,比如创建、读取、更新和删除(CRUD)。

由于 CouchDB 通过以上 REST 式 HTTP 接口来公开数据,因此使用 CouchDB(您已经在浏览器中见识一二)是相当简单的。几乎所有工作都可以通过 HTTP 来完成。

您可以选择各种工具来与 HTTP 进行交互。在使用 REST 式接口时,我比较偏爱 Groovy HTTPBuilder 的 RESTClient 扩展(参见 参考资料)。HTTPBuilder — 针对 Apache Commons Project 的 HTTPClient 的包装器 — 在 HTTP POST、GET、PUT 和 DELETE 的语法中添加了一些 Groovy 因素。由于 HTTPBuilder 是使用 Groovy 创建的,因此编写利用 REST 式概念的脚本(比如与 CouchDB 通信)都简单得不能再简单了。

Grape 的进一步简化

为了与 Java 开发 2.0 的一般主题保持一致 — 快速、简便和免费(或便宜) — Groovy 的便捷的 Grape(Groovy Advanced Packaging Engine 或 Groovy Adaptable Packaging Engine)特性对于与 HTTPBuilder 这样的交互非常有用(参见 参考资料)。Grape 是一种依赖项管理器,它允许 Groovy 脚本和类在运行时 自动配置自己的特定依赖项。这简化了各种开源库的使用,因为您不需要下载各种 JAR 文件便可开始编写代码。举例来说,借助 Grape,您可以编写一个 Groovy 脚本来使用 HTTPBuilder,而不需要 HTTPBuilder 的 JAR。借助 Grape,可以在运行时或编译时下载它们(通过 Apache)。

您将通过注释和方法调用来利用 Grape。举例来说,您可以使用 @Grab 注释来修饰某个方法或类声明。在该注释中,您将主依赖项指定一些相关的元数据(借助 Ivy 的魔力,所有中间依赖项都可以悉数确定)。在运行时或编译时(无论孰前孰后),Grape 将下载这些依赖项并确保它们在您的类路径下。如果已经下载了依赖项(比如说从之前的运行中),则 Grape 仍然会确保类路径下包含适当的 JAR 文件。


通过 Groovy 简化 CouchDB 的 REST 风格

在可以在 CouchDB 中创建任意文档之前,您必须首先创建一个数据库。要创建一个停车罚单数据库,可以通过 HTTPBuilder 的域相关语言(DSL)使用其 RESTClient 来发出一个 HTTP PUT,如清单 2 所示。(本文示例中的所有 Groovy 代码都可以从 下载 小节获得。)

清单 2. 创建一个 CouchDB 数据库
import static groovyx.net.http.ContentType.JSON
import groovyx.net.http.RESTClient

@Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', 
   version='0.5.0-RC2')
def getRESTClient(){
  return new RESTClient("http://localhost:5498/")
}

def client = getRESTClient()
def response = client.put(path: "parking_tickets",
        requestContentType: JSON, contentType: JSON)

assert response.data.ok == true : "response from server wasn't ok"

CouchCB 应该会返回应答 {"ok":true}。如清单 2 所示,在 HTTPBuilder 中可以轻松地解析 JSON 并确保 ok 元素的值确实为 true

接下来,我们需要创建一些文档,以便与停车罚单的主题保持一致。要建立一个停车罚单模型,需要记住一些与罚单相关的方面。另外需要记住,由于它们是警官将填写的实际表单,因此一些字段可以不填或者采用预定义的模式 — 考虑交汇点与准确地点。

使用 HTTPBuilder,您可以通过 HTTP PUT 在 CouchDB 中创建一个文档(就像在清单 2 中创建数据库一样)。由于 CouchDB 将处理 JSON 文档,因此您必须遵循 JSON 的名称值格式。为此,在 Groovy 中创建一种类似于映射的数据结构(HTTPBuilder 会将其转换为有效的 JSON)。如清单 3 所示:

清单 3. 通过 RESTClient创建一个 CouchDB 文档
response = client.put(path: "parking_tickets/1234334325", contentType: JSON,
        requestContentType:  JSON,
        body: [officer: "Kristen Ree",
                location: "199 Baldwin Dr",
                vehicle_plate: "Maryland 77777",
                offense: "Parked in no parking zone",
                date: "2009/01/31"])

assert response.data.ok == true : "response from server wasn't ok"
assert response.data.id == "1234334325" : "the returned ID didn't match"

清单 3 完成了多项任务。首先,在为 CouchDB 文档发出 PUT 时,您必须分配一个 UUID。CouchDB 可以为您分配这些值,或者您也可以自已管理它们。在清单 3 中,我设定了一个值(1234334325);随后,该 UUID 将附加到 URL。如果该 UUID 可用,则 CouchDB 会为它分配执行了 PUT 操作的文档。在 put 调用的 body 部分,注意为各名称指定相关值的方式,几乎与普通的映射无异。举例来说,指定警官的名称为 Kristen Ree,而罚单的地点是 199 Baldwin Dr。

清单 4 采用相同的技巧在 CouhDB 中创建了另外一个罚单:

清单 4. 另一个停车罚单
def id = new Date().time
response = client.put(path: "parking_tickets/${id}", contentType: JSON,
        requestContentType:  JSON,
        body: [officer: "Anthony Richards",
                location: "Walmart Parking lot",
                vehicle_plate: "Delaware 4433-OP",
                offense: "Parked in non-parking space",
                date: "2009/02/01"])


assert response.data.ok == true : "response from server wasn't ok"
assert response.data.id == "${id}" : "the returned ID didn't match"

每次通过 RESTClient 发出 PUT 时,我都断言 JSON 应答的 ok 值为 TURE,并且验证是否存在 id 值。注意在清单 4 中,我没有创建 UUID,而是使用了当前的时间 — 并不是非常简单的技巧,但我已经不再满足于使用简单的交汇点。

在 CouchDB 中成功创建了新文档之后,它会返回一个包含 UUID 和修订 ID 的 JSON。举例来说,该应答表示我在清单 4 中验证的 JSON:

{"ok":true,"id":"12339892938945","rev":"12351463"}

您的 idrev 值肯定不一样。注意,我可以通过发出 response.data.id 这样的调用来捕获 id 值。

在 CouchDB 中,它将通过修订来跟踪文档,因此您可以返回之前的文档版本(通过修订 ID),这与 CVS 或 Subversion 中的方法极为类似。


CouchDB 中的视图

现在,我已经创建了一些停车罚单(或者,用 CouchDB 的术语来说是一些文档),接下来可以在 CouchDB 中创建一些视图了。记住,视图就是实际的 MapReduce 函数;因此,您必须定义它们。在许多情况下,您都不需要 reduce 函数;map 函数可以帮助您完成大多数任务。正如其名,它是任务的映射。举例来说,您可以映射希望过滤或查找的任何 “事物” 或方面。

我已经定义了两个罚单:一个由 Officer Ree 开示,而另一个是由 Officer Richards 发出的。举例来说,要查找 Officer Ree 开出的所有罚单,您可以编写一个 map 函数来过滤相应的 officer 属性。然后,您可以将结果传递给 CouchDB 的 emit 函数。

使用 CouchDB 的管理接口:Futon

您可以通过 CouchDB 的 REST 式 API 或通过 CouchDB 的管理接口 Futon 来定义视图。Futon 仅仅是一个 Web 应用程序,可以从 http://localhost:5498/_utils/ 下载它。立即访问该位置(假定您已经跟随我创建了数据库和一些文档),您应该能看到一个针对 parking_tickets 的简单接口,如图 1 所示:

图 1. Futon 接口
Futon 接口屏幕快照

如果选择 parking_tickets 数据库,则随后可以在最右侧看到一个下拉列表(Select view:)。通过选择 Custom query...来定义一个自定义视图,如图所示:

图 2. Futon 的视图选择接口
Futon 的视图选择接口屏幕快照

现在,Futon 接口可允许您定义 map 函数和 reduce 函数。(您可能需要单击 查看代码 链接)。在 Map 文本框中,定义如清单 5 所示的简单映射:

清单 5. CouchDB 中的简单的 map 函数
function(doc) {
  if(doc.officer == "Kristen Ree"){
    emit(null, doc);
  }
}

如您所见,清单 5 中的 map 函数是使用 JavaScript 定义的。它的作用是通过文档的 officer 属性来过滤 CouchDB 数据库中的文档。特别需要说明的是,仅当警官的名称为 Kristen Ree 时,该函数才会传递一个文档给 emit。图 3 显示了在 Futon 中定义此函数的位置:

图 3. 创建一个 MapReduce 函数
futon

接下来,您需要指定文档名称(输入 by_name)和视图名称(输入 officer_ree)。这些名称将作为建立 URL 以便稍后调用此视图的途径(该 URL 也就是 http://localhost:5498/parking_tickets/_view/by_name/officer_ree)。

现在,您可以通过 HTTPBuilder 来使用该视图:

清单 6. 调用您的新视图
response = client.get(path: "parking_tickets/_view/by_name/officer_ree", 
        contentType: JSON,  requestContentType:  JSON)

assert response.data.total_rows == 1

response.data.rows.each{
   assert it.value.officer == "Kristen Ree"
}

该视图将返回一个 JSON 应答,其中只包含一个文档:Officer Ree 于 1 月 31 日开出的罚单。通过解析相应的 JSON,清单 6 中的 response 对象将隐藏原始 HTTP 应答。您可以通过对 responsedata 属性调用 toString 方法来查看原始 JSON 应答。原始应答将如清单 7 所示:

清单 7. 视图的原始结果
{"total_rows":1,"offset":0,"rows":[
  {"id":"1234334325","key":null,
   "value":{"_id":"1234334325","_rev":"4205717256","officer":"Kristen Ree",
       "location":"199 Baldwin Dr","vehicle_plate":"Maryland 77777",
       "offense":"Parked in no parking zone","date":"2009/01/31"}}]}

从返回的原始 JSON 文档中可以看出,HTTPBuilder 可以非常轻松地解析 JSON,因为它支持通过类似于对象图的机制来估计各种属性及其相应的值。

为便于演示,我将向数据库再添加一些文档。为了跟随示例,您应该使用 代码下载 完成相同的任务。

CouchDB 的 emit 函数将充当各种形式的组织器。如果未在 map 函数中添加限制(像我在 清单 5 中做的那样),则 emit 的基本作用是对传入文档进行排序。举例来说,如果您希望按日期获取所有罚单(此处可看作 SQL 的 ORDER BY 语句),则可以按文档的 date 来执行 emit,如清单 8 所示:

清单 8. 一个比较简单的 map 函数
function(doc) {
  emit(doc.date, doc);
}

清单 9 向此视图发出了一个 HTTP GET(我已经指定 dates 作为设计文档名,by_date 作为视图名。)。

清单 9. 调用的另一个视图
response = client.get(path: "parking_tickets/_view/dates/by_date", contentType: JSON,
        requestContentType:  JSON)
assert response.data.total_rows == 4

清单中的查询将按日期顺序返回 parking_tickets 数据库中的所有文档。assert 语句仅验证 total_rows 属性是否等于 4。这是一个关键点。视图将返回一些结果以及少许元数据(比如说返回文档的数量);因此,它将帮助在开始解析之前查看原始应答。清单 10 显示的原始结果:

清单 10. 按日期排序的原始 JSON 文档
{"total_rows":4,"offset":0,"rows":[
  {"id":"85d4dbf45747e45406e5695b4b5796fe","key":"2009/01/30",
   "value":
    {"_id":"85d4dbf45747e45406e5695b4b5796fe","_rev":"1318766781",
     "officer":"Anthony Richards",
     "location":"54th and Main","vehicle_plate":"Virginia FCD-4444",
     "offense":"Parked in no parking zone","date":"2009/01/30"}},
  {"id":"1234334325","key":"2009/01/31",
   "value":
    {"_id":"1234334325","_rev":"4205717256",
     "officer":"Kristen Ree",
     "location":"199 Baldwin Dr","vehicle_plate":"Maryland 77777",
     "offense":"Parked in no parking zone",
     "date":"2009/01/31"}},
  {"id":"12345","key":"2009/01/31",
   "value":
    {"_id":"12345","_rev":"1479261876",
     "officer":"Anthony Richards","location":"1893 Main St",
     "vehicle_plate":"Maryland 4433-OP",
     "offense":"Parked in no parking zone","date":"2009/01/31"}},
  {"id":"12339892938945","key":"2009/02/01",
   "value":
    {"_id":"12339892938945","_rev":"12351463","officer":"Anthony Richards",
     "location":"Walmart Parking lot","vehicle_plate":"Maine 4433-OP",
     "offense":"Parked in non-parking space",
    "date":"2009/02/01"}}]}

在定义这样的视图时,您可以随后向它传递一个— 也就是 emit 函数的初始值。举例来说,清单 8 中定义的视图主要用于按日期排序。如果希望按具体的 日期,则将该日期传递给视图查询。举例来说,可以在浏览器的地址栏中输入以下 URL:

http://localhost:5498/parking_tickets/_view/dates/by_date?key="2009/01/31"

然后,这个视图只会返回 1 月 31 日发出的罚单。您应该可以在浏览器窗口中看到一些 JSON 样式的文本,它们与清单 11 中的内容相似。注意,使用浏览器作为查询工具是查看 HTTP 请求的原始 JSON 应答的一种极其简单的方式。

清单 11. 1 月 31 日仅开出了两张罚单
{"total_rows":4,"offset":1,"rows":[
   {"id":"1234334325","key":"2009/01/31",
    "value":
     {"_id":"1234334325","_rev":"4205717256","officer":"Kristen Ree",
       "location":"199 Baldwin Dr","vehicle_plate":"Maryland 77777",
       "offense":"Parked in no parking zone",
       "date":"2009/01/31"}},
   {"id":"12345","key":"2009/01/31",
    "value":
     {"_id":"12345","_rev":"1479261876","officer":"Anthony Richards",
       "location":"1893 Main St","vehicle_plate":"Maryland 4433-OP",
       "offense":"Parked in handicap zone without permit",
        "date":"2009/01/31"}}]}

您可以根据需要来定制视图的功能。举例来说,只需要少量 JavaScript 字符串操作,我就可以编写一个查找 Main Street 上开出的罚单的视图,如清单 12 所示:

清单 12. 另一个添加了字符串魔力的视图
function(doc) {
  if(doc.location.toLowerCase().indexOf('main') > 0){
   emit(doc.location, doc);
  }
}

可以从清单 12 中看出,如果任何文档的 location 元素包含 main,则该文档将被传递给 emit 函数。记住,这种搜索相当广泛。如果某文档的 location 包含一个 Germaine Street 这样的字符串,则会返回它。对于我所定义的少量罚单,视图将返回如清单 13 所示的结果:

清单 13. 按 Main Street 过滤的结果
{"total_rows":2,"offset":0,"rows":[
  {"id":"123433432asdefasdf4325","key":"4th and Main",
   "value":
    {"_id":"123433432asdefasdf4325","_rev":"498239926",
    "officer":"Chris Smith","location":"4th and Main",
      "vehicle_plate":"VA FGA-JD33",
      "offense":"Parked in no parking zone","date":"2009/02/01"}},
 {"id":"123433432223e432325","key":"54 and Main",
   "value":
    {"_id":"123433432223e432325","_rev":"841089995",
    "officer":"Kristen Ree","location":"54 and Main Street",
      "vehicle_plate":"Maryland 77777",
      "offense":"Parked in no parking zone","date":"2009/02/02"}}]}

注意,JSON 应答包含一个 key 元素,用于描述为何要生成特定的文档。这一方面的信息相当有用。还需注意,我所定义的各种罚单中的数据在一致性方面做的不是很好:一些地点是准确的,但也有一些是不精确的。虽然该数据可以存储在关系数据库中,但也同样可以使用面向文档的模型。另外,借助 Groovy 和 HTTPBuilder 的高效解析 JSON 的强大能力,可以非常轻松地获取数据(比原始 JDBC 简单很多)。


CouchDB 作为 Web 数据库

CouchDB 的迷人之处在于它的使用非常简单。关系数据库也很简单,但该数据库的优势在于您只需要熟悉 Web 浏览器就可以掌握其 API。此外,由于 CouchDB 提供了 REST 式 API,您可以通过一些炫酷的框架来与其通信,比如说 HTTPBuilder 的 RESTClient。您也不会受制于 HTTPBuilder;各种 Java 库都在尝试简化 CouchDB 的使用。其中非常有潜力的一个就是 jcouchdb(参见 参考资料),它可以帮助您避免 REST 化和 JSON 化,同时允许您以编程方式使用 Java 语言操作文档和视图。

敬请期待下个月的专栏文章,我将回过头来讨论 Google App Engine。在开放创新精神的大旗下,各种新框架不断涌现,旨在促进 Google App Engine 的开发和部署。您将了解它们如何能够简化 Google 云平台上的 Java 开发 2.0 。


下载

描述名字大小
文章示例的 Groovy 源代码j-javadev2-5.zip2KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=459163
ArticleTitle=Java 开发 2.0: 通过 CouchDB 和 Groovy 的 RESTClient 实现 REST
publish-date=12282009