1.0.7-babel • Published 6 months ago

koa-lambda-middleware v1.0.7-babel

Weekly downloads
-
License
MIT
Repository
-
Last release
6 months ago

koa-lambda-middleware

一接口一函数,传一参,反一世界。

koa-lambda中间件提供lambda方式进行接口开发,保持简单,使用函数式方式,约定统一仅使用POST请求,去TMD的RESTful,降低心智负担才是生产效率。

虽然koa原生的中间件为同为函数式,但是其实函数的传参可以进一步优化,并抽象到前端接口调用的传参,并做统一。而且return返回值也没有充分利用上,因此koa-lambda中间件弥补这些不足带来了这些便利。

开发要高效,不仅需要代码和配置量少,代码逻辑清晰,易于维护,而且框架/库提供的规范需要符合直觉,约定要优于配置。

安装

npm i -s koa-lambda-middleware

初始化koaLambda中间件

koaLambda中间件依赖 body parsers中间件,这里以koa-body为例(其实只要解析为ctx.request.body就行)

const Koa = require('koa');
const { koaBody } = require('koa-body');
const koaLambda = require('koa-lambda-middleware');
const app = new Koa();


app.use(koaBody())
    .use(koaLambda({}, app)); // 初始化koaLambda中间件

app.listen(3333);

koaLambda为初始化方法,传参为(<配置>,<当前koa app实例>) 返回中间件。

配置说明(默认值)为

{ 
  handlerAopDefault: "",        //函数逻辑在next前还是在next后,为空无next   值有:'after' | 'before' | ''
  root: "",                     //http请求访问路径头,如果配置baz,接口访问都统一 http://localhost/baz/**下
  dirname: __dirname + "/src",  //源码目录,递归获取所有src下的模块js文件
  filter: /(.*)\.js$/,          //过滤器  可以是 正则或函数
  debug: true,                  //是否开启debug模式,开启后打印注释、响应时间、错误信息
}



// koaLambda({}, app) 相当于默认配置:
koaLambda({
  handlerAopDefault: "",
  root: "",
  dirname: __dirname + "/src",
  filter: /(.*)\.js$/, 
  debug: true,
}, app)

路由

在src目录下创建一个js模块文件hello.js。定义一个hello方法,这里后面我们统一称为Lambda函数,函数的路径为/hello/hello

module.exports = {
  hello(){
    return "hello world!"
  }
};

对应访问地址为:http://localhost/hello/hello 路由地址:文件名/函数路径

不过没有特定配置(默认)情况下,需要httpPOST才能正常请求和响应,这是约束或者说更是约定

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 11
Date: Mon, 28 Feb 2022 03:20:46 GMT
Connection: close

hello world

允许出现这种嵌套方式定义Lambda函数。但是注意不要在目录里面出现同路径文件名,请保持路由唯一

module.exports = {
  a:{
    b:{
      c(){
         return {path:'a.b.c'}
      }
    }
  }
};

接口地址为:http://localhost/path/a/b/c

传参

传参上面的约定,前端以application/json(取决于bodyparsers中间件)传args数组为参数数组

POST http://localhost:3333/a/foo HTTP/1.1
content-type: application/json

{
    "args":[
        2,
        3
    ]
}

创建一个a.js文件为例,返回传参之和,这里Lambda函数的参数a, b分别对应接口请求传入的args数组

module.exports = {
  foo(a, b){ // 这里a, b分别为2, 3
    return {sum: a + b}
  }
};

响应返回(即为函数return值)如下:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 9
Date: Mon, 28 Feb 2022 06:41:51 GMT
Connection: close

{
  "sum": 5
}

Hook

在Lambda函数中使用了类似React hook的方式,通过使用 useContext 获取ctx, 使用 useNext 获取next。可以说是一次曲线救国,解决纯函数没有 ctx 和 next。

const { useContext, useNext } = require('koa-lambda-middleware');

module.exports = {
  async foo(){
    let ctx = useContext();
    let next = useNext();
    
    ctx.body = 'ok!';
    
    await next();
  }
};

这里需要注意的是如果next后置同时需要返回,方法的return就不应该使用了,应该直接去修改ctx.body。

Lambda函数并没有减少koa中间件功能。他们之间完全可以替代和相互转换。

另外,基于这种Hook方式,在项目内可以自行封装一些常用Hook,诸如:useDBuseOrmuseModeluseSessionuseOssuseRedisuseCookieuseValidator等。

高阶和转化

拥抱koa的丰富的中间件生态,并保持简单通用,不必自定义另外的规范,你的项目中应该仅需要Lambda和中间件,并且你可以利用好它们之间的转化。

const { middleware, lambda } = require('koa-lambda-middleware');

// 注:下面例子有些乱,总之属性上挂的是lambda函数

module.exports = {
  // 单个中间件 用中间件来当作lambda
  foo: middleware(async(ctx, next)=>{
    ctx.body = "hello"
    await next()
  }),
  
  // lambda 转 middleware 再转 lambda  套娃😂
  foo2: middleware(lambda(async(a, b)=>{
    return a + b
  })),
   
 // 多个中间件, 数组即可,会合并为一个中间件,内部运作也是一个洋葱模型
  bar: middleware([
    async (ctx, next)=>{
      //todo something
      await next()
    },
    async (ctx, next)=>{
      ctx.body = "hello"
      await next()
    },
    async (ctx, next)=>{
      //todo something
      await next()
    },
  ]),
  
  // 可以套娃 lambda转换为中间件
  baz: middleware([
    async (ctx, next)=>{
      //todo something
      await next()
    },
    lambda(async (a, b)=>{
      return a + b
    }),
    async (ctx, next)=>{
      //todo something
      await next()
    },
    
    //嵌套
    middleware([
      async (ctx, next)=>{
        await next()
      }
      //...
    ])
    
  ])
};

注意:middleware方法的数组内中间件同样遵循 洋葱模型

自定义传参规则

lambda的参数默认约定是ctx.request.body.args数组作为参数,如果要自定义可以对koaLambda.requestParams方法进行重新定义。

const Koa = require('koa');
const koaBody = require('koa-body');
const koaLambda = require('koa-lambda-middleware');
const app = new Koa();


//自定义参数逻辑
koaLambda.requestParams = function(ctx, next){
  //处理获取参数,将参数以数组方式返回
  return [a, b, c...];
};

app.use(koaBody())
    .use(koaLambda({}, app));

app.listen(3333);

其它请求方式

虽然约定了统一使用POST请求,如果项目有这种特殊需求可以对lambda函数的method属性进行设置

module.exports = {
  foo(a, b){
    return a + b
  }
};
//修改foo函数为get方式请求
module.exports.foo.method = 'get';

注意:如果要使用get请求,前端请求需要将参数放在query中,而不是body中。?&args=1&args=2 这样上面的例子参数a、b 就分别为1、2

前端封装示例简易版

这个例子约定使用fetch请求,当然也可以使用axios等请求库去封装实现请求。

const host = `http://localhost:3000`;

// https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
export const fn = async (url:string, ...params:any[]) => {
  let apiUrl = `${host}${url}`;
  try{
    let response = await fetch(apiUrl, {
      mode: 'cors',
      method: 'POST', //这里只考虑了POST请求
      credentials: "include",
      headers: {
        'Content-Type': 'application/json'
      },
      body:JSON.stringify({
        //args是这里默认约定的字段!!
        //它决定了前端传参的方式,
        //如果自定义传参规则,需要修改requestParams方法
        args:[...params]   
      })
    });
    let data = await response.json();
    console.log(`response from:${url}:`, data)
    return data;
  }catch(e){
    console.error(`请求${apiUrl}发生错误`,e)
    throw e; 
  }
};


/**
调用方法:

import {fn} from "./fn";

// 用户列表
export const userList = async function(params = {}){
    return fn('/api/user/list', params);
}

*/

前端 client.js 调用示例

参考文件static/client.js,它是对前面简易版调用封装。 client方法生成的对象具备动态响应式路径。

约定:invoke对象调用链路就是接口访问地址路径

import client from './client.js'

// 用client方法返回的invoke对象是一个动态路径的对象
// 动态路径对象说明:
// 简单理解为:invoke对象下的属性有任意多的(动态)属性,你可以随意执行诸如:
// invoke.a(); invoke.b()... invoke.z()...
// 甚至 invoke.a 其实也是动态路径对象,所以可以继续链式调用属性,
// 比如:invoke.a.b.c.d.e.f()   任意路径,但这个动态路径是有意义的
// 只需和url路径和后端的路由都保持一致,你就可以不关心接口的url问题

let invoke = client({
    host: 'http://localhost:3333',
    root: '', //根路径 理论上应该与后端koaLambda配置的root一致
});


let foo2 = await invoke.test.foo2(1, 2);
// 调用接口地址/test/foo2 路径与函数 invoke.test.foo2 路径对应
console.log("/test/foo2:", foo2);


let gg = await invoke.test.gg(12, 2);
// 调用接口地址/test/gg
console.log("/test/gg:", gg);


let bar = await invoke.a.b.c.bar();
// 调用接口地址/a/b/c/bar
console.log("/a/b/c/bar:", bar);

特性:

  1. 约定统一默认使用POST请求,约定content-type: application/json
  2. 文件目录即对应接口路径
  3. 约定使用args数组作为函数参数,前后端调用一致性
  4. 保持koa洋葱模型,lambda既是函数也是中间件
  5. hook方式获取ctx 和 next,和koa中间件可以互替换
  6. 前端(client.js)搭配,使用动态路径对象访问接口(前端调用链、接口url路径、后端函数路由)三者统一,且无需额外配置