elpis-core是什么?
elpis-core是一个基于Koa实现的服务端内核引擎,参考了eggjs的设计,使用约定优于配置的设计理念,用于构建 Node.js Web 应用,比如 API 接口、Web 服务、管理后台、服务端渲染等,非常适合用来搭建BFF层。
BFF层是什么?
BFF(Backend for Frontend)层是一种后端架构模式,专门为前端应用程序量身定制的后端服务。它的核心理念是:为不同类型的前端(Web、移动端、小程序等)提供专属的后端接口层,从而简化前端开发,提高性能和开发效率。
为什么需要BFF层
在没有 BFF 的架构中,前端直接调用后端的 API(通常是通用型的微服务或 REST API),会面临以下问题:
- 数据过多或过少:前端拿到的数据不是太多(要裁剪),就是太少(要多次请求)。
- 接口耦合性强:后端接口为多个前端服务而设计,前端需求变化难以快速响应。
- 前端逻辑冗余:需要在前端做拼装、过滤、处理数据的逻辑,影响性能与维护性。
- 缺乏统一网关控制:如权限控制、缓存策略、请求优化等需要额外处理。
BFF 层通常会做什么?
- 聚合多个服务的数据(接口聚合)
- 对数据进行格式转换、过滤、计算等(适合前端使用)
- 做缓存策略、权限校验
- 处理设备/平台差异
总结一下,BFF相当于一个中间层,它的主要职责是: 向真正的后端服务请求数据 ➝ 进行整合/处理 ➝ 返回给前端一个“量身定制”的结果。一般情况下前端使用nodejs就可以完成
内核实现
elpis-core的核心就是loader加载器,通过一系列的loader将规定目录下的文件加载到内存中,然后通过koa启动服务。
const Koa = require('koa');
const path = require('path');
const { sep } = path; //兼容不同操作系统的斜杠
const env = require('./env');
const configLoader = require('./loader/config'); // 先加载应用配置
const extendLoader = require('./loader/extend'); // 加载扩展
const middlewareLoader = require('./loader/middleware'); // 加载中间件
const serviceLoader = require('./loader/service'); // 先加载服务 (因为控制器依赖服务)
const controllerLoader = require('./loader/controller'); // 再加载控制器
const routerSchemaLoader = require('./loader/router-schema'); // 路由校验
const routerLoader = require('./loader/router'); // 最后加载路由 (确保所有依赖已准备好)
module.exports = {
/* 启动项目
@params options 项目配置 */
start(options = {}) {
//koa实例
const app = new Koa();
//应用配置
app.options = options;
//基础路径
app.baseDir = process.cwd();
//业务文件路径
app.businessPath = path.resolve(app.baseDir, `.${sep}app`);
//初始化环境配置
app.env = env();
//加载middleware
middlewareLoader(app);
console.log(`-- [start] load middleware done --`);
//加载routerSchema
routerSchemaLoader(app);
console.log(`-- [start] load routerSchema done --`);
//加载controller
controllerLoader(app);
console.log(`-- [start] load controller done --`);
//加载service
serviceLoader(app);
console.log(`-- [start] load service done --`);
//加载config
configLoader(app);
console.log(`-- [start] load config done --`);
//加载extend
extendLoader(app);
console.log(`-- [start] load extend done --`);
//注册全局中间件
try {
require(`${app.businessPath}${sep}middleware.js`)(app);
console.log(`-- [start] load global middleware done --`);
} catch (e) {
console.log('[exception] there is no global middleware file.');
}
//加载router
routerLoader(app);
console.log(`-- [start] load router done --`);
//启动服务
try {
const port = process.env.PORT || 8080;
const host = process.env.IP || '0.0.0.0';
app.listen(port, host);
console.log(`Server runnin on port:${port}`);
} catch (e) {
console.error(e);
}
}
}
不同loader的作用:
- configLoader通过读取不同环境下的配置文件加载到app.config中。
- extendLoader用来加载扩展,增强elpis-core的功能,例如可以在这里增加日志打印功能。
- middlewareLoader用来加载中间件,将其挂载在koa实例上。每个中间件拥有独立的功能,依靠Koa洋葱圈模型执行操作。在这里可以进行错误捕获、参数校验、api签名请求合法性校验等一系列操作。通过Koa洋葱圈模型保证所有的API请求和页面请求都能通过中间件进行业务逻辑处理。
- serviceLoader用来加载服务,将其挂载在koa实例上,在这里进行读写数据库操作或者调用真正的服务。
- controllerLoader用来加载控制器,将其挂载在koa实例上,在这里调用service服务,进行业务逻辑处理,返回数据给API请求或者页面请求。
- routerSchemaLoader加载路由校验文件,将其挂载在koa实例上。通过 'json-schema & ajv' 对API规则进行约束,配合api-params-verify 中间件使用。
- routerLoader加载路由文件,将其挂载在koa实例上。
中间件执行机制
elpis-core是基于koa实现的,所以它也继承了Koa 中间件的执行机制 = 洋葱模型。什么是洋葱模型呢? Express 的中间件是链式执行,每个中间件都必须调用 next(),如果你忘记 next(),请求就会卡住,不返回结果,前端也会超时。而koa的中间件它不仅支持顺序执行,还可以在中间件内部写 await next() 后回来继续执行,就像洋葱的层层包裹一样。
Koa 洋葱模型
假设你有 3 个中间件:
app.use(async (ctx, next) => {
console.log('👉 中间件 1 - 进入');
await next();
console.log('👈 中间件 1 - 返回');
});
app.use(async (ctx, next) => {
console.log('👉 中间件 2 - 进入');
await next();
console.log('👈 中间件 2 - 返回');
});
app.use(async (ctx, next) => {
console.log('👉 中间件 3 - 进入');
await next();
console.log('👈 中间件 3 - 返回');
});
当请求进来时,打印顺序会是:
👉 中间件 1 - 进入
👉 中间件 2 - 进入
👉 中间件 3 - 进入
👈 中间件 3 - 返回
👈 中间件 2 - 返回
👈 中间件 1 - 返回
就像剥洋葱一样“进去一层 → 再回来一层”,所以叫「洋葱模型」。
假如你做一个日志记录中间件:
app.use(async (ctx, next) => {
console.log(`[请求] ${ctx.method} ${ctx.url}`);
const start = Date.now();
await next(); // 等下面处理完
const ms = Date.now() - start;
console.log(`[响应] ${ctx.method} ${ctx.url} - ${ms}ms`);
});
这个中间件可以在请求“前”打印请求信息,在请求“后”打印响应时间,这在 Express 里是做不到的(因为没办法“回来”)。
总结一下: Koa 的中间件机制支持“回溯执行”,核心靠的是 await next()。而 Express 是单向执行的链,无法回溯执行
小结
通过实现elpis-core这个服务端引擎,对nodejs,koa框架和BFF架构有了一个全新的认识。
注:本文灵感来自抖音“哲玄前端”《大前端全栈实践》