MEAN实践——LAMP的新时代替代方案(上)

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

本系列博客的两篇文章主要关注 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 文档完成:

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

如果你精通关系型数据库技术,那么你将了解数据库、表格、列和行。在 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 文档示例:

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

再次提醒,这里同样必须拥有一个 _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 :

{
     "_id" : ObjectId("54ad6c3ae764de42070b27b1"),
     "active" : true,
     "email" : "[email protected]",
     "firstName" : "Test",
     "lastName" : "User1",
     "sp_api_key_id" : "6YQB0A8VXM0X8RVDPPLRHBI7J",
     "sp_api_key_secret" : "veBw/YFx56Dl0bbiVEpvbjF”,
     "lastLogin" : ISODate("2015-01-07T17:26:18.996Z"),
     "created" : ISODate("2015-01-07T17:26:18.995Z"),
     "subs" : [ ObjectId("523b1153a2aa6a3233a913f8"),
                                ObjectId("54b563c3a50a190b50f4d63b") ],
}

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

User-Feed-Entry Mapping Collection

{
     "_id" : ObjectId("523b2fcc054b1b8c579bdb82"),
     "read" : true,
     "user_id" : ObjectId("54ad6c3ae764de42070b27b1"),
     "feed_entry_id" : ObjectId("523b1153a2aa6a3233a91412"),
     "feed_id" : ObjectId("523b1153a2aa6a3233a913f8")
}

最后一个 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:

{
    "name": "reader-api",
    "main": "server.js",
    "dependencies": {
    "express" : "~4.10.0",
    "stormpath" : "~0.7.5", "express-stormpath" : "~0.5.9",
    "mongodb" : "~1.4.26”, "mongoose" : "~3.8.0",
    "body-parser" : "~1.10.0”, "method-override" : "~2.3.0",
    "morgan" : "~1.5.0”, "winston" : "~0.8.3”, "express-winston" : "~0.2.9",
    "validator" : "~3.27.0",
    "path" : "~0.4.9",
    "errorhandler" : "~1.3.0",
    "frisby" : "~0.8.3",
    "jasmine-node" : "~1.14.5",
    "async" : "~0.9.0"
    }
}

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

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

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

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

one
two

但实际情况的输出是:

two
one

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

actionArray = [ function one(cb) { someAsyncFunction(params, function(err,
        results) { if (err) { cb(new Error(“There was an  error”)); } console.log(“one”);
        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」。对于建立一个更健壮的应用程序来说,前者显然更安全些。

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

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

本文系 OneAPM 工程师编译整理。OneAPM 是应用性能管理领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客

时间: 2024-08-27 23:54:01

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

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

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

用友财务云,引领智能财务新时代

在中国经济新常态."一带一路"全球化.产业结构升级以及科技日新月异的新时代背景下,每一个企业转型升级都迫在眉睫.企业的发展.扩张.商业模式调整.竞争力提升给财务和业务部门均带来了挑战,大企业财务转型升级势在必行.8月19日,由用友网络主办的"2017中国企业互联网大会"在乌镇召开.以"财务云,引领智能财务新时代"为主题的大企业财务管理转型高峰论坛上,来自中国铝业.北京国家会计学院.山东信发集团.新钢联冶金有限公司.鞍钢集团.天士力控股集团.延长石

写在新时代伊始

2019年的最后一天稀松平常,但年代之交这种人类自定义的巧合,也给许久没吐槽的我一些落笔的动力.我早就忘了十二岁的我曾对未来有何期待,但仍想给三十岁的自己一些回忆.又十个轮回后,明天或意外,还有那么大的分别吗? 大环境下的焦虑和愿景 在即将走出校园的时刻,我对社会大环境有了前所未有的焦虑.扩展了自己的认知边际后,感触越来越深刻的,无非是自己在社会中的渺小和无力.作为理科生,少有工科能直接改变周围环境的手段或途径,逐渐羡慕起投身科技行业内的变革者:同时对历史和政治的批判,加深了自身在制度和历史问题

视频是新时代的文本,微信也不得不认了!

视频是新时代的文本,终于等到微信的认可了.今天你肯定已经被朋友圈里的"大视频"刷屏了.微信在新版本中对视频功能做了较大迭代,将朋友圈小视频从 6 秒延长至 10 秒,同时在横屏之外也开始支持竖屏拍摄.特别重要的是,它允许用户从本地直接上传视频到朋友圈,升级了之前的"小视频"功能.(没错,看下图,你再也找不到小视频按钮了吼.) 总体看来,微信这次虽然对视频功能做了开放,但限制依旧存在,比如不支持即时转发到朋友圈.但相比之前的 H5,视频看起来更快捷,也容易引起大家对产

《劲道》课程笔记——新时代的领导新方法

Fierce Conversation 劲道 Fierce指的是我们卸下面具,投入对话,让对话变得真实. 劲道四大目标: 质问真相. 刺激学习(产生新观点). 对付挑战. 丰富关系 我们要相信自己的选择,我们要为自己选择相信的事物负责. ——Cardinal Newman 下面情况属于劲道: 说真心话, 谈话直指问题核心, 真诚提问与倾听, 散发热情, 增进人际关系, 对话结束后,你有转变. Everyone Communicats,Few Connect 每个人都在沟通,但很少产生连接 ——J

新时代 DevOps 需求下,我们该如何保障服务的安全?

[编者按]时下,传统安全策略显然已无法支撑 DevOps 环境的敏捷需求.那么,对于一个决策者来说,你又该如何实现 DevOps 速度与安全的兼得?本篇译自 Dzone 的一篇运维文章,题为「Security Breaks DevOps – Here's How to Fix It」,由 OneAPM 工程师编译整理. 现在,通信.协作.抽象.自动化.流程等理念已成为快速 DevOps 操作的基础.同时,在虚拟基础设施和 IaaS(基础设施即服务)的冲击下,手动的配置和管理已然不再可行--它需要

新时代的人工智能的发展?

上周六,在英国皇家学会举办的图灵测试中,一台由俄罗斯工程师设计的机器(更准确的说是软件)成功通过测试,成为有史以来第一台通过图灵测试的机器.很多科技媒体高呼人工智能即将进入新时代,显然,他们太乐观了. 什么是图灵测试 图灵测试一词来源于计算机科学和密码学的先驱——阿兰图灵写于1965年的一篇论文<计算机器与智能>( ComputingMachinery and Intelligence),图灵认为:机器能否思考的重要标准就是是否可以模仿一个真正的人.于是图灵设计了一套测试规则: “如果一个人A

刷机的新时代,浅谈,刷机必备神器!

论述:在刷机的新时代,Rom助手被刷机者们简称为"刷机神器",为何会被赋予这么神圣的名字? 刷机的狂潮总是掀起波澜,让我们不得不倾听身边刷机的佳话.数不胜数的刷机软件更让我们看的眼花缭乱,力求简单,快捷,全方位的刷机神器,也因此成为刷机爱好者们共同追求的新目标.在刷机者们看来,这就像是在追求身边简单的奢侈品,总会存在一个简单而不失格调的刷机软件.而在恰当的时间,准确的人群中, rom助手的横空出世,也就名副其实的成为刷机爱好者共同推崇的稀世珍宝. 成功总需付出努力,没有汗水的实践怎会被

即会通视频会议------开创企业的轻奢新时代

喜欢动,所以爱上的轻便的跑步鞋:喜欢文艺,爱上了下午茶的清香:喜欢慢生活的工作状态,你会爱上即会通视频会议. 10点钟的部门会议,不需要担心你9点50还在路上.普通的4g网络就可以满足你的开会需求.带上耳机坐在后座的位置,车堵到什么时候都不会影响你的会议讨论.你所要做的就是在手机上点开别人发送的视频邀请的链接. 对于商务人士而言,早上的时光是很急促,中午的时光很短暂,晚上的时光很疲惫.奔波于城市的任何一个角落.不断的会议,不断的出差,业务绩效的压力是让人在北京这样的城市里压得透不过气.企业何尝不