在这篇文章中,我们将比较Express.js--用Node.js构建Web应用最常用的服务器端框架之一--和Marble.js,另一个较新的反应式服务器端Node.js框架。
前提条件
如果你不熟悉函数式反应式编程(FRP),你可能想在阅读之前暂停一下。我推荐Conal Elliott的作品,它包含了很多更精确的信息。
FRP的一般要点是,它是一个描述动态(又称时间变化)信息和顺序执行的连续副作用的一般范式。Conal Elliott用这个声明抓住了FRP的含义。
FRP是并发的,而没有触犯理论上和实践上的老鼠窝,这些老鼠窝困扰着命令式并发。
Andre Staltz,Cycle.js框架的作者和RxJS的核心贡献者,也给出了这个精彩的解释。
反应式编程提高了代码的抽象程度,因此人们可以专注于定义业务逻辑的事件的相互依赖性,而不是不断地摆弄大量的执行细节。
这使得我们的程序可以明确地执行。一旦在一个新的线程上的进程开始执行,它应该能够被控制。
现在,让我们继续进行比较。
Marble.js vs. Express.js:一个基本的比较
什么是Marble.js?
Marble.js是一个用于构建服务器端应用程序的功能性反应式Node.js框架。它基于TypeScript和RxJS,并依赖于反应式范式,在Node平台之上有功能糖。
Marble.js的主要构件是一个Effect,或一个返回事件流的函数。
由于Marble.js的基于事件的性质,当一个服务执行一个其他服务可能感兴趣的动作时,该服务会产生一个事件,也就是执行的动作的记录。其他服务消费这些事件,以执行作为事件结果所需的行动。
一个Effect适合于实现分布式事件处理系统,因此,Marble.js除了通过基本的HTTP协议操作外,还可以用于一般的消息驱动架构(MDA)解决方案,如WebSockets或微服务。
什么是Express.js?
Express.js是一个快速、灵活、简约的Node.js网络开发框架,可以设计具有一系列强大功能的单页、多页和混合应用程序。
Express提供了大量的中间件和其他内置支持,使API的创建变得容易,同时也支持第三方模块与数据库的对接。这些优势使你可以用Express使用几乎任何形式的数据库。
对于Marble v3和Express v4的高层比较,让我们看看下面的表格。
| 特点和标准 | Marble.js v3 | Express v4 |
| 最低支持的Node.js版本 | ≥v8.0 | ≥v0.10.0 |
| 支持TypeScript | ![]() | ![]() |
| 内建的中间件 | ![]() | ![]() |
| 核心包大小 | 102 kB | 208 kB |
| HTTP辅助工具 | ![]() | ![]() |
| 模块化设计 | ![]() | ![]() |
| I/O处理 | ![]() | ![]() |
| 支持fp-ts@2.0 | ![]() | ![]() |
| 异步读取器 | ![]() | ![]() |
| 请求主体解析 | ![]() | ![]() |
| 日志 - 可插拔的、开箱即用的服务器/服务日志解决方案。 | ![]() | ![]() |
| 支持RxJS | ![]() | ![]() |
创建我们的Marble.js和Express.js目录
我们将使用Marble.js和Express.js创建一个 "Hello, World!"程序,以概述Marble.js和Express.js是如何处理路由的,并在创建的Web应用程序上运行一个基准测试工具,看看这两个框架中哪个更快。
正如我们上面指出的,Express.js需要Node≥v0.10.0,而Marble.js需要Node≥v8.0。我将使用Yarn作为我的包管理器和Windows操作系统。
在你所期望的项目目录中,创建将容纳你的Marble应用程序的文件夹。
$ mkdir MarbleJsApp
在你喜欢的代码编辑器中打开你的MarbleJsApp 文件夹;我将使用VS Code。
$ code
一旦它被打开,打开终端窗口并运行命令。
$ yarn init -y
安装@marblejs/http 模块。
$ yarn add @marblejs/core @marblejs/http fp-ts rxjs
Every @marblejs/* package requires @marblejs/core + fp-ts + rxjs to be installed first.
在另一个项目目录中,创建将容纳你的Express应用程序的文件夹。
$ mkdir ExpressJsApp
打开ExpressJsApp 文件夹。
$ code
在终端窗口中,运行命令,创建package.json 文件。
$ yarn init -y
现在,打开你的ExpressJsApp 文件夹和终端窗口,安装Express。
$ yarn add express
创建一个Marble.js服务器
从4.0版本开始,@marblejs/http 包包含了所有与HTTP相关的API,我们需要安装这个包,以便让Marble进行我们创建服务器端应用程序所需的两个基本配置。这两个基本配置是HTTP听众定义和HTTP服务器配置。
我们将在我们项目的根目录下创建三个基础文件。
index.tshttp.listener.tsapi.effects.ts
打开终端窗口,输入这些命令来创建这些文件。
$ touch index.ts http.listener.ts api.effects.ts
在我们的index.ts 文件中,我们首先用createServer 方法创建一个Marble应用实例,从 [@marblejs/http module](https://docs.marblejs.com/other/api-reference/marblejs-http);createServer 方法是对Node.js服务器创建者的一个封装。
import { createServer } from '@marblejs/http';
import { IO } from 'fp-ts/lib/IO';
import { listener } from './http.listener';
const server = createServer({
port: 1337,
hostname: '127.0.0.1',
listener,
});
const main: IO<void> = async () =>
await (await server)();
main();
为了测试运行新创建的服务器,我们安装TypeScript编译器和ts-node 。
$ yarn add typescript ts-node
在我们的package.json 文件中添加以下脚本。
"scripts": {
"start": "ts-node index.ts"
},
启动服务器。
$ yarn start
该服务器应该正在运行。你可以通过访问你的浏览器来检查 [http://localhost:1337](http://localhost:1337).
创建一个Express.js服务器
我们在我们的ExpressJSApp 文件夹中创建一个index.js 文件,并创建一个服务器,在port 1338 上监听连接。
const express = require("express");
const app = express();
const port = 1338;
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
用以下命令运行Express应用程序。
$ node app.js
我们只用了几行代码就创建了我们的服务器--这是对Express的强大和简单的一个偷窥。
我们可以打开我们的浏览器标签并粘贴URL [http://localhost:1338](http://localhost:1338).应用程序应该以 "你好,世界!"作为回应。
Marble.js与Express.js。路由
让我们来看看如何在Express.js和Marble.js中处理路由问题。
Express.js在他们的req 和res 对象中包含了很多辅助功能,这些功能带有诸如res.send,res.download,res.redirect 等方法。
const express = require("express");
const app = express();
const port = 1338;
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
相比之下,在Marble中定义路由是通过定义一个API效果来完成的。作为提醒,一个效果只是一个响应传入请求的事件流。在我们的例子中,它将返回我们的 "你好,世界!"信息。
为了在Marble中建立我们的路线,我们打开http.listener.ts 文件来定义我们的 [httpListener](https://docs.marblejs.com/other/api-reference/marblejs-http/core-httplistener) 程序。这是每一个Marble应用程序的基本起点,因为它包括所有全局中间件和API效果的定义。
然后,我们将在我们的应用程序中安装两个更重要的模块:一个用于记录,另一个用于解析。
用下面的命令安装中间件包。
$ yarn add @marblejs/middleware-logger @marblejs/middleware-body
并像这样定义httpListener 。
import { httpListener } from '@marblejs/http';
import { logger$ } from '@marblejs/middleware-logger';
import { bodyParser$ } from '@marblejs/middleware-body';
import { api$ } from './api.effects';
const middlewares = [
logger$(),
bodyParser$(),
];
const effects = [
api$,
];
export const listener = httpListener({
middlewares,
effects,
});
使用效果接口,我们可以定义API端点或事件处理程序。我们将创建一个API效果,向用户返回 "Hello, world!"。
打开api.effects.ts 文件。在这里,我们将创建我们的第一个端点。为了路由我们的第一个httpEffect ,我们必须定义路径和方法,以匹配传入的请求。
在这里,我们将matchPath 属性指定为 ("/").我们带有matchType 属性的方法是一个GET 方法。
定义我们的第一个端点是在api.effects.ts
import { r } from '@marblejs/http';
import { mapTo } from 'rxjs/operators';
export const api$ = r.pipe(
r.matchPath('/'),
r.matchType('GET'),
r.useEffect(req$ => req$.pipe(
mapTo({ body: 'Hello, world!' }),
)));
如果我们返回到 [http://localhost:1337](http://localhost:1337),我们应该看到我们的 "Hello, world!"消息被返回。
比较性能基准
硬件。
- 笔记本电脑。联想Thinkpad W540
- CPU。Intel ® Core(
) i7-4800MQ CPU @ 2.70GHz
条件。
- 125个连接
- 5000000个请求
对Marble.js的HTTP性能进行基准测试
为了测试,我将使用Go Bombardier包,用以下命令运行5000000个请求,125个并发连接。
运行以下命令,指定Marble.js项目的URL。
$ bombardier -c 125 -n 5000000 http://127.0.0.1:1337/
下面是我们在Marble.js应用程序上运行测试的结果。
Bombarding http://127.0.0.1:1337/ with 5000000 request(s) using 125 connection(s)
5000000 / 5000000 [=====================================================================================] 100.00% 2355/s 35m22s
Done!
Statistics Avg Stdev Max
Reqs/sec 2368.27 790.92 6658.29
Latency 53.07ms 12.42ms 601.59ms
HTTP codes:
1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0
others - 0
Throughput: 604.92KB/s
对Express.js的HTTP性能进行基准测试
运行下面的命令,指定Express.js项目的URL。
$ bombardier -c 125 -n 5000000 http://127.0.0.1:1338/
下面是我们在Express.js应用程序上运行测试的结果。
Bombarding http://127.0.0.1:1338/ with 5000000 request(s) using 125 connection(s)
5000000 / 5000000 [================] 100.00% 5738/s 14m31s
Done!
Statistics Avg Stdev Max
Reqs/sec 5745.13 1164.50 34982.51
Latency 21.78ms 4.02ms 411.92ms
HTTP codes:
1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0
others - 0
Throughput: 1.65MB/s
基准测试的目的都是关于性能和比较两个框架的性能。考虑到这一点,最重要的指标是每秒钟的请求数。
在考虑这个衡量标准时,我们希望得到尽可能高的数值,因为应用程序每秒能处理的请求越多,应用程序就越快。
下面是我们基准测试的结果。
| 框架 | Marble.js | Express.js |
| 每秒请求数 | 2368.27 | 5745.13 |
| 延迟(毫秒) | 53.07 | 21.78 |
| 处理5000000个请求的总时间 | 35分钟 | 14分钟 |
按照这个指标,Express.js的延迟更高。看看我们的基准测试结果,Express.js应用程序的表现比Marble.js应用程序好得多。
总结
Express.js非常适合创建简单的应用程序,正如我们从创建 "Hello, world!"程序、建立简单的路由和易学性等方面所看到的。
在我们的基准测试中,事实证明Express比Marble.js更有能力处理更多的请求。
Marble.js提供了对TypeScript的支持,对RxJS提供的更高级别的函数有很好的支持,加上其专用的@marblejs/messaging 模块,Marble.js为构建基于事件的微服务提供了一个强大而统一的架构。
Express.js在构建RESTful API方面非常出色,但在构建具有事件驱动架构的应用程序方面,Marble.js表现出色。
Express.js仍然很受欢迎,并将继续在主要项目和行业中使用,但Marble.js在功能性反应式编程的支持者中相当受欢迎。希望我们能继续看到Marble在那些希望将其异步性纳入依赖服务器处理繁重工作和复杂计算而不冻结主线程的应用程序的开发者中的使用增加。
The postMarble.js vs. Express.js:比较Node.js网络框架首次出现在LogRocket博客上。

