前言
node的后端的开发接触至今也不过4个月,在最近的开发中选用了之前没有接触过的egg.js。虽然还没有深入开发,但是在与之前的项目的node端比较后还是有了比较多的感想。
node后台开发实战
之前的项目的主要选型是koa2+sequelize,主要项目结构似乎也是有板有眼,view层模版渲染,router层负责使用控制器,处理参数,控制器层调用sequelize或者作出数据处理,model层则是数据表的实例。
开发中的问题
- 控制器层的解耦和抽象,这部分在之前的开发中是有一点问题的。原因就是没有面向对象,而是把控制器直接写成了一些函数的module,这样的问题就是很难继承抽象,控制器的一个方法对应了router层的一个api,非常耦合。
- 在api的层的参数校验没有做,这层的参数校验其实很有必要,可以更早的把问题直接在这层抛出
- 没有考虑事务,这部分其实也和之前开发的系统较为简单有关。
egg.js实战
在此次新项目的选型中首要就是解决面向对象的问题,虽然也看了一些别人的项目,但是在把控制器写成一个单例还是把控制器的函数干脆作为静态函数来用的疑惑时,我参考了以下egg.js。之后我发现node后台开发完全可以和java后台开发一样有出色的架构。
控制反转和依赖注入
相信这两种设计思想很多人都会了解,而egg.js中同样有这两种思想的实战。这边只举一个例子就可以清楚的发现
//app/controller/post.js
const Controller = require('../core/base_controller');
class PostController extends Controller {
async list() {
const posts = await this.service.listByUser(this.user);
this.success(posts);
}
}
在控制器中并不需要去实例化需要的service,而是可以直接通过自身的sevice属性就可以拿到需要的实例。整个egg其实就是一个容器,类的实例化和依赖管理就是egg的任务了。这一部分的处理其实和spring是非常接近。
另外不得不提的一点是,egg在对controller和service的实例化处理是并不不同的,egg并不会在一开始就把所有的service实例化,而是采用了即用即插的方式。那么在实际项目中就需要把更多的业务逻辑抽象到service,并且就需要把可以分离的服务拆分好。
egg.js依赖注入的实现
controller的基类
class BaseContextClass {
constructor(ctx) {
this.ctx = ctx;
this.app = ctx.app;
this.config = ctx.app.config;
this.service = ctx.service;
}
}
之后就是ctx.service的由来
// define ctx.service
Object.defineProperty(app.context, property, {
get() {
// distinguish property cache,
// cache's lifecycle is the same with this context instance
// e.x. ctx.service1 and ctx.service2 have different cache
if (!this[CLASSLOADER]) {
this[CLASSLOADER] = new Map();
}
const classLoader = this[CLASSLOADER];
let instance = classLoader.get(property);
if (!instance) {
instance = getInstance(target, this);
classLoader.set(property, instance);
}
return instance;
},
});
这段简短的代码加注释很容易就发现service如何实现的即用即插。至于service的loader其实也很容易就不上源码了,大概就是加载了服务的信息和路径。而controller的loader则是在初始化的时候就加载控制器。
服务和控制器的实例化的代码
function getInstance(values, ctx) {
// it's a directory when it has no exports
// then use ClassLoader
const Class = values[EXPORTS] ? values : null;
let instance;
if (Class) {
if (is.class(Class)) {
instance = new Class(ctx);
} else {
// it's just an object
instance = Class;
}
// Can't set property to primitive, so check again
// e.x. module.exports = 1;
} else if (is.primitive(values)) {
instance = values;
} else {
instance = new ClassLoader({ ctx, properties: values });
}
return instance;
}
以上就是egg实现依赖注入的原理。
最后
在询问了做Java后台的同学后了解到service在很多时候回分布式的部署,并且最佳实践就是与上层的控制器和下层的model层实现完全解耦。总的来说eggjs的学习对今后在代码解耦的实践中会有很大的启发,并且可以借这个机会重新认识node的后端开发。
之后会接着去学习egg.js的另一大特色——插件。