«
Egg.js

时间:2022-4   


【egg已经是过街老鼠了吗?】首选ts版本midway。我能说的它们是经过阿里业务严重过,双十一,还有蚂蚁的ssr,量大到全网最大node集群,这个说法不过分。

稳,可扩展,生态好,出了问题自己努努力还是能搞定的。

如果非要ts,可以考虑midway或daruk,一个是egg生态加强版,一个基于koa小到刚好的。都不错。

nest或next或nuxt这种,sloc试试就知道了。太重,无大佬慎用。文档也比egg差一大截。

其实fastify,blitz也是可以考虑的。


star数不能说明质量一定好,它只是同等情况下,star数高更好。比如express在2010年就有了,曾经的王者,一直有很高的用户量,star数高很正常。

nest是很像spring,但实话讲,没学到精髓。我其实曾去研究过javaee规范和Tomcat原理,也用过ssh1和2,和spring boot。node其实简化这一套,无论容器,上下文,connector,都没了。那nest是啥?Tomcat+spring?


  1. 一个框架是否轻量级不是看他的代码库有多大,而是看他复用你原有的代码的能力强不强,是否需要写框架专用代码。很明显 egg 需要写一堆专用代码才能利用 koa 的中间件,所以 egg 不轻量级。

koa一共5/600行代码,最多为一个中间件内核。它不内置一个中间件,它肯定比egg轻量。

egg核心是面向架构设计,做了大量约定和抽象,使用koa中间件代码可以直接用,只需要按照egg写法就好了,有很复杂?好好看看文档

https://eggjs.org/zh-cn/intro/egg-and-koa.html​eggjs.org/zh-cn/intro/egg-and-koa.html

  1. 一个框架是否设计的漂亮,需要看他如何适配外部的代码,和如何安排内置的功能。很明显一个 httpclient 绑在 context 上的框架设计并不能说设计的漂亮。context 是啥?请求上下文啊,把 httpclient 绑在 context 上你想干嘛?

不理解啥时候bff吧,去看看

  1. ctx是个垃圾堆,啥都往里面堆,到后面你都不知道自己往 ctx 里面写了什么东西,此之谓全局命名空间污染。

这其实一个妥协,ctx和express req一样,用多了都恶心,中间件机制在这里,没有更好的方式,有更好解法吗?

  1. service 没有生命周期,这个东西导致了 ctx 和 app 变成了垃圾堆。

去看看Java里,dao层和service层的关系,理解service为业务逻辑,为啥需要生命周期呢?没看懂。

  1. 一个东西经受住双十一的考验不代表他是一个好东西,因为 egg 的设计真的垃圾。

从n个bu,n个框架抽象出来的,到底哪里垃圾,貌似你没有讲出来。

  1. 你可能认为你拿 egg 很快写上一个 web ,但实际 egg 已经带偏了你的认知,不如直接上 koa/express。

是一个级别吗?egg号称企业级框架,岂是koa和express可比。

  1. 约定优于配置,配置即代码。实际上 egg 是没地方给你配置,配置脱离代码。要看约定优于配置,配置即代码,可以看看 asp.net core ,为什么是 asp.net core 不是其他?因为我只学过 asp.net core 没学过 spring boot。

约定大于配置起源自ruby on rails,那.net理解,总觉得怪怪的,egg的config目录,没地方配?各种环境抽象,设计的很差?

先好好学学吧。

  1. 不是你没用依赖注入就是轻量级,实际上依赖注入天生为轻量级而生。依赖注入不仅轻量级,还能让你的代码依赖有迹可寻。不是你不理解的东西就是重量级。相反 ctx 到处绑,这是真的重耦合。

egg出现的时候,ts还没有今天这样成熟,更不要提ioc,本身就是node优势就是极其轻量,一个文件起一个server,纯js写法能实现ioc,但社区和业界并没有很好的实践,我理解这也是一种取舍。egg帮你做了一些约定,但没有完全走ioc,代码依赖有迹可循是有的,一旦习惯egg,效率非常高的。ctx是请求级别的,适当共享一些内容是ok的,看怎么样用。用不好,不代表锤子不好。


egg维护为啥不那么频繁?因为成熟了

ts版本egg有midway,复用生态,不内卷,完美。


Node的web框架也有很多可以选择的,前一阵梳理了一下7类

总结

如果不开心就自己写一个,参考 https://scalatra.org//guides/2.7/http/routes.html

具体写法如下。

class A {
    get(path = "/") {
        return '{"message":"hello world"}'
    }
}

这样写起来更简单。

第一版代码

const http = require('http')
const router = require('find-my-way')()

const FN_ARGS = /^(function)?\s*\*?\s*[^\(]*\(\s*([^\)]*)\)/m
const FN_ARG_SPLIT = /,/
const FN_ARG = /^\s*(_?)(\S+?)\1\s*$/
const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg

const cache = {}

function getParameters(fn) {
    const fnText = fn.toString();
    if (!cache[fnText]) {
        const inject = {};
        const argDecl = fnText.replace(STRIP_COMMENTS, '').match(FN_ARGS);
        // console.log(argDecl)

        argDecl[2].split(FN_ARG_SPLIT).forEach(function (arg) {
            // console.log(arg)
            if (arg.indexOf('=') != -1) {
                var i = arg.split("=")
                inject[i[0].trim()] = i[1].split('"')[1]
            }
            arg.replace(FN_ARG, function (all, underscore, name) {
                inject[name] = null
            });
        });

        cache[fnText] = inject
    }

    return cache[fnText]
}

class A {
    get(path = "/") {
        return '{"message":"hello world"}'
    }
}

var a = new A()

var propertyNames = Object.getOwnPropertyNames(Object.getPrototypeOf(a));
console.dir(propertyNames)

for (var i in propertyNames) {
    // console.dir(propertyNames[i])
    if ('constructor' !== propertyNames[i]) {
        // console.dir(propertyNames[i].toUpperCase())

        var parameters = getParameters(a[propertyNames[i]])
        var path = parameters['path']
        // console.dir(b)

        var _original = a[propertyNames[i]];
        var _newfn = function () {
            // console.log('in');
            // console.dir(arguments[0])
            var result = _original.apply(this, arguments);
            // console.log('out');
            return result;
        }

        router.on(propertyNames[i].toUpperCase(), path, (req, res, params) => {
            // res.end('{"message":"hello world"}')
            a.req = req
            a.res = res

            var html = _newfn.bind(a)(path, req, res, params);
            res.end(html)
        })
    }
}

const server = http.createServer((req, res) => {
    router.lookup(req, res)
})

server.listen(3000, err => {
    if (err) throw err
    console.log('Server listening on: http://localhost:3000')
})

简单的做了一下benchmark,和find-my-way基本一样,没有性能损耗。

scalatra

➜  ~ autocannon -c 100 -d 5 -p 10 localhost:3000
Running 5s test @ http://localhost:3000
100 connections with 10 pipelining factor

┌─────────┬──────┬───────┬───────┬───────┬──────────┬─────────┬───────┐
│ Stat    │ 2.5% │ 50%   │ 97.5% │ 99%   │ Avg      │ Stdev   │ Max   │
├─────────┼──────┼───────┼───────┼───────┼──────────┼─────────┼───────┤
│ Latency │ 8 ms │ 12 ms │ 23 ms │ 25 ms │ 12.47 ms │ 4.94 ms │ 92 ms │
└─────────┴──────┴───────┴───────┴───────┴──────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec   │ 66559   │ 66559   │ 80767   │ 82111   │ 77612.8 │ 5713.37 │ 66510   │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 8.31 MB │ 8.31 MB │ 10.1 MB │ 10.3 MB │ 9.7 MB  │ 715 kB  │ 8.31 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘

Req/Bytes counts sampled once per second.

388k requests in 5.06s, 48.5 MB read

find my way

➜  ~ autocannon -c 100 -d 5 -p 10 localhost:3000
Running 5s test @ http://localhost:3000
100 connections with 10 pipelining factor

┌─────────┬───────┬───────┬───────┬───────┬──────────┬─────────┬───────┐
│ Stat    │ 2.5%  │ 50%   │ 97.5% │ 99%   │ Avg      │ Stdev   │ Max   │
├─────────┼───────┼───────┼───────┼───────┼──────────┼─────────┼───────┤
│ Latency │ 11 ms │ 12 ms │ 19 ms │ 25 ms │ 12.57 ms │ 4.49 ms │ 96 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec   │ 66047   │ 66047   │ 79359   │ 80639   │ 76947.2 │ 5524.71 │ 66010   │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 8.25 MB │ 8.25 MB │ 9.92 MB │ 10.1 MB │ 9.62 MB │ 691 kB  │ 8.25 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘

Req/Bytes counts sampled once per second.

385k requests in 5.06s, 48.1 MB read