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 , 删除监听方法等。
这个方法不建议在生产环境使用, 可能遇到未执行完就结束进程的问题。