如果说 Express / Koa 给你的是"一堆乐高零件,随便拼",那 Egg.js 给你的是"一套带图纸的施工规范"。它由阿里开源,基于 Koa 之上,核心理念只有一句:约定优于配置(Convention over Configuration)。
这篇讲清 Egg 在"约定"什么,以及这种约束对大团队意味着什么。
约定优于配置:少做选择题
Express 给你最大的自由,但自由是有代价的:十个团队能写出十种目录结构、十种代码组织方式。新人接手项目,光摸清"这个项目把 service 放哪了"就要花半天。
Egg 的思路是反过来的:框架替你把这些决定都做了。文件放哪、怎么命名、按什么规则被加载,全有明确约定。你只要按约定把文件放进对应目录,框架自动识别、自动挂载。
app/
├── router.js # 路由配置,统一入口
├── controller/ # 控制器:处理请求、参数、响应
│ └── post.js
├── service/ # 服务:业务逻辑、数据访问
│ └── post.js
├── middleware/ # 中间件
│ └── auth.js
├── extend/ # 扩展 ctx / application / helper
│ └── context.js
└── schedule/ # 定时任务
└── cleanup.js
config/
├── config.default.js # 默认配置
├── config.prod.js # 生产环境配置(自动按环境合并)
└── plugin.js # 插件开关你不需要 require 这些文件,Egg 的 Loader 会自动加载并按约定挂载。
一个请求是怎么流转的
约定不只在目录,也在调用关系上。Egg 强约定 Controller → Service 的分层:
// app/router.js —— 路由集中声明
module.exports = (app) => {
const { router, controller } = app
router.get('/posts/:id', controller.post.show)
}// app/controller/post.js —— 只管参数和响应,不写业务
const { Controller } = require('egg')
class PostController extends Controller {
async show() {
const { ctx } = this
const id = Number(ctx.params.id)
// 调用 service 拿数据
const post = await ctx.service.post.findById(id)
if (!post) {
ctx.status = 404
ctx.body = { message: '文章不存在' }
return
}
ctx.body = post
}
}
module.exports = PostController// app/service/post.js —— 业务逻辑、数据库访问都在这
const { Service } = require('egg')
class PostService extends Service {
async findById(id) {
// this.app.mysql 是 egg-mysql 插件提供的
return await this.app.mysql.get('posts', { id })
}
}
module.exports = PostService注意 ctx.service.post、this.app.mysql 这些 —— 你从来没手动 new 过 PostService,也没手动连过数据库。Egg 把这些都按约定注入好了,你只管按规矩调用。这是约定带来的"心智省力"。
插件机制:可插拔的能力
Egg 把"能力"做成了插件(plugin)。数据库、Redis、安全防护、定时任务…… 都是插件。需要什么,装上、在 plugin.js 里打开开关、在 config 里配好参数即可:
// config/plugin.js
exports.mysql = {
enable: true,
package: 'egg-mysql',
}// config/config.default.js
exports.mysql = {
client: {
host: '127.0.0.1',
port: 3306,
user: 'root',
password: '...',
database: 'blog',
},
}启用后,app.mysql / ctx.service 里就能直接用了。这种"约定好的扩展点"让团队能沉淀自己的插件库,把通用能力复用到每个项目。
Egg 的多进程模型:Master / Agent / Worker
这是 Egg 最有特色、也最值得理解的设计。Egg 应用启动后,不是一个进程,而是一组角色明确的进程:
┌─────────┐
│ Master │ 总管:不跑业务,负责守护和调度
└────┬────┘
┌──────────┴──────────┐
┌────┴────┐ ┌─────┴─────┐
│ Agent │ │ Worker × N │
└─────────┘ └───────────┘
只有一个,跑 按 CPU 核数起多个,
"全局唯一"的活 真正处理请求三种角色分工明确:
- Master:进程总管。不处理任何业务请求,只负责创建 / 守护 Agent 和 Worker,某个 Worker 挂了它负责重启。它是整个应用的稳定基石
- Agent:全局只有一个。用来干那些"不该每个 Worker 都干一遍"的活 —— 比如监听配置中心、维护一个全局长连接、做集中式的定时任务调度
- Worker:真正处理请求的进程,按 CPU 核数起多个,充分利用多核
为什么要有 Agent?设想你有 4 个 Worker,如果每个都去连配置中心、每个都跑同一个定时任务,就重复了 4 次。把这类"全局唯一"的活交给唯一的 Agent,Worker 只管处理请求,职责就清爽了。
这套多进程模型由
egg-cluster实现。理解它对排查生产问题很有用:看到日志里 master / agent / worker 的区分,就知道是哪个角色出了状况。
框架定制:Egg 真正的野心
Egg 还有一层很多人没注意到的能力:它本身被设计成"可以被继承的框架"。
大公司里,不同业务线往往有共性需求(统一的鉴权、统一的日志格式、内部 RPC)。Egg 允许你基于它定制出上层框架,把这些共性沉淀进去,各业务线再基于你的框架开发:
Koa → Egg → 公司框架(如 chair) → 各业务应用这种"框架的框架"能力,正是 Egg 主打"企业级"的底气 —— 它不只服务一个应用,而是服务一整个组织的工程规范。
和 Nest 怎么选
同样是企业级,Egg 和 Nest 风格迥异:
- Egg:约定驱动,基于 Koa,JS 友好(也支持 TS),多进程模型开箱即用,适合"按规范快速产出"、团队希望强统一的场景
- Nest:依赖注入驱动,TS 优先,装饰器风格,架构概念更重,适合习惯 Spring/Angular 思路、追求强类型和细粒度切面的团队
没有谁更好,看团队基因。喜欢"约定省心"选 Egg,喜欢"DI + 强类型"选 Nest。
小结
- Egg 的核心是约定优于配置:目录怎么放、文件怎么命名、按什么规则加载,框架都定好了,减少团队的"选择内耗"
- 强约定 Controller(管请求)→ Service(管业务) 分层,靠 Loader 自动加载注入
- 能力做成插件,装上 + 开开关 + 配参数即可用,便于团队沉淀复用
- 独特的 Master / Agent / Worker 多进程模型:Master 守护、Agent 干全局唯一的活、Worker 处理请求
- 可以基于 Egg 定制上层框架,沉淀整个组织的工程规范
- 想要"约定省心"用 Egg,想要"DI + 强类型"用 Nest
约定优于配置的本质,是用"框架的约束"换取"团队的一致性"。项目越大、人越多,这种约束的价值就越明显。