五、Meteor 风格的数据
我们几乎已经完成了借阅图书馆应用,无需过多担心数据的存储方式。 Meteor 的数据缓存和同步方法是有意构建的,使构建应用的这一部分尽可能简单,这样你就不用花大量时间在数据库连接、查询和缓存上,而可以专注于编写一个伟大的程序。
但是,我们确实想回顾一下方法论,确保我们对 Meteor 如何处理数据有一个坚实的理解,这样我们就可以执行一些常见的优化,并更快地构建我们的应用。
在本章中,您将学习以下主题:
- MongoDB 和面向文档的存储
- 广播变化-Meteor 如何使您的 web 应用反应
- 配置发布者-如何精简和保护您的数据
文档存储
Meteor 使用 MongoDB (minimongo)的一个版本来存储模型中的所有数据。 它能够使用任何其他的 NoSQL/面向文档的数据库,但 MongoDB 默认带有 Meteor 安装。 这个功能使您的程序更简单,更容易编写,并且在快速、轻量级的数据存储方面工作得非常好。
但是为什么不使用关系数据库呢
传统上,数据是使用关系模型存储的。 关系模型及其所有相关规则、关系、逻辑和语法是现代计算不可或缺的重要组成部分。 关系数据库的严格结构,以及对每个记录、关系和关联的精确要求,为我们提供了快速搜索、可伸缩性和深度分析的可能性。
然而,这种精确并不总是必要的。 例如,在我们的借阅库中,使用一个成熟的关系数据库就有点过分了。 事实上,在某些情况下,拥有一个灵活的数据存储系统更有效,您可以快速扩展该系统,而无需进行重大的编码。
举个例子,如果你想添加一个新属性list
对象,这将是更简单的数据库添加新的属性,让担心,而不必重构数据库代码,添加一个新列,更新你所有的 SQL 语句和触发器,并确保所有先前的记录新财产。
这就是面向文档存储发挥作用的地方。 在面向文档的存储系统中,数据存储由一组键值对的文档组成。 那份文件的结构是什么? 数据存储并不真正关心。 它可以包含几乎任何东西,只要每条记录有一个唯一的键,以便它可以被检索。
在一个文档条目中,你可以有一个非常简单的文档。 也许是键值对。
{name:phone_number}
然后,在另一个文档条目中(在同一个数据存储中),您可以有一个复杂的对象,具有嵌套数组、嵌套对象等等。
{ people: [
{firstname:"STEVE", lastname:"Scuba", phones :[
{type:cell, number:8888675309},
{type:home, number:8005322002}]
},
{firstname:...
...
}]
}
见鬼,它可能是威廉·莎士比亚的未删节作品。 这并不重要。 只要数据存储能够获取该文档并为其分配唯一的键,就可以存储该文档。
您可能已经猜到,结构的缺乏可能会降低查询、排序和操作这些文档的效率。 但这没关系,因为我们主要关心的是编码的便捷性和开发速度,而不是效率。
此外,因为我们的应用只有几个核心功能,所以我们可以快速确定最常使用的查询,并围绕这些查询优化文档模式。 在某些情况下,这使得面向文档的数据库实际上比传统的关系数据库执行更好。
提示
有一些非常复杂的面向文档的存储解决方案,有些人可能会认为它们与标准关系数据库一样有效,甚至更有效,但这些讨论超出了本书的范围。
考虑到面向文档存储系统的灵活性,它非常适合快速更改,Meteor 提供的基础库使我们不必担心连接或结构。 我们所要做的就是对如何检索、添加和修改这些文档有一个高层次的了解,我们可以把剩下的留给 Meteor。
MongoDB
mongodb 是一个开源的 NoSQL(不仅仅是 SQL)数据库。 它提供了复杂的特性,如索引、链接和原子操作,但其核心是面向文档的存储解决方案。
提示
如需了解更多 MongoDB 信息,请访问官方网站http://www.mongodb.org。
使用简单的命令,我们可以看到哪些记录(哪些文档)是可用的,将这些记录转换为 JavaScript 对象,然后保存那些更改过的对象。 把 MongoDB 记录想象成一个实际的文本文档:
- 找到文档,并打开它进行编辑(Meteor 等效:
lists.find (...)
)。 - 对文档进行更改(Meteor 等效:
lists.update({...})
)。 - 保存文件(使用
.update()
功能自动完成)。
它不是那么简单,你需要有很多的语法学习如果你要考虑自己的专家 MongoDB,但是你可以清楚地看到一个简单,干净的面向文档的方法:查找/创建一个记录,修改,保存/更新记录到数据存储。
我们需要讨论最后一个概念,以帮助您更好地理解 MongoDB 如何工作。 它被称为数据库,但更容易将其概念化为文档的集合。 集合被索引并且可以快速访问,但它仍然是一个集合,而不是一组关系表/实体。 就像你会想到一个文件夹在你的硬盘上,你保存所有的文本文件,想想 MongoDB 作为一个文件的集合,所有的文件都是可访问的,可以“打开”,改变,和保存。
使用直接命令
为了更好地理解 MongoDB 的工作方式,让我们在命令行中玩一些有趣的。
- 首先,确保您的应用已经启动并正在运行(打开一个终端窗口,
cd
到~/Documents/Meteor/LendLib
目录,并执行meteor
命令)。 接下来,打开浏览器到http://localhost:3000
。 -
Now, you will want to open an additional terminal window,
cd
to the~/Documents/Meteor/LendLib
directory, and run the following command:``` meteor mongo
```
你应该会看到类似如下截图的消息:
现在,您已经为您的 Lending Library 应用连接到了正在运行的 MongoDB 数据库。 让我们来看看几个命令。
-
首先,让我们打开帮助屏幕。 输入以下命令,点击,输入:
```
help
```
-
You'll get a list of commands, and a brief explanation of what each one does. One in particular will show us even more commands we can use:
db.help()
. This will give us a list of database-related commands. Type the following into your terminal window, and press Enter:```
db.help()
```
不要被可能的命令数量所压倒。 你不需要知道所有这些,除非你想成为 MongoDB 的专家。 你只需要知道一些,但四处看看不会伤害任何人,所以让我们继续。
-
如前所述,文档在 MongoDB 中存储在一个称为集合的逻辑分组中。 我们可以直接看到这一点,并在终端窗口直接查看我们的列表集合。 要查看所有可用集合的列表,请输入以下命令:
-
在响应中,您将找到您的借阅图书馆收藏的名称:
lists
。 让我们来看看lists
系列。 输入如下:```
db.getCollection('lists')
```
-
Well, that wasn't very exciting. All we got back was
meteor.lists
. We want to be able to perform some queries on that collection. So this time, lets assign the collection to a variable.```
myLists = db.getCollection('lists')
```
看起来我们得到了和上次一样的结果,但我们得到的比上次多得多。 我们现在已经将
lists
集合赋值给变量myLists
。 因此,我们可以在终端窗口中运行命令,就像在 Meteor 代码中一样。 -
Let's get the
Clothes
list, which currently doesn't have any items in it, but still exists. Enter the following command:```
Clothes = myLists.findOne({Category:"Clothes"})
```
这将返回一些非常基本的 JSON。 如果您仔细查看,您将能够看到以
"items" : [ ]
表示的空项目数组。 你还会注意到一个_id
键值,旁边有一个很长的数字,类似如下:"_id" : "520e4f45-8469-47b9-8621-b41e60723de0",
我们没有添加
_id
。 MongoDB 为我们创建了它。 这是一个唯一的密钥,所以如果我们知道它,我们就可以在不影响其他文档的情况下对该文档进行更改。 我们实际上在我们的借阅库应用中在多个位置使用这个。
如果你看看里面的~/Documents/Meteor/LendLib/LendLib.js
,你会看到下面的函数添加一个项目到一个列表:
function addItem(list_id,item_name){
if (!item_name&&!list_id)
return;
lists.update({_id:list_id},
{$addToSet:{items:{Name:item_name}}});
}
注意,当我们调用lists.update()
函数时,我们通过_id
确定要更新哪个文档。 这确保了我们不会意外地更新多个文档。 例如,如果您要给两个列表提供相同的类别名称(例如,"DVDs"
),并使用类别作为选择器({Category:"DVDs"}
),那么您将对两个类别列表采取操作。 相反,如果您使用_id
,它只会用匹配的_id
更新惟一的文档。
回到终端,我们现在将变量myLists
赋给了我们的lists
集合,并将Clothes
变量赋给了代表Clothes
列表的lists
集合中的文档。
请注意当前浏览器中的Clothes
列表。
让我们继续,并将我们最喜欢的衬衫添加到Clothes
列表。 我们将直接在终端窗口中进行操作。 输入以下命令:
>myLists.update({_id:Clothes._id},{$addToSet:{items:{Name:"Favorite Shirt"}}})
该命令使用Clothes._id
作为选择器更新myLists
,并调用$addToSet
,使用Name:"Favorite Shirt"
添加项。 Meteor 更新需要几秒钟,但你很快就会看到你最喜欢的衬衫现在添加到列表中。
如果您重新运行Clothes
赋值命令Clothes = myLists.findOne({Category:"Clothes"})
,您现在将看到items
数组中有您喜欢的衬衫的条目。
我们可以使用带有不同参数的.update()
函数($pull
用于删除,$set
用于更新)来轻松地更新或删除一个项目。
提示
对于代码示例,请查看LendLib.js
中的removeItem()
和updateLendee()
函数。
更多关于 MongoDB 命令的深入教程,请访问http://mongodb.org,并点击TRY IT OUT。
现在,我们已经介绍了一些可以直接实现的命令,让我们回顾一些LendLib.js
代码,并讨论跟踪集合更改的响应式代码。
广播改变
通过使用发布/订阅模型,Meteor 一直在寻找集合和Session
变量的变化。 当发生更改时,将广播(或发布)更改事件。 回调函数正在监听(或订阅)正在广播的事件,当它订阅的特定事件被发布时,函数中的代码被激活。 或者,数据模型可以直接绑定到 HTML/Handlebars 模板的部分,这样当发生更改时,HTML 就会重新呈现。
已发布事件
那么,什么时候发布事件呢? 正如前面提到的,当模型中有变化时,事件被广播。 换句话说,当一个集合或变量被修改时,Meteor 发布适当的更改事件。 如果将文档添加到集合中,则会触发事件。 如果已经在集合中的文档被修改,然后又被保存回集合中,则会触发一个事件。 最后,如果改变了Session
变量,则触发一个事件。 函数和模板正在侦听(订阅)特定事件,并将适当地处理数据中的变化。
第三章,为什么是陨石! ,这是模型视图视图模型模式在工作。 在响应式上下文中,函数和模板对模型中的更改作出反应。 反过来,视图中的操作将通过视图-模型逻辑创建对模型的更改:
MeteorMVVM 是一种干净、简洁的开发模式:
- 为模型更改设置订阅者(模型=集合、文档和
Session
变量)。 - 创建处理视图事件的逻辑(视图事件=按钮单击、文本输入,等等)。
- 在逻辑调用模型时更改模型(更改=已发布的事件)。
它一遍又一遍,点击一个按钮导致模型更改,然后触发一个事件,由模板侦听。 这将基于模型更改更新视图。 泡沫,冲洗,重复。
配置发布者
到目前为止,我们一直在使用autopublish
。 这意味着,我们不必为任何事件或集合编写特定的发布事件。 这对于测试来说很好,但是我们想对发布的事件和文档有更多的控制,这样我们就可以提高性能和安全性。
如果我们有一个大的数据集,我们可能不希望每次都返回整个集合。 如果使用了autopublish
,则整个集合将返回,这可能会减慢速度,或者会暴露我们不想暴露的数据。
关闭自动发布
是时候关掉autopublish
了。 通过打开运行meteor
命令的终端窗口,暂时停止 Meteor 应用(如果它仍在运行)。 按Ctrl+C即可停止。 一旦停止,输入以下命令:
> meteor remove autopublish
这删除了autopublish
库,该库负责自动发布 Meteor 内部的所有事件。
提示
从项目中删除autopublish
被认为是最佳实践。 autopublish
用于开发和调试,当你准备开始认真使用你的应用时,应该关闭它。
通过关闭此选项,您实际上使应用什么也不做! 恭喜你! 再次启动 Meteor 服务(输入meteor
命令,按enter),打开/导航至http://localhost:3000
,即可看到您的惊人进展。 你会看到下面的截图:
分类/列表消失了! 如果你愿意,你甚至可以在控制台进行检查。 输入以下命令:
> lists.find().count()
您应该看到一个6
的计数,但您将看到一个0
的计数:
到底发生了什么事? 其实很简单。 因为我们删除了autopublish
库,服务器不再向我们的模型发布任何更改。
我们为什么要这么做? 破坏应用的目的是什么? 啊! 因为我们想让应用更高效。 不是自动获取每条记录,而是只获取我们需要的记录,以及这些记录的最小数据字段集。
上市类别
在LendLib.js
中,if(Meteor.isServer)
块内部,创建以下Meteor.publish
函数:
Meteor.publish("Categories", function() {
return lists.find({},{fields:{Category:1}});
});
这告诉服务器发布一个"Categories"
事件。 只要对函数内部的变量做了更改,它就会发布这个。 在本例中,它是lists.find()
。 当一个改变会影响lists.find()
的结果时,Meteor 将触发/发布一个事件。
如果您注意到,lists.find()
调用不是空的。 有一个选择器:{fields:{Category:1}}
。 这个选择器告诉lists.find()
调用只返回指定的fields:
。 并且只指定了一个字段——{Category:1}
。
这个 JSON 片段告诉选择器我们想要获得Category
字段(1
= true,0
= false)。 因为这是唯一提到的字段,它被设置为1
(true),Meteor 假设你想要排除所有其他属性。 如果你有任何字段设置为0
(false),Meteor 会假设你想包括所有其他字段,你没有提到。
提示
更多关于find()
功能的信息,请参考 MongoDB 文档http://www.mongodb.org/display/DOCS/Advanced+Queries。
所以,如果你保存这个更改,你的浏览器会刷新… 显示器没有任何变化!
为什么? 正如您可能已经猜到的,删除autopublish
库所做的不仅仅是消除publish
事件。 它也摆脱了听众/订阅者。 我们没有设置任何用户收听Categories
事件频道。 因此,我们需要添加一个用户事件,将其调到Categories
频道。
在if (Meteor.isClient)
函数内部,在最上面,就在左括号内,输入以下代码行:
Meteor.subscribe("Categories");
保存此更改,现在您将看到您的Categories
回到它们所属的位置。
在我们庆祝之前,点击服装类别。
我们最喜欢的衬衫不见了! 您现在可能已经知道,这是因为我们设置的发布事件非常具体。 Categories
频道中唯一发布的字段是Category
字段。 所有其他领域,包括我们的items
(以及我们最喜欢的球衣)都没有被播出。
我们仔细检查这个。 点击浏览器中服装类别内的+按钮,输入Red Hooded Sweatshirt
,按输入。 新条目将出现一秒钟,然后它将消失。 这是因为本地缓存和服务器同步。
当您输入新的item
时,本地缓存包含一个副本。 这是你的客户暂时可见的。 但是,当与服务器进行同步时,服务器更新只发布Category
字段,因此当服务器模型更新本地模型时,item
不再包含在内。
还有一个测试,只是为了好玩。 在终端窗口中,停止 Meteor 服务(Ctrl+C)。 现在,在浏览器中,在Clothes类别中输入另一个item
(我们将使用Pumped Up Kicks
)。 因为服务停止了,没有与服务器的同步,所以你使用的是本地缓存,而这是你的item
。
现在启动服务器备份。 您的客户端将与服务器同步,然后噗的一声! 你的 T0 又不见了。
列出项目
这是不好的,因为我们想看到我们的items
。 因此,让我们将添加回items
,并在Category
被选中时抓取适当的items
列表。 在LenLib.cs
中,就在if(Meteor.isServer)
块内的第一个Meteor.publish()
函数下面,添加以下函数:
Meteor.publish("listdetails", function(category_id){
return lists.find({_id:category_id});
});
此publish
功能将在"listdetails"
频道上发布。 任何侦听器/订阅器都将提供变量category_id
,因此find()
函数将返回一个精简的记录集。
注意,我们的客户端还没有任何改变(您的items
仍然不可见)。 这是因为我们需要创建subscribe
函数。
就在我们的第一个Meteor.subscribe()
函数下面,添加以下函数:
Meteor.subscribe("Categories");
Meteor.autosubscribe(function() {
Meteor.subscribe("listdetails", Session.get('current_list'));
});
保存您的更改,并检查您的 swag衣服集合!
让我们看一分钟,看看刚才发生了什么。 注意,订阅使用了Session.get('current_list')
。 这是传递给 publish 函数的变量。 换句话说,Session
变量current_list
中的值将被用作find()
函数的选择器中的category_id
。
如果你还记得第 4 章,模板,我们设置了一个点击事件处理程序来监听Category
的变化。 例如,当你点击Clothes时,会触发一个事件,LendLib.js
中的selectCategory()
函数会处理该事件,并更改Session
变量。
function selectCategory(e,t){
Session.set('current_list',this._id);
}
该Session.set()
触发发布事件。 我们将Meteor.subscribe()
函数包裹在"listdetails"
通道内的Meteor.autosubscribe()
函数中。 我们这样做是因为Session.set()
事件会触发Meteor.autosubscribe()
,而且我们有一个Meteor.subscribe()
功能,特别针对"listdetails"
通道。
换句话说:
Session.set()
触发事件。Meteor.subscribe()
监听该事件,因为它使用了Session
变量。- Meteor 在
"listdetails"
频道上重置订阅侦听器(因为它被包装在Meteor.autosubscribe()
内)。 - Meteor 看到新的订阅侦听器并触发一个初始事件。
Meteor.subscribe()
函数获取该事件,传入category_id
变量,UI 会因为模型更改而刷新。
检查您的流线数据
现在的显示与我们开始本章时没有什么不同。 但在显示屏之下,这款机型要瘦得多。 选择Clothes类别后,在浏览器控制台中执行如下命令:
> lists.findOne({Category:"DVDs"})
展开对象,您将看到没有列出任何项。
没有项的原因是我们的Session
变量current_list
被设置为Clothes
,而不是DVDs
。 find()
功能只获取current_list
的完整记录。
现在在浏览器控制台中输入以下命令,按,输入:
> lists.findOne({Category:"Clothes"})
展开该对象,您将看到数组中的三个项。
单击四周,向类别中添加项,添加新类别,并检查在客户机上可见的基础数据模型。 你会发现你的清单现在不那么显眼了,因此更加安全和谨慎。 对于您自己的个人借阅库应用来说,这可能不是问题,但随着我们在下一章展开这一内容,以便多人可以使用它,精简和谨慎的数据将真正提高性能。
小结
在本章中,您已经了解了 MongoDB 是什么,面向文档的数据库是如何工作的,并且您已经在命令行中执行了直接查询,熟悉了 Meteor 的默认数据存储库系统。 您还通过删除autopublish
简化了应用,并对 Meteor 内置的发布/订阅设计模式有了牢固的理解。
在下一章中,您将真正加强应用的安全性,允许多个用户跟踪和控制他们自己的项目列表,您将看到如何通过使用文件夹进一步简化您的客户端和服务器代码。
版权属于:月萌API www.moonapi.com,转载请注明出处