1

Node之Apr框架开发

 2 years ago
source link: https://lianpf.github.io/posts/%E5%BC%80%E5%8F%91%E6%97%A5%E8%AE%B0/12.apr%E6%A1%86%E6%9E%B6%E5%BC%80%E5%8F%91/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Node之Apr框架开发

2021-04-12

在低代码平台开发过程,由于物料管理系统以及Node Server 中间层的搭建使用过程中,发现很多通用的库和配置可以…

在低代码平台开发过程,由于物料管理系统以及Node Server 中间层的搭建使用过程中,发现很多通用的库和配置可以沉淀,形成可复制的解决方案。所以,基于开源框架Egg.js(基于Koa2)进行了拓展封装形成了初版Apr。

整个团队依赖初版Apr作为统一的Node方案,随着业务场景越来越丰富,一些插件、middle等公共配置成为最佳实践后,逐渐下沉到Apr框架中,让Apr越来越成熟。而后,依赖Apr的其他项目仅需简单的升级下框架的版本即可享受到Apr带来的红利。

如果你的团队也在考虑搭建类似的框架,那么请考虑如下问题:

  • 如果你的团队遇到过:
    • 维护很多个项目,每个项目都需要复制拷贝诸如 gulpfile.js / webpack.config.js 之类的文件
    • 每个项目都需要使用一些相同的类库,相同的配置
    • 在新项目中对上面的配置做了一个优化后,如何同步到其他项目?
  • 如果你的团队需要:
    • 统一的技术选型,比如数据库、模板、前端框架及各种中间件设施都需要选型,而框架封装后保证应用使用一套架构
    • 统一的默认配置,开源社区的配置可能不适用于公司,而又不希望应用去配置
    • 统一的部署方案,通过框架和平台的双向控制,应用只需要关注自己的代码,具体查看应用部署
    • 统一的代码风格,框架不仅仅解决代码重用问题,还可以对应用做一定约束,作为企业框架是很必要的。Apr依赖于 Egg, Egg 在 Koa 基础上做了很多约定,框架可以使用 Loader 自己定义代码规则

如果需要,以下是Apr的简易版搭建过程,也许对你有所帮助:

稳定成熟版涉及到公司相关业务,故仅存内网中,不对外暴露,请勿私聊获取

二、框架与多进程

框架的扩展是和多进程模型有关的,我们已经知道多进程模型,我们需要扩展的类有 Agent 和 Application。

在 Agent Worker 启动的时候会实例化 Agent,而在 App Worker 启动时会实例化 Application,这两个类又同时继承 EggCore。

EggCore 可以看做 Koa Application 的升级版,默认内置 Loader、Router 及应用异步启动等功能,可以看做是支持 Loader 的 Koa。 而我们要搭建的Apr则依赖于EggCore。

      Koa Application
             ^
          EggCore

             ^
          AprCore
             ^
      ┌──────┴───────┐
      │              │
  Apr Agent      Apr Application
     ^               ^
agent worker     app worker

三、定制框架: Apr

1. create Apr & init

$ mkdir apr && cd apr
$ npm init egg --type=framework
$ npm i
$ npm test

2. 框架继承

框架支持继承关系,可以把框架比作一个类,基类是 Egg 框架,定义一个框架需要继承于Egg且实现 Egg 所有的 API。

// package.json
{
  "name": "apr",
  "dependencies": {
    "egg": "^2.0.0"
  }
}

// index.js
module.exports = require('./lib/framework.js');

// lib/framework.js
const path = require('path');
const egg = require('egg');
const EGG_PATH = Symbol.for('egg#eggPath');

class Application extends egg.Application {
  get [EGG_PATH]() {
    // 返回 framework 路径
    return path.dirname(__dirname);
  }
}

// 覆盖了 Egg 的 Application
module.exports = Object.assign(egg, {
  Application,
});

应用启动时需要指定框架名(在 package.json 指定 egg.framework,默认为 egg),Loader 将从 node_modules 找指定模块作为框架,并加载其 export 的 Application

{
  "scripts": {
    "dev": "egg-bin dev"
  },
  "egg": {
    "framework": "apr"
  }
}

现在 apr 框架目录已经是一个 loadUnit,那么相应目录和文件(如 app 和 config)都会被加载

3. 自定义 Agent

上面的例子自定义了 Application,因为 Egg 是多进程模型,所以还需要定义 Agent,原理是一样的

// lib/framework.js
const path = require('path');
const egg = require('egg');
const EGG_PATH = Symbol.for('egg#eggPath');

class Application extends egg.Application {
  get [EGG_PATH]() {
    // 返回 framework 路径
    return path.dirname(__dirname);
  }
}

class Agent extends egg.Agent {
  get [EGG_PATH]() {
    return path.dirname(__dirname);
  }
}

// 覆盖了 Egg 的 Application
module.exports = Object.assign(egg, {
  Application,
  Agent,
});

但因为 Agent 和 Application 是两个实例,所以 API 有可能不一致

4. 自定义 Loader

Loader 应用启动的核心,使用它还能规范应用代码,我们可以基于这个类扩展更多功能,比如加载数据代码。

扩展 Loader 还能覆盖默认的实现,或调整现有的加载顺序等

自定义 Loader 也是用 Symbol.for(‘egg#loader’) 的方式,主要的原因还是使用原型链

// lib/framework.js
const path = require('path');
const egg = require('egg');
const EGG_PATH = Symbol.for('egg#eggPath');

class AppWorkerLoader extends egg.AppWorkerLoader {
  load() {
    super.load();
    // 自己扩展
  }
}

class Application extends egg.Application {
  get [EGG_PATH]() {
    // 返回 framework 路径
    return path.dirname(__dirname);
  }
  // 覆盖 Egg 的 Loader,启动时使用这个 Loader
  get [EGG_LOADER]() {
    return AppWorkerLoader;
  }
}

// 覆盖了 Egg 的 Application
module.exports = Object.assign(egg, {
  Application,
  // 自定义的 Loader 也需要 export,上层框架需要基于这个扩展
  AppWorkerLoader: AppWorkerLoader,
});

AgentWorkerLoader 扩展也类似,这里不再举例

AgentWorkerLoader 加载的文件可以于 AppWorkerLoader 不同,比如:默认加载时,Egg 的 AppWorkerLoader 会加载 app.js 而 AgentWorkerLoader 加载的是 agent.js。

5. 拓展其他功能

你还可以自己去丰富plugins、middle等各种各样的功能特效,让你的Apr功能变得变得越来越强大,此处以plugins为例。

  • 内置 nunjucks 来提供服务端模板渲染能力
  • 封装 egg-mysql
// Apr 框架配置
// package.json
{
  "name": "apr",
  "version": "1.0.0",
  "dependencies": {
    "egg-mysql": "^3.0.0",
    "egg-view-nunjucks": "^2.0.0"
  }
}

// config/plugin.js
/**
 * @desc: framework 集成各种 plugins
 * @path: 'config/plugin.js'
 * */
module.exports = {
  // mysql
  mysql: {
    enable: false,
    package: 'egg-mysql',
  },
  // add you build-in plugin here, example:
  nunjucks: {
    enable: false,
    package: 'egg-view-nunjucks',
  }
}

使用Apr的应用配置:

// 应用配置
// package.json
{
  "dependencies": {
    "apr": "^1.0.0",
  }
}

// config/plugin.js
module.exports = {
  // 开启插件
  mysql: true,
  nunjucks: true,
}

在框架的基础上还可以扩展出新的框架,也就是说框架是可以无限级继承的,有点像类的继承

+-----------------------------------+--------+
|      app1, app2, app3, app4       |        |
+-----+--------------+--------------+        |
|     |              |  framework2  |        |
+     |       Apr    +--------------+ plugin |
|     |              |  framework1  |        |
+     +--------------+--------------+        |
|                   Egg             |        |
+-----------------------------------+--------|
|                   Koa                      |
+-----------------------------------+--------+

四、单元测试

参考

五、框架启动原理

  • startCluster 启动传入 baseDir 和 framework,Master 进程启动
  • Master 先 fork Agent Worker
    • 根据 framework 找到框架目录,实例化该框架的 Agent 类
    • Agent 找到定义的 AgentWorkerLoader,开始进行加载
    • AgentWorkerLoader,开始进行加载 整个加载过程是同步的,按 plugin > config > extend > agent.js > 其他文件顺序加载
    • agent.js 可自定义初始化,支持异步启动,如果定义了 beforeStart 会等待执行完成之后通知 Master 启动完成。
  • Master 得到 Agent Worker 启动成功的消息,使用 cluster fork App Worker
    • App Worker 有多个进程,所以这几个进程是并行启动的,但执行逻辑是一致的
    • 单个 App Worker 和 Agent 类似,通过 framework 找到框架目录,实例化该框架的 Application 类
    • Application 找到 AppWorkerLoader,开始进行加载,顺序也是类似的,会异步等待,完成后通知 Master 启动完成
  • Master 等待多个 App Worker 的成功消息后启动完成,能对外提供服务

六、Apr结构

Apr
├── README.md
├── app
│   ├── extend
│   │   ├── application.js
│   │   └── context.js
│   └── service
│       └── test.js
├── config
│   ├── config.default.js
│   └── plugin.js
├── index.js
├── lib
│   └── framework.js
├── package-lock.json
├── package.json
└── test
    ├── fixtures
    │   └── example
    │       ├── app
    │       │   ├── controller
    │       │   │   └── home.js
    │       │   └── router.js
    │       ├── config
    │       │   └── config.default.js
    │       └── package.json
    └── framework.test.js

demo源码仓库,欢迎 star


最后, 希望大家早日实现:成为前端高手的伟大梦想!
欢迎交流~

微信公众号

本文版权归原作者曜灵所有!未经允许,严禁转载!对非法转载者, 原作者保留采用法律手段追究的权利!
若需转载,请联系微信公众号:连先生有猫病,可获取作者联系方式!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK