egg(蛋)
egg是什么?
我们做后端应用的开发,都是基于MVC这种模式,虽然是一个统一的程序设计思想,但是在实现上肯定是千奇百怪,不同的人对框架的设计一定是不同的,那么对于一个团队的开发来讲,就带来了难度,正所谓众口难调。
egg是基于js的后端开发服务框架,奉行一个理念约定优于配置,按照统一的一套约定进行应用开发。约定优于配置,当我第一次在egg文档中看到这句话的时候还是挺亲切的,因为以前在看spring boot的时候,也有相同的理念,约定优于配置(慢慢体会,意味深长),下面就是的项目目录就是egg的约定。app中的目录结构就是应用的约定,config目录下就是各种开发环境,中间件等的配置文件。
如果一个框架有固定的技术选型会使框架的扩展性变差,无法满足各种定制需求。通过 Egg,团队的架构师和技术负责人可以非常容易地基于自身的技术架构在 Egg 基础上扩展出适合自身业务场景的框架。这就是egg的第二个特点,没有固定的插件绑定,我们可以根据自身业务的需求来扩张应用的框架,选取心仪的插件。
第三个特点就是egg继承自koa,继承了很多koa中的优秀策略和基本对象。例如,koa中的中间件模式是一种洋葱型的模式,egg页一样,那么koa中的中间件也可以直接拿过来在egg中使用。再如koa中请求的request对象,response对象,在egg中还是存在的,可以通过ctx对象获得。
如何快速初始化一个egg项目
快速的初始化,推荐直接使用脚手架,只需几条简单指令,即可快速生成项目:
$ npm i egg-init -g $ egg-init egg-example --type=simple $ cd egg-example $ npm i |
然后打开egg-example项目就可以看到初始化之后的工程项目就是下图所示
其中有一个package文件标示当前工程所有版本信息和依赖等等。

我们看一下文档中关于目录的约定规范
egg-project ├── package.json ├── app.js (可选) ├── agent.js (可选) ├── app | ├── router.js │ ├── controller │ | └── home.js │ ├── service (可选) │ | └── user.js │ ├── middleware (可选) │ | └── response_time.js │ ├── schedule (可选) │ | └── my_task.js │ ├── public (可选) │ | └── reset.css │ ├── view (可选) │ | └── home.tpl │ └── extend (可选) │ ├── helper.js (可选) │ ├── request.js (可选) │ ├── response.js (可选) │ ├── context.js (可选) │ ├── application.js (可选) │ └── agent.js (可选) ├── config | ├── plugin.js | ├── config.default.js │ ├── config.prod.js | ├── config.test.js (可选) | ├── config.local.js (可选) | └── config.unittest.js (可选) └── test ├── middleware | └── response_time.test.js └── controller └── home.test.js |
如上,由框架约定的目录:
-
app/router.js
用于配置 URL 路由规则,具体参见 Router。 -
app/controller/**
用于解析用户的输入,处理后返回相应的结果,具体参见 Controller。 -
app/service/**
用于编写业务逻辑层,可选,建议使用,具体参见 Service。 -
app/middleware/**
用于编写中间件,可选,具体参见 Middleware。 -
app/public/**
用于放置静态资源,可选,具体参见内置插件 egg-static。 -
app/extend/**
用于框架的扩展,可选,具体参见框架扩展。 -
config/config.{env}.js
用于编写配置文件,具体参见配置。 -
config/plugin.js
用于配置需要加载的插件,具体参见插件。 -
test/**
用于单元测试,具体参见单元测试。 -
app.js
和agent.js
用于自定义启动时的初始化工作,可选,具体参见启动自定义。关于agent.js
的作用参见Agent机制。
Egg 在 Koa 的基础上进行增强最重要的就是基于一定的约定,根据功能差异将代码放到不同的目录下管理,对整体团队的开发成本提升有着明显的效果。Loader 实现了这套约定,并抽象了很多底层 API 可以进一步扩展。
Egg 将应用、框架和插件都称为加载单元(loadUnit),因为在代码结构上几乎没有什么差异,下面是目录结构
loadUnit ├── package.json ├── app.js ├── agent.js ├── app │ ├── extend │ | ├── helper.js │ | ├── request.js │ | ├── response.js │ | ├── context.js │ | ├── application.js │ | └── agent.js │ ├── service │ ├── middleware │ └── router.js └── config ├── config.default.js ├── config.prod.js ├── config.test.js ├── config.local.js └── config.unittest.js |
不过还存在着一些差异
文件 | 应用 | 框架 | 插件 |
---|---|---|---|
package.json | ✔︎ | ✔︎ | ✔︎ |
config/plugin.{env}.js | ✔︎ | ✔︎ | |
config/config.{env}.js | ✔︎ | ✔︎ | ✔︎ |
app/extend/application.js | ✔︎ | ✔︎ | ✔︎ |
app/extend/request.js | ✔︎ | ✔︎ | ✔︎ |
app/extend/response.js | ✔︎ | ✔︎ | ✔︎ |
app/extend/context.js | ✔︎ | ✔︎ | ✔︎ |
app/extend/helper.js | ✔︎ | ✔︎ | ✔︎ |
agent.js | ✔︎ | ✔︎ | ✔︎ |
app.js | ✔︎ | ✔︎ | ✔︎ |
app/service | ✔︎ | ✔︎ | ✔︎ |
app/middleware | ✔︎ | ✔︎ | ✔︎ |
app/controller | ✔︎ | ||
app/router.js | ✔︎ |
文件按表格内的顺序自上而下加载
在加载过程中,Egg 会遍历所有的 loadUnit 加载上述的文件(应用、框架、插件各有不同),加载时有一定的优先级
- 按插件 => 框架 => 应用依次加载
- 插件之间的顺序由依赖关系决定,被依赖方先加载,无依赖按 object key 配置顺序加载,具体可以查看插件章节
- 框架按继承顺序加载,越底层越先加载。
比如有这样一个应用配置了如下依赖
app | ├── plugin2 (依赖 plugin3) | └── plugin3 └── framework1 | └── plugin1 └── egg |
最终的加载顺序为
=> plugin1 => plugin3 => plugin2 => egg => framework1 => app |
plugin1 为 framework1 依赖的插件,配置合并后 object key 的顺序会优先于 plugin2/plugin3。因为 plugin2 和 plugin3 的依赖关系,所以交换了位置。framework1 继承了 egg,顺序会晚于 egg。应用最后加载。
上面已经列出了默认会加载的文件,Egg 会按如下文件顺序加载,每个文件或目录再根据 loadUnit 的顺序去加载(应用、框架、插件各有不同)。
- 加载 plugin,找到应用和框架,加载
config/plugin.js
- 加载 config,遍历 loadUnit 加载
config/config.{env}.js
- 加载 extend,遍历 loadUnit 加载
app/extend/xx.js
-
自定义初始化,遍历 loadUnit 加载
app.js
和agent.js
- 加载 service,遍历 loadUnit 加载
app/service
目录 - 加载 middleware,遍历 loadUnit 加载
app/middleware
目录 - 加载 controller,加载应用的
app/controller
目录 - 加载 router,加载应用的
app/router.js
Egg提供了应用启动(beforeStart
), 启动完成(ready
), 关闭(beforeClose
)这三个生命周期方法
beforeStart
方法在 loading 过程中调用, 所有的方法并行执行。 一般用来执行一些异步方法, 例如检查连接状态等, 比如 egg-mysql
就用 beforeStart
来检查与 mysql 的连接状态。所有的 beforeStart
任务结束后, 状态将会进入 ready
。不建议执行一些耗时较长的方法, 可能会导致应用启动超时。
ready
方法注册的任务在 load 结束并且所有的 beforeStart
方法执行结束后顺序执行, HTTP server 监听也是在这个时候开始, 此时代表所有的插件已经加载完毕并且准备工作已经完成, 一般用来执行一些启动的后置任务。
beforeClose
注册方法在 app/agent 实例的 close
方法被调用后, 按注册的逆序执行。一般用于资源的释放操作, 例如 egg
用来关闭 logger , 删除监听方法等。
这个方法不建议在生产环境使用, 可能遇到未执行完就结束进程的问题。