摘要:90 年代,LAMP 曾风靡一时,然而随着需求的变迁和数据流量的激增,LAMP 已不可避免的走下神坛。近日,在 MongoDB Blog 中,Dana Groce 介绍了一个基于新时代架构的实践 MEAN ,下面一起走进。

【编者按】在九十年代,Linux+Apache+Mysql+PHP 架构曾风靡一时,直到现在仍然是众多 Web 应用程序的基本架构。然而随着需求的变迁和数据流量的激增,LAMP 已不可避免的走下神坛。近日,在 MongoDB Blog 中,Dana Groce 介绍了一个基于新时代架构的实践 —— MEAN,MongoDB/Mongoose.js、Express.js、Angular.js 和 Node.js 。

以下为译文

本系列博客的两篇文章主要关注 MEAN 技术堆栈的使用 —— MongoDB/Mongoose.js 、Express.js、Angular.js 和 Node.js 。这些技术都使用了 JavaScript 以获取更高的软件性能和开发者生产效率。

第一篇博文主要描述应用程序的基本结构和进行数据建模过程,而第二篇则会创建测试来验证应用程序行为,然后介绍如何设置并运行应用程序。

本系列博文阅读并不需求拥有这些技术的实践经验,所有技能等级的开发人员都可以从中获益。如果在这之前你没有使用过 MongoDB、JavaScript 或建立一个 REST API 的经验,不用担心,这里将用足够的细节介绍这些主题,包括身份验证、在多文件中构建代码、编写测试用例等。首先,从 MEAN stack 的定义开始。

什么是 MEAN Stack

MEAN stack 可概括为:

  • M = MongoDB/Mongoose.js 。流行的数据库,对 node . js 来说是一个优雅的 ODM 。
  • E = Express.js :一个轻量级 Web 应用程序框架。
  • A = Angular.js :一个健壮的框架用于创建 HTML5 和 JavaScript-rich Web 应用程序。
  • N = Node.js 服务器端 JavaScript interpreter 。

MEAN stack 是 LAMP (Linux、Apache、MySQL,PHP / Python) stack 的一个现代替代者,在九十年代末,LAMP 曾是 Web 应用程序的主流构建方式。

在这个应用程序中并不会使用 Angular.js ,因为这里并不是要构建一个 HTML 用户界面。相反,这里创建的是一个没有用户界面的 REST API,但它却可以作为任何界面的基础,如一个网站、一个 Android 应用程序,或者一个 iOS 应用程序。也可以说我们正在 ME(a)N stack 上构建 REST API ,但这不是重点!

REST API 是什么?

REST 代表 Representational State Transfer,是 SOAP 和 WSDL XML-based API 协议的一个更轻量级替代方案。

REST 使用客户端-服务器模型,服务器是一个 HTTP 服务器,而客户端发送 HTTP 行为(GET、POST、PUT、DELETE),以及 URL 编码的变量参数和一个 URL 。URL 指定了对象的作用范围,而服务器则会通过结果代码和有效的 JavaScript Object Notation (JSON) 进行响应。

因为服务器用 JSON 回复,MongoDB 与 JSON 又可以很好地交互,同时所有组件都使用了 JavaScript,因此 MEAN stack 非常适合本用例中的应用程序。在进入开始定义数据模型后,你会看到一些 JSON 的例子。

CRUD 缩略词常被用来描述数据库操作。CRUD 代表创建、读取、更新和删除。这些数据库操作能很好地映射到 HTTP 动作:

  • POST:客户想要插入或创建一个对象。
  • GET:客户端想要读取一个对象。
  • PUT:客户想要更新一个对象。
  • DELETE:客户想要删除一个对象。

在定义 API 后,这些操作将变得更加直观。REST APIs 中通常会使用的一些常见 HTTP 结果代码如下:

  • 200 ——“OK”。
  • 201 ——“Created”(和POST一起使用)。
  • 400 ——“Bad Request”(可能丢失所需参数)。
  • 401 ——“Unauthorized”(身份验证参数丢失)。
  • 403 ——“Forbidden”(已验证,但是权限不够)。
  • 404 ——“Not Found”。

RFC 文档中可以找到一个完整的描述,这个在本博客末尾的参考资料中列出。上面这些结果代码都会在本应用程序中使用,随后就会展示一些例子。

为什么从 REST API 开始?

部署一个 REST API 可以为建立任何类型应用程序打下基础。如前文所述,这些应用程序可能会基于网络或者专门针对某些平台设计,比如 Android 或者 iOS 。

时下,已经有许多公司在建立应用程序时不再使用 HTTP 或者 Web 接口,比如 Uber、WhatsApp、Postmates 和 Wash.io 。从一个简单的应用程序发展成一个强大的平台,REST API 可以大幅度简化这个过程中其他接口和应用程序的实现。

建立 REST API

这里会建立一个 RSS Aggregator,类似Google Reader,应用程序主要会包含两个组件:

  1. REST API
  2. Feed Grabber(类似 Google Reader)

本系列博文都将聚焦这个 REST API 的打造,不会去关注 RSS feeds 的复杂性。现在,Feed Grabber 的代码已经可以在 github repository 中发现,详情可以见博文列出的资源。下面将介绍打造这个 API 所需的步骤。首先会根据具体需求来定义数据模型:

  • 在用户账户中储存用户信息
  • 跟踪需要被监视的RSS feeds
  • 将feed记录pull到数据库
  • 跟踪用户feed订阅
  • 跟踪用户会阅读哪个订阅的feed

用户则需要可以完成下列操作:

  • 建立一个账户
  • 到feed的订阅或者退订
  • 阅读feed记录
  • 标记feed/记录的阅读状态(已读/未读)

数据建模

这里不会深入讨论 MongoDB 中的数据建模,详细资料可以在博文后的列举的资料中发现。本用例需要 4 个 collections 来管理这个信息:

  • Feed collection
  • Feed entry collection
  • User collection
  • User-feed-entry mapping collection

Feed Collection

下面一起进入一段代码,Feed Collection 的建模可以通过下述 JSON 文档完成:

  1. {
  2. "_id": ObjectId("523b1153a2aa6a3233a913f8"),
  3. "requiresAuthentication": false,
  4. "modifiedDate": ISODate("2014-08-29T17:40:22Z"),
  5. "permanentlyRemoved": false,
  6. "feedURL": "http://feeds.feedburner.com/eater/nyc",
  7. "title": "Eater NY",
  8. "bozoBitSet": false,
  9. "enabled": true,
  10. "etag": "4bL78iLSZud2iXd/vd10mYC32BE",
  11. "link": "http://ny.eater.com/",
  12. "permanentRedirectURL": null,
  13. "description": "The New York City Restaurant, Bar, and Nightlife Blog”
  14. }

如果你精通关系型数据库技术,那么你将了解数据库、表格、列和行。在 MongoDB 中,大部分的关系型概念都可以映射。从高等级看,MongoDB 部署支持 1 个或者多个数据库。1 个数据库可能包含多个 collection,这个类似于传统关系型数据库中的表格。Collection 中会有多个 document,从高等级看,document 相当于关系型数据库中的行。这里需要注意的是,MongoDB 中的 document 并没有预设的格式,取而代之,每个 document 中都可以有 1 个或者多个的键值对,这里的值可能是简单的,比如日期,也可以是复杂的,比如 1 个地址对象数组。

上文的 JSON 文档是一个 Eater Blog 的 RSS feed 示例,它会跟踪纽约所有餐馆信息。因此,这里可能存在许多字段,而用例中主要关注的则是 feed 中的 URL 以及 description 。描述是非常重要的,因此在建立一个移动应用程序时,它会是 feed 一个很好的摘要。

JSON 中的其他字段用于内部使用,其中非常重要的字段是 _id 。在 MongoDB 中,每个 document 都需要拥有一个 _id 字段。如果你建立一个没有 —— id 的 document,MongoDB 将为你自动添加。在 MongoDB 中,这个字段就是主键的存在,因此 MongoDB 会保证这个字段值在 collection 范围唯一。

Feed Entry Collection

在 feed 之后,用例中还期望追踪 feed 记录。下面是一个 Feed Entry Collection 文档示例:

  1. {
  2. "_id": ObjectId("523b1153a2aa6a3233a91412"),
  3. "description": "Buzzfeed asked a bunch of people...”,
  4. "title": "Cronut Mania: Buzzfeed asked a bunch of people...",
  5. "summary": "Buzzfeed asked a bunch of people that were...”,
  6. "content": [{
  7. "base": "http://ny.eater.com/",
  8. "type": "text/html",
  9. "value": LOTS OF HTML HERE ",
  10. "language": "en"
  11. }],
  12. "entryID": "tag:ny.eater.com,2013://4.560508",
  13. "publishedDate": ISODate("2013-09-17T20:45:20Z"),
  14. "link": "http://ny.eater.com/archives/2013/09/cronut_mania_41 .php",
  15. "feedID": ObjectId("523b1153a2aa6a3233a913f8")
  16. }

再次提醒,这里同样必须拥有一个 _id 字段,同时也可以看到 description、title 和 summary 字段。对于 content 字段,这里使用的是数组,数据中同样储存了一个 document。MongoDB 允许通过这种方式嵌套使用 document,同时这个用法在许多场景中也是非常必要的,因为用例往往需求将信息集中存储。

entryID 字段使用了 tag 格式来避免复制 feed 记录。这里需要注意的是 feedID 和 ObjectId 的用法——值则是 Eater Blog document 的 _id 。这提供了一个参考模型,类似关系型数据库中的外键。因此,如果期望查看这个 ObjectId 关联的 feed document,可以取值 523b1153a2aa6a3233a913f8,并在 _id 上查询 feed collection,从而就会返回 Eater Blog document。

User Collection

这里有一个用户需要使用的 document :

  1. {
  2. "_id" : ObjectId("54ad6c3ae764de42070b27b1"),
  3. "active" : true,
  4. "email" : "testuser1@example.com",
  5. "firstName" : "Test",
  6. "lastName" : "User1",
  7. "sp_api_key_id" : "6YQB0A8VXM0X8RVDPPLRHBI7J",
  8. "sp_api_key_secret" : "veBw/YFx56Dl0bbiVEpvbjF”,
  9. "lastLogin" : ISODate("2015-01-07T17:26:18.996Z"),
  10. "created" : ISODate("2015-01-07T17:26:18.995Z"),
  11. "subs" : [ ObjectId("523b1153a2aa6a3233a913f8"),
  12. ObjectId("54b563c3a50a190b50f4d63b") ],
  13. }

用户应该有 email 地址、first name 和 last name。同样,这里还存在 sp_api_key_id 和 sp_api_key_secret —— 在后续部分会结合 Stormpath(一个用户管理 API )使用这两个字段。最后一个字段 subs,是 1 个订阅数组。subs 字段会标明这个用户订阅了哪些 feeds。

User-Feed-Entry Mapping Collection

  1. {
  2. "_id" : ObjectId("523b2fcc054b1b8c579bdb82"),
  3. "read" : true,
  4. "user_id" : ObjectId("54ad6c3ae764de42070b27b1"),
  5. "feed_entry_id" : ObjectId("523b1153a2aa6a3233a91412"),
  6. "feed_id" : ObjectId("523b1153a2aa6a3233a913f8")
  7. }

最后一个 collection 允许映射用户到 feeds,并跟踪哪些 feeds 已经读取。在这里,使用一个布尔类型(true/false)来标记已读和未读。

REST API 的一些功能需求

如上文所述,用户需要可以完成以下操作:

  • 建立一个账户
  • 到 feed 的订阅或者退订
  • 阅读 feed 记录
  • 标记 feed / 记录的阅读状态(已读 / 未读)

此外,用户还需求可以重置密码。下表表示了这些操作是如何映射到 HTTP 路由和动作。



在生产环境中,HTTP(HTTPS)安全需求使用一个标准的途径来发送敏感信息,比如密码。

通过 Stormpath 实现现实世界中的身份验证

在一个鲁棒的现实世界应用程序中,提供用户身份验证不可避免。因此,这里需要一个安全的途径来管理用户、密码和密码重置。

在本用例中,可以使用多种方式进行身份验证。其中一个就是使用 Node.js 搭配 Passport Plugin ,这个方式通常被用于社交媒体账户验证中,比如 Facebook 或者 Twitter 。然而,Stormpath 同样是一个非常不错的途径。Stormpath 是一个用户管理即服务,支持身份验证和通过 API keys 授权。根本上,Stormpath 维护了一个用户详情和密码数据库,从而客户端应用程序 API 可以调用 Stormpath REST API 来进行用户身份验证。

下图显示了使用 Stormpath 后的请求和响应流。



详细来说,Stormpath 会为每个应用程序提供一个安全秘钥,通过它们的服务来定义。举个例子,这里可以定义一个应用程序作为「Reader Production」或者「Reader Test」。如果一直对应用程序进行开发和测试,定义这两个应用程序非常实用,因为增加和删除测试用户会非常频繁。在这里,Stormpath 同样会提供一个 API Key Properties 文件。Stormpath 同样允许基于应用程序的需求来定义密码属性,比如:

  • 不低于 8 个字符
  • 必须包含大小写
  • 必须包含数字
  • 必须包含 1 个非字母字符

Stormpath 会跟踪所有用户,并分配他们的 API keys(用于 REST API 身份验证),这将大幅度简化应用程序建立过程,因为这里不再需要为验证用户编写代码。

Node.js

Node.js 是服务器端和网络应用程序的运行时环境。Node.js 使用 JavaScript 并适合多种不同的平台,比如 Linux、Microsoft Windows 和 Apple OS X。

Node.js 应用程序需要通过多个库模块建立,当下社区中已经有了非常多的资源,后续应用程序建立中也会使用到。

为了使用 Node.js,开发者需要定义 package.json 文件来描述应用程序以及所有库的依赖性。

Node.js Package Manager 会安装所有库的副本到应用程序目录的一个子目录,也就是 node_modules/ 。这么做有一定的好处,因为这样做可以隔离不同应用程序的库版本,同时也避免了所有库都被统一安装到标准目录下造成的代码复杂性,比如 /usr/lib。

命令 npm 会建立 node_modules/ 目录,以及所有需要的库。

下面是 package.json 文件下的 JavaScript:

  1. {
  2. "name": "reader-api",
  3. "main": "server.js",
  4. "dependencies": {
  5. "express" : "~4.10.0",
  6. "stormpath" : "~0.7.5", "express-stormpath" : "~0.5.9",
  7. "mongodb" : "~1.4.26”, "mongoose" : "~3.8.0",
  8. "body-parser" : "~1.10.0”, "method-override" : "~2.3.0",
  9. "morgan" : "~1.5.0”, "winston" : "~0.8.3”, "express-winston" : "~0.2.9",
  10. "validator" : "~3.27.0",
  11. "path" : "~0.4.9",
  12. "errorhandler" : "~1.3.0",
  13. "frisby" : "~0.8.3",
  14. "jasmine-node" : "~1.14.5",
  15. "async" : "~0.9.0"
  16. }
  17. }

应用程序被命名为 reader-api,主文件被命名为 server.js,随后会是一系列的依赖库和它们的版本。这些库其中的一些被设计用来解析 HTTP 查询。在这里,我们会使用 frisby 作为测试工具,而 jasmine-node 则被用来运行 frisby 脚本。

在这些库中,async 尤为重要。如果你从未使用过 node.js,那么请注意 node.js 使用的是异步机制。因此,任何阻塞 input/output (I/O) 的操作(比如从 socket 中读取或者 1 个数据库查询)都会采用一个回调函数作为最后的参数,然后继续控制流,只有在阻塞操作结束后才会继续这个回调函数。下面看一个简单的例子来理解这一点。

  1. function foo() { someAsyncFunction(params, function(err, results) { console.log(“one”);
  2. }); console.log(“two”); }

在上面这个例子中,你想象中的输出可能是:

  1. one
  2. two

但实际情况的输出是:

  1. two
  2. one

造成这个结果的原因就是 Node.js 使用的异步机制,打印 「one」 的代码可能会在后续的回调函数中执行。之所以说可能,是因为这只在一定的情景下发生。这种异步编程带来的不确定性被称之为 non-deterministic execution 。对于许多编程任务来说,这么做可以获得很高的性能,但是在顺序性要求的场景则非常麻烦。而通过下面的用法则可以获得一个理想中的顺序:

  1. actionArray = [ function one(cb) { someAsyncFunction(params, function(err,
  2. results) { if (err) { cb(new Error(“There was an error”)); } console.log(“one”);
  3. cb(null); }); }, function two(cb) { console.log(“two”); cb(null); } ] async.series(actionArray);

总结

通过本篇文章,相信大家对 Node.js 和异步函数设置都有了一定的理解,因此下篇博文将会描述更深入层次的一些知识。取代开始建立应用程序,这里会进入建立测试以及验证应用程序的行为。这种方式则被称为 test-driven 开发,它会带来两大好处:

首先,它会帮助开发者弄清数据和函数的消费方式,同时也可以帮助弄清一些奇怪的需求,比如数组中会储存多个对象。

通过在建立应用程序之前编写测试,模型会从「assumed to be working until a test fails」转换成「broken / unimplemented until proven tested OK」。对于建立一个更健壮的应用程序来说,前者显然更安全些。

未完待续。

原文链接Building your first application with MongoDB: Creating a REST API using the MEAN Stack - Part 1

本文系 OneAPM 工程师编译整理。想阅读更多技术文章,请访问 OneAPM 官方博客

MEAN实践——LAMP的新时代替代方案(上)的更多相关文章

  1. MEAN实践——LAMP的新时代替代方案(下)

    在本系列文章的第一部分旨在介绍一些应用程序的基础技术细节和如何进行数据建模,而这个部分文章将着手建立验证应用程序行为的测试,并会指出如何启动和运行应用程序. 首先,编写测试 首先定义一些小型配置库.文 ...

  2. C#情怀与未来,怨天尤人还是抓住机会,能否跟上dnc新时代浪潮?

    C#情怀与未来,怨天尤人还是抓住机会,能否跟上dnc新时代浪潮?   经常看到有.NET圈子在讨论是否应该转其它语言   C#情怀是一方面,如果觉得C#未来没前途,光靠情怀是撑不住的, 建议对C#未来 ...

  3. 新时代运维重器 Tencent Hub 最佳实践——云+未来峰会开发者专场回顾

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 演讲者:邹辉 腾讯云 PaaS 产品总监 背景:5月23-24日,以"焕启"为主题的腾讯"云+未来" ...

  4. WebAssembly,Web的新时代

    在浏览器之争中,Chrome凭借JavaScript的卓越性能取得了市场主导地位,然而由于javascript的无类型特性,导致其运行时消耗大量的性能做为代价,这也是JavaScript的瓶颈之一.W ...

  5. Vimer的福音 新时代的Vim C++自动补全插件 clang_complete

    使用vim的各位肯定尝试过各种各样的自动补全插件,比如说大名鼎鼎的 OmniCppComplete .这一类的插件都是对 Ctags 生成的符号表进行字符串匹配来获得可能的补全项.他们在编写 C 代码 ...

  6. 新时代的Vim C++自动补全插件 clang_complete

    Vimer的福音 新时代的Vim C++自动补全插件 clang_complete   使用vim的各位肯定尝试过各种各样的自动补全插件,比如说大名鼎鼎的 OmniCppComplete .这一类的插 ...

  7. 全景智慧城市——VR全景,开启VR营销新时代

    全景是一种新兴的富媒体技术. 与视频.声音.图片等传统主流媒体最大的区别是"可操作,可交互". 全景给人以三维立体感觉的实景360°全方位图像,此图像最大的三个特点: 全方位:展示 ...

  8. 论 大并发 下的 乐观锁定 Redis锁定 和 新时代事务

    在 <企业应用架构模式> 中 提到了 乐观锁定, 用 时间戳 来 判定 交易 是否有效, 避免 传统事务 的 表锁定 造成 的 瓶颈 . 在 现在的 大并发 的 大环境下, 传统事务 及其 ...

  9. Redis进阶实践之三如何在Windows系统上安装安装Redis(转载)

    Redis进阶实践之三如何在Windows系统上安装安装Redis 一.Redis的简介 Redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括 ...

随机推荐

  1. [div+css布局]命名规则

    //首页可能碰到的 页头:header登录条:loginBar标志:logo侧栏:sideBar广告:banner导航:nav子导航:subNav菜单:menu子菜单:subMenu搜索:search ...

  2. netfilter

    http://jingyan.baidu.com/article/642c9d3415d2c9644a46f7c6.html http://blog.csdn.net/zhangskd/article ...

  3. Objective - C中属性和点语法的使用

    一.属性        属性是Objective—C 2.0定义的语法,为实例变量提供了setter.getter方法的默认实现能在一定程度上简化程序代码,并且增强实例变量的访问安全性         ...

  4. SCSF智能客户端学习笔记(一)

    什么是智能客户端 要了解智能客户端,首先要认识瘦客户端技术和胖客户端技术各自的优缺点. 对于前者,典型的应用就是使用浏览器,通过输入URL远程访问服务端,并向服务端发送命令,获取服务端的资源,然后在客 ...

  5. 55.ERROR:Place:1136 - This design contains a global buffer instance…… non-clock load pins off chip

    ISE在布局布线时,出现下图所示错误. 对于"clock_dedicated_route”错误原因有两种情况: 1.  就是有一个时钟你没有放到全局时钟或者局部时钟的引脚,布局的时候不能把它 ...

  6. 套接字I/O模型之WSAEventSelect

    今天我又学习了一种新的套接字I/O模型------WSAEventSelect,他与WSAAsyncSelect一样也是一种异步事件通知模型,不同的是WSAAsyncSelect是与窗口句柄关联在一起 ...

  7. 基于.net mvc的校友录(源程序)

    废话不多说,上程序再说: http://pan.baidu.com/s/11MnLo 我.net mvc4的正式学习时长,其实也就一个多月,期间除去玩游戏.听歌.谈恋爱,也就半个月,大神请轻喷~~ 转 ...

  8. mysql字符集基础知识梳理

    接着上一篇继续来一篇关于mysql字符设置等问题学习笔记,这篇就不说什么废话了,直接进入正题,不过还是感谢十八哥的无私分享! 我们首先看看mysql整个数据存储和读取一个流程: 连接器(connect ...

  9. android 开发怎么让程序生成的图片文件不会被系统扫描到

    我们在写应用的时候,可能会保存很多图片,大的小的,仅仅是我们的应用中会用到,处于种种原因不希望用户看到,我是觉着如果被用户看到了,就失去了我的应用的那一层神秘的面纱,用户是米有闲情逸致去打开你一层层的 ...

  10. Memcached使用

    一.Memcached简介 Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网 ...