Closed killagu closed 6 years ago
Translation of this issue:
Some plugins may need to be asynchronous before they can be loaded. They need to use beforeStart
to perform asynchronous operations.
Mount it to ʻapp`.
At the same time, before the application starts, if such a plugin is used in beforeStart
, it may not start normally.
E.g:
// pluginA/index.js
app.beforeStart(async ()=>{
Const bar = await pull();
app.addSingleton('foo', (config,app) => {
Return new Foo(bar);
});
});
// app.js
app.beforeStart(async ()=>{
Await app.foo.foooo(); // app.foo may not be injected yet
});
Add appReady to EggCore
.
beforeAppStart(scope) {
Const done = this.appReady.readyCallback(name);
process.nextTick(() => {
This.ready()
.then(()=>utils.callFn(scope))
.then(() => done(), done);
});
}
The server should also be modified to ʻapp.appReady.ready(startServer);`
app.appReady 下面还有几个 API?
app.appReady
这个就是个ready-callback
对象。
@killagu 你可以在应用启动后执行相关的服务,例如:app的API接口已经提供了ready方法,例如:
module.exports = app => {
app.ready(() => {
console.log('app is ready!');
const ctx = app.createAnonymousContext();
ctx.service.send.abc();
ctx.service.send.abd();
ctx.service.send.acd();
});
};
@tong3jie 你讲的没错。ready
是应用启动后执行相关的服务。
但是我讲的情况都是在应用启动前的, beforeStart
的情况。
addSingleton had support async since last month
两个想法
提供 afterReady,等现在的 ready 后出发 afterReady 再触发现在的 ready(也就是启动)
提供一个启动检查模式,在 app/start_checker 中定义一个类,实现 check 方法(实现和第一条类似)。
提供 afterReady,等现在的 ready 后出发 afterReady 再触发现在的 ready(也就是启动)
afterReady
这个名字有歧义。其实是在ready
之前触发的。
start_checker
+1
可能还可以有 health checker
boot +1
boot + 1
starter 呢?
定义一个新目录, 暂时命名为app/checker
直觉让我反对新加一个目录
新加目录比新加 API 友好些,比如 schedule,notify 都是新增目录的
这个功能比较反对新加目录的原因主要还是这些功能都是应用开发者基本用不到的。
egg 的启动,有点像 react 的生命周期的意思了。是否应该再完善一点,把启动时期的各个生命周期给定义下来:
- appDidLoad
- appWillStart
- appDidStart
- appWillClose
life cycle +1
而且 issue 中描述的这个问题,通过新增一个应用启动阶段,看似可以解决当下的问题,但是其实是在埋坑,最多只能算是一个临时解决方案。现在如果 B 依赖 A,把 B 放在启动阶段,A 放在 beforeStart 是可以解决了,那如果出现了 C 依赖 B, B 依赖 A 呢?C 放哪里?
本质上这还是执行顺序的问题,想清楚之前不建议加新的
如果 C 和 B 都是应用定义的话,那在应用里面可以写在一起。
本质还是应用开发者对 beforeStart 的真正启动顺序不理解,不改变 beforeStart 的前提下,需要提供应用启动的环节。增加 API 和增加目录都是具体的解决方案,应用启动的这个环节是要真正增加的。
现在给应用层的 app.js 实在是太尴尬了。90% 的人是用做应用启动阶段的,但是还有部分的人在里面操作 middleware,这个又不能算启动阶段。
我本身是不太建议在应用层用 app.js 的,因为对传统开发者来说这就是入口文件,感觉对顺序还是比较难理解的。
关于生命周期这个, 我觉得是这样的:
- app.beforeStart // 代表load的过程
- appDidStart: app.ready
- appWillClose: app.beforeClose
现在appDidLoad
和appWillStart
是没有对应的实现, 但是这两个生命周期是APP层面就可以实现的。
要把原来的这些方法改造相比于增加一个目录来说成本太大了。
@dead-horse
egg 的启动,有点像 react 的生命周期的意思了。是否应该再完善一点,把启动时期的各个生命周期给定义下来:
同意,但是觉得没必要像 react 那么复杂
我能接受的三个方案
应用里面的 app.beforeStart
放到所有其他 beforeStart 执行完以后再执行
如上描述
之前有问题的 case 是:
app.beforeStart(async function() {
// 异步操作
await fetchPWD();
app.addSingleton('mysql', () => {
// ...
return instance;
});
});
可以改成如下,确保 app.mysql 一开始就有
app.beforeStart(async function() {
app.addSingleton('mysql', async () => {
// 异步操作
await fetchPWD();
// ...
return instance;
});
});
偏向我提的方案 1
如果在 加 API
vs 加目录
里选的话,稍微偏向加目录一点。原因是:
@popomore: 本质还是应用开发者对 beforeStart 的真正启动顺序不理解,不改变 beforeStart 的前提下,需要提供应用启动的环节。增加 API 和增加目录都是具体的解决方案,应用启动的这个环节是要真正增加的。
@dead-horse: 现在给应用层的 app.js 实在是太尴尬了。90% 的人是用做应用启动阶段的,但是还有部分的人在里面操作 middleware,这个又不能算启动阶段。
现在生命周期这块确实有点不清晰,我有时候也晕晕的,这块建议是补充下文档,画一张生命周期的图 @popomore
至于 加API
vs 加目录
,后者我觉得更像是 加文件
,如 app/extend/boot.js
,但后者本质其实是在 加 API
的基础上加一个目录规范而已,我更倾向于讨论清楚生命周期,把已知的加了,并把文档清晰化。
那我先来把egg现有的生命周期画一下,然后讨论一下需要增加的生命周期。
issue先hold
通过约定 app.js 来实现可能更加容易理解,而且考虑到 app 和 agent 都有这个过程,如果定义目录就要拆两个目录,对用户太复杂了
// app.js
module.exports = class AppBoots {
constructor(app) {
this.app = app;
}
async afterLoad() {
}
async beforeReady() {
}
async ready() {
}
async beforeClose() {
}
};
感觉现在只需要解决异步挂 API 这个问题,就够了。
甚至不加 API 的话,通过文档,建议开发者,在 beforeStart 或者 configWillReady 之类钩子,让开发者去获取远程配置。
然后开发者的 API 挂载,还是放在 app/extend/application.js 里面,用 getter 做延迟初始化。 这样是不是就可以了?
// app.js
app.beforeStart(async () => {
app.config.mysql.client = await app.curl('http://');
});
// app/extend/application.js
module.exports = {
get mysql() {
if (!this[MYSQL]) {
this[MYSQL] = new MYSQL(this.config.mysql.client);
}
return this[MYSQL];
}
}
class Boot {
// config文件加载完成
// 可以用来修改中间件顺序
configDidLoad() {}
// 文件都加载完成
// 可以做一些client的异步加载
async appDidLoad() {}
// 所有的插件已经加载完毕
// 可以正常的使用插件
async appWillReady() {}
// 当前进程ready
// 与app.ready相同
async appDidReady() {}
// 所有的进程ready
async serverDidReady() {}
}
cc: @atian25 @gxcsoccer @dead-horse @popomore
大家可以套用一下场景,看看能不能吻合。
将会替换 app.js/agent.js, 作为启动点。
@killagu 加下注释,这几个函数的描述
确定最终的 API 方式,包括今天没讨论清楚的,app 和 agent 的区别
讨论清楚了,就是 app.js 和 agent.js 的差别
把方法前缀 app
都去掉了。
异步API的注册推荐在 didLoad
阶段使用。
beforeStart 会有很长一段时间和声明周期共存,需要确定清楚 beforeStart 的明确执行时间。是不是放在 willReady 之后执行比较合适?
beforeStart 的功能保持原状,刚是从 DidLoad 开始到 DidReady 之前,这样在不改的前提下,应用在 DidReady 注册的方法还是可以在插件完成后执行。
我建议直接实现 hook 体系,不管是框架层还是应用层都能用。
支持两种方式调用,功能少的可以在 app.js 中直接定义,多则写到 app/hook 中 比如 app.js :
module.exports = app => {
// 框架层的 hook
app.hook.add('appBeforeReady', async () => {
console.log('before running!');
});
// 应用层的 hook
app.hook.add('userAfterCreate', async () => {
console.log('app is ready!');
});
};
如果 hook 比较多怎么办?放到 app/hook 中
// app/hook/appBeforeReady.js
module.exports = async (data) => {
// blabla
}
// app/hook/userAfterCreate.js
module.exports = async (data) => {
// blabla
}
运行的使用就直接调
await app.hook.run('appBeforeReady');
await app.hook.run('userAfterCreate');
这种方式我觉得比较舒服,也有扩展性~
@okoala boot
类是为了取代 app.js
现有的写法的, 框架层和应用层都能使用的。
提供的示例代码中 hook
方式更像是底层API, 实现的时候可能是使用类似的方法, 上层提供类的方式更友好一些。
面向应用开发者提供一种实现方式,达到统一的规范。
还有没看懂 userAfterCreate
这是什么方法?
运行时如何调用是实现细节了, 就不在此处讨论了。
@killagu userAfterCreate
表明是自定义的 hook,可以是业务层自己自定义,hook 是比较底层,但是扩展性就更广了。甚至你可以 hook 到某个插件中。
@okoala 你这样说就好像 messenger
了不是么, 一方去 messenger.on('foo')
, 另一方去 messenger.send('foo')
, 和我们讨论的生命周期不是一个概念了。
文档是不是也要改改
land via https://github.com/eggjs/egg/pull/2972 and publish at https://github.com/eggjs/egg/pull/2989
@killagu:What about making 'configDidLoad' async?All the other methods are async.
@atian25:I'll help to do English trans if free.
@Maledong configDidLoad
should be sync. It's same as
// app.js
module.exports = app => {
// e.g. modify core middlewares order
};
It have be done before other files load, so the modifications can take effect.
背景描述
有的插件可能需要异步操作后才能加载, 需要使用
beforeStart
来执行异步操作, 挂载到app
上。同时应用启动前, 又在
beforeStart
中使用了这样的插件, 就可能不能正常启动。例如:
解决方案
在EggCore
上增加appReady。服务器启动时也应该修改为app.appReady.ready(startServer);
定义一个新目录, 暂时命名为
app/checker
。在应用启动阶段(所有的
beforeStart
都执行完毕),调用
app/checker
目录下的checker
实现, 调用完成后调用通过app.ready
注册的回调。现征集该目录的命名, 暂有两个选项:
cc: @gxcsoccer @XadillaX @popomore @coolme200