UNPKG

utquidem

Version:

The meta-framework suite designed from scratch for frontend-focused modern web development.

327 lines (238 loc) 9.57 kB
--- sidebar_position: 4 title: 运行时框架 --- 如前面章节所说,目前 Modern.js 的 [BFF](/docs/guides/features/server-side/bff/frameworks) 支持 4 种主流的 Server 框架,可以根据自身偏好选择。 多框架支持也是【 一体化 BFF 】中重要的一环,多数情况下,开发者直接使用钩子文件来扩展 BFF 函数,无需关心通过框架启动服务、日志输出等应用级别的问题。 所有框架均支持 BFF 函数的所有能力,并且使用方式是相同的,例如: - RESTful API - Schema 模式 - Hooks(useContext) - 不同的数据类型 - 动态路由 - 一体化调用 Modern.js BFF 中兼容了这些框架大部分的规范,开发者可以直接使用对应 Server 框架的约定和生态。 每一种框架都提供了两类扩展写法 BFF 函数的方式,分别是【 函数写法 】和【 框架写法 】。 ## 函数写法 在上一章节中,简单的演示了 Express 扩展 BFF 函数的示例。 函数写法就是通过添加钩子文件 `_app.ts` 的方式,编写中间件逻辑来扩展 BFF 函数。 ### [Express](https://expressjs.com/) Express 的函数写法,可以通过在 `_app.[tj]s` 下添加 `express` 的中间件: ```ts import { hook } from "@modern-js/runtime/server"; export default hook(({ addMiddleware }) => { addMiddleware(async (req, res, next) => { req.query.id = "express"; await next(); }); }); ``` ### [Nest](https://nestjs.com/) Nest 支持添加两种类型的内容:Express 的函数中间件和 Nest 中的 [Module](https://docs.nestjs.com/modules)。 Nest 的函数中间件的添加与 Express 段中的示例相同,Module 写法如下: ```ts title=api/_app.ts import { hook } from "@modern-js/runtime/server"; import { Module, Injectable, Controller, Get } from "@nestjs/common"; @Controller("cats") export class CatsController { constructor(private readonly catsService: CatsService) {} @Get() getHello() { return this.catsService.getHello(); } } @Injectable() class CatsService { getHello(): string { return "Hello world!"; } } @Module({ controllers: [CatsController], providers: [CatsService], }) class CatsModule {} export default hook(({ addMiddleware }) => { addMiddleware(CatsModule); }); ``` ### [Koa](https://koajs.com/) Koa 函数写法下,可以通过在 `_app.[tj]s` 下添加 koa 的中间件: ```ts import { hook } from "@modern-js/runtime/server"; export default hook(({ addMiddleware }) => { addMiddleware(async (ctx, next) => { console.info(`access url: ${req.url}`); next(); }); }); ``` ### [Egg](https://eggjs.org/) Egg 函数写法下,可以通过在 `_app.[tj]s` 下添加 egg 的中间件: ```ts import { hook } from "@modern-js/runtime/server"; export default hook(({ addMiddleware }) => { addMiddleware((options) => async (ctx, next) => { console.info(`access url: ${ctx.url}`); next(); }); }); ``` 也可以添加 egg 第三方的中间件,且给第三方中间件注入参数: ```ts import { hook } from "@modern-js/runtime/server"; export default hook(({ addMiddleware }) => { addMiddleware([ "eggMiddleware", // 此处为包名 { name: "modernjs", }, ]); }); ``` ## 框架写法 框架写法是一种使用框架分层结构来扩展 BFF 函数的方式。 和函数写法相比,框架写法虽然能够利用更多框架的结构,在复杂场景下让整个 BFF Server 更加清晰,但也相的更加复杂,需要关心更多框架层面的内容。 :::info 注 多数情况下,函数写法就能覆盖大多数 BFF 函数的定制需求。只有当你的项目服务端逻辑比较复杂,代码需要分层,或者需要使用更多框架的元素时(如 egg 插件),才需要使用框架写法。 ::: 框架写法中,所有的 BFF 函数都需要写在 `api/lambda/` 目录下,并且无法使用钩子文件 `_app.[tj]s`### Express Express 的框架写法支持可在 `api/app.[tj]s` 定义 API Server 的启动逻辑,执行应用的初始化工作,添加全局中间件,声明路由,甚至扩展原有框架等。 :::info 注 注意这里是 `app.[tj]s`,而不是函数写法中的钩子文件 `_app.[tj]s`::: BFF 函数定义的路由会在 `app.ts` 文件定义的路由之后注册,所以在这里你也可以拦截 BFF 函数定义的路由,进行预处理或是提前响应。 ```ts import express from "express"; const app = express(); app.put("/user", function (req, res) { res.send("Got a PUT request at /user"); }); app.use(async (req, res, next) => { console.info(`access url: ${req.url}`); next(); }); export default app; ``` ### Nest Nest 虽然有定制的启动器,但本质与 ExpressKoa 相同,所以 Modern.js 沿用了 Nest 定制启动器的默认入口:`api/main.ts`。 按照 Nest 官方生成器生成的项目结构,在 Modern.js 中使用 Nest 框架写法时,`api/` 目录结构为: ```markdown . ├── app.controller.ts ├── app.module.ts ├── app.service.ts ├── lambda │ └── hello.ts └── main.ts ``` 其中 `api/main.ts` 中的内容与 Nest 官方生成器生成模版有所不同,MWA 中支持了两种模式: 不包含内置 Module```ts title=api/main.ts import { defineCustom } from "@modern-js/plugin-nest"; import { NestFactory } from "@nestjs/core"; import { Module } from "@nestjs/common"; import { AppModule } from "./app.module"; export default NestFactory.create(AppModule); ``` 包含内置 Modlue```ts title=api/main.ts import { defineCustom } from "@modern-js/plugin-nest"; import { NestFactory } from "@nestjs/core"; import { Module } from "@nestjs/common"; import { AppModule } from "./app.module"; export default defineCustom(async (modules) => { @Module({ imports: [AppModule, ...modules], }) class MainModule {} return NestFactory.create(MainModule); }); ``` ### Koa Koa 框架写法与 Express 类似,支持在 `app.[tj]s` 定义 API Server 的启动逻辑,执行应用的初始化工作,添加全局中间件,声明路由,扩展原有框架等。 :::info 注 注意这里是 `app.[tj]s`,而不是函数写法中的钩子文件 `_app.[tj]s`::: BFF 函数定义的路由会在 `app.ts` 文件定义的路由之后注册,所以在这里你也可以拦截 BFF 函数定义的路由,进行预处理或是提前响应。 :::caution 注意 在框架写法下,当没有 `app.ts` 的时候,Modern.js 默认会添加 `koa-body`;当有 `app.ts` 时,如果开发者希望使用带有 BodyBFF 函数,需要确保 `koa-body` 中间件已经添加。 ::: ```ts import koa from "koa"; const app = new Koa(); app.put("/user", function (req, res) { res.send("Got a PUT request at /user"); }); app.use(async (ctx, next) => { console.info(`access url: ${ctx.url}`); await next(); }); export default app; ``` ### Egg #### 目录结构 Modern.js 在 egg 框架写法中添加的初始样板文件较为简单,但 Modern.js 允许开发者使用 egg 框架约定的几乎所有文件(如中间件,定时器,控制器等)。 ```bash title="egg 框架写法的初始结构" . ├── api/ │ ├── app/ │ │ └── middleware/ │ │ └── trace.ts │ ├── config/ │ │ └── config.default.ts │ └── lambda/ │ └── hello.ts ├── src/ ├── modern.config.js ├── package.json ├── pnpm-lock.yaml ├── tsconfig.json ``` #### Modern.js 的 一体化 BFF 函数 和 egg 中的 controller 有什么不同? - `lambda/` 下的 BFF 函数文件拥有定义路由和处理 API 逻辑两种功能,当你写一体化 BFF 函数的时候;就无需再写 controller 和 router。 - `lambda/` 目录通过文件的目录结构定义路由,Egg 通过在 `router.ts` 文件编写代码定义路由。 - `lambda/` 下可以写 BFF 函数,去处理 BFF 的逻辑;Egg 需要定义 class 声明 `controller`BFF 函数的样板代码更少。 - `lambda/` 下的文件可以使用 Modern.js 提供的 BFF API,如 `useContext`。 - `lambda/` 下的 BFF 函数可以和 `egg``router``controller` 等混合使用,但通常不建议这么做。 #### 调用 service 在 egg 的框架写法下,同 egg 框架的使用方式一致,开发者也可以通过 `ctx` 调用定义的 service。 假设有以下目录结构和文件: ```js {4-5} . ├── api/ │ ├── app/ │ │ └── service/ │ │ └── user.ts │ ├── config/ │ │ ├── config.default.ts │ └── lambda/ │ └── hello.ts ``` ```ts title=/app/service/user.ts import { Service } from "egg"; class UserService extends Service { async find(uid) { const user = await this.ctx.db.query( "select * from user where uid = ?", uid ); return user; } } export default UserService; ````lambda` 目录下定义的 BFF 函数中,可以使用 `useContext` API 获取 egg 请求上下文 ```ts title=/api/lambda/hello.ts import { useContext } from "@modern-js/runtime/server"; export const get = async () => { const ctx = useContext(); const userId = ctx.params.id; const user = await ctx.service.user.find(userId); return user; }; ``` #### 自定义启动逻辑 在 egg 的框架写法中,同样支持启动自定义,`api/app[tj]s``agent.[tj]s` 遵循 egg 的规范。 具体可见[启动自定义](https://eggjs.org/zh-cn/basics/app-start.html)。