面向body编程的新框架Apie
发布于 8 年前 作者 i5ting 4510 次浏览 来自 分享

屏幕快照 2017-04-22 16.01.34.png

面向body编程的新框架apie

轮子还是要造的,万一好用呢?哈哈

面向body编程

最近要写一个非常好用的api-mock的软件,但是目前的所有mock实现都不太符合我的期望。

  • 简单
  • 可配置
  • 复用已有知识
  • 如果可以,也可以进行逻辑处理

基于以上几点,我不得不再次审视http协议和已有的这些Node.js Web 框架,它们合适么?答案肯定是不合适的,虽然express、koa已经很简单了,但上面的要点都无法完成。

那么基于约定呢?

我们知道整个http请求过程里,在Mock的时候,请求是你来做,而结果是Mock Server来做。所以在设计Mock Server的时候,核心是Response,而Request只是约束条件,如果req符合条件,才允许给res。

那么常见的写法是什么样的呢?

{
  "req": {
    "method": "get",
    "path": "/home"
  },
  "res": {
    "status": 200,
    "body": "Hello world!",
    "headers": {
      "Content-Type": "text/plain"
    }
  }
}

这样就基本满足了我们的描述式配置写法。大部分人都能按照这个写的,无论语义还是结构,都是不错的。我们来对比一下req和res里的属性,重合的几率有多大?常用的除了headers,基本没有太多了,那么我们是否能够将这些属性提到根节点呢?

{
  "method": "get",
  "path": "/home"
  "status": 200,
  "body": "Hello world!",
  "req": {
    "headers": {}
  },
  "res": {
    "headers": {
      "Content-Type": "text/plain"
    }
  }
}

这样是不是更加简单?再进行约定, 貌似除了body外,其他都是可选的

- method可选,默认为get
- path可选,默认以文件路径为路由,之前的mount-routes等就是这样实现,配置的path比文件路径优先级高
- status可选,默认200

所以body就成整个抽象的核心。那么body是哪些可能性呢?

  • body是字符串类型,直接返回字符串
  • body是字符串类型,且是存在的json文件,读取并返回json
  • body是plain old object,返回json
  • body是函数就允许用户自己写代码
  • body是数组,约定定为中间件数组

这样就可以涵盖绝大多数场景了。如果还有啥express-generator支持的功能

  • public静态Server(无须处理,指定目录即可)
  • res.render视图渲染,模板引擎集成(可能要配置,安装对应的模板引擎模块)
  • 中间件约定,配置(约定目录,自动挂载,按名字匹配)

Response.body Oriented Programming(ROP)

我将这种ROP编程命名为面向body编程

Apie

基于以上想法,设计了apie和apik框架。这里以Apie为例,进行用法说明。

Apie支持2种写法

  • .json:配置文件,mock接口最方便
  • .js:业务逻辑支持

准备

只需三步

$ git clone  https://github.com/apiejs/apie-demo.git
$ cd   apie-demo
$  npm start

接下你就专注于写你的api就好了

文件即路由

继承mount-routes,指定目录下的所有文件即对应路由。文件的名称即默认的请求path。可以无限目录级别的处理,只要是目录下的所有带有body属性的js和json都是路由。

比如我新建一个simple.js,

module.exports = {
    // "path": "/cnode",
    "body": "Hello world!"
}

此时

  • path 是 /simple
  • method 是 GET
  • body 是返回值字符串 "Hello world!"

如果想改为/cnode,只需要修改配置

module.exports = {
    "path": "/cnode",
    "body": "Hello world!"
}

是不是很简单?

区分一下

  • 文件名不支持复杂路由,比如具名路由,正则路由等
  • 配置文件的path支持express里的所有路由方式

Mock-Api

采用JSON作为配置文件,是最常见的编写Mock接口的方式

hello world

比如我新建一个sang.json,

{
    "body": {
        "json": "Hello Sang!"
    }
}

此时

  • path 是 /sang
  • method 是 GET
  • body 是返回值

body type

上面说了body支持5种类型,对于简单的mock-api说能用的大致有3种

  • body是字符串类型,直接返回字符串
  • body是字符串类型,且是存在的json文件,读取并返回json
  • body是plain old object,返回json

举例1: 返回字符串

module.exports = {
    "body": "Hello String"
}

举例2: 返回JSON

module.exports = {
    "body": {
        "json": "Hello world!"
    }
}

这种规则理解起来应该是非常容易理解的。

json file

举例3:根据json文件,返回JSON,由于此种较为特殊,所以单独列出来讲解。

比如jsonfile.js里配置如下

module.exports = {
    "path": "/jsonfile",
    "body": "demo.json"
}

此时

  • path 是 /sang
  • method 是 GET
  • body 是返回值 "demo.json"文件的内容,类型为json

demo.json

{
    "path": "demo.json",
    "demo": {
        "json": "Hello world!"
    }
}

这种jsonfile使用场景

  • 多个api复用同一个json文件
  • 返回的json非常复杂

业务逻辑支持

body是中间件函数

举例function.js

module.exports = {
    "body": function(req, res){
       res.json("Hello world!")
    }
}

body是express中间件函数,允许用户自己写代码,至于你想要啥逻辑,自己动手就好。

  • 1)针对req里的params、query、body、header、cookie等进行处理
  • 2)按需进行response响应

结合node的强大模块生态,在此处想做啥都可以,比如数据库连接,文件读写等,可以说你能想到的,都可以实现。

body是中间件数组

如果大家express路由比较熟悉,会知道路由上可以有n个中间件,n>1,这个的表现形式其实用数组更合适。

  • body是数组,约定定为中间件数组

这样就可以涵盖绝大多数场景了。

举例

module.exports = {
  "body": [function(req, res, next) {
    next()
  }, function(req, res){
    res.json("Hello world!")
  }]
}

是不是感觉和express一模一样?其实它就是换种写法而已,抽象一下就简单多了。

视图渲染

通过res.render视图渲染,模板引擎集成,安装对应的模板引擎模块,默认是pug

module.exports = {
    "body": function (req, res) {
        res.render('index', { title: 'Express' });
    }
}

剩下的就是模板引擎用法了,这里就不过多介绍了。

中间件约定

在写express的时候有一个习惯,建立middlewares目录专门存储中间件,很是方便。这里也一样,apie会自动把middlewares目录里的中间件挂在到app.middlewares对象上,继而我们就可以通过app.middlewares[key]的方式调用里。

目录

  ├── middlewares
       ├── a.js
       └── b.js

a.js

module.exports = function a (req, res, next) {
  console.log('a')
  next()
}

用法,在配置里增加middlewares属性

单个中间件

module.exports = {
    "path": "/middleware",
    "middlewares": 'a',
    "body": {
        "json": "Hello middleware world!"
    }
}

多个中间件

module.exports = {
    "path": "/middlewares",
    "middlewares": ['a', 'b'],
    "body": {
        "json": "Hello middlewares world!"
    }
}

复用所有express插件

比如session、jwt、redis、ratelimit、数据库等等

集成方式

  • 1)放到middlewares里,自己包一下,主要是针对参数。然后配置的熟悉里配置依赖
  • 2)放到body是function或数组的依赖里

静态Server

按照约定,假定app是根目录,那么app/public就是静态server对应的文件夹。其他用法和express了的一样。

调用方式

安装依赖

$ npm install --save apie

方式1,直接启动,自嗨模式

var apie = require('apie')

apie('./routes', 3000);

返回express的app实例,可以像express一样启动,也可以作为express的子项目提供引用。

var apie = require(apie).app;

// return an express app
var app = apie('./routes');

// start server
app.listen(3000)

Project Directory

$ tree .
.
├── LICENSE
├── README.md
├── app
│   ├── all.js
│   ├── api
│   │   ├── index.js
│   │   └── user.js
│   ├── demo.json
│   ├── home.js
│   ├── json.js
│   ├── jsonfile.js
│   ├── middleware.js
│   ├── middlewares
│   │   ├── a.js
│   │   └── b.js
│   ├── middlewares.js
│   ├── movies.js
│   ├── post.js
│   ├── public
│   │   ├── images
│   │   ├── javascripts
│   │   └── stylesheets
│   ├── sang.json
│   ├── simple.js
│   ├── users.js
│   ├── view.js
│   └── views
│       ├── error.pug
│       ├── index.pug
│       └── layout.pug
├── app.js
└── package.json

这是一个简单的demo,为了和express-generator生产的一致。

性能测试

以同样简单的json api和view作为测试结果,性能比express-generator生成的低了不到5%,属于正常范围,是可以接受的。

总结

apie使用场景

  • 对于mock数据尤其友好、简单,可配置
  • 支持express/koa支持的所有的业务逻辑处理
  • 对express/koa的插件、经验完美继承

目前欠缺的是对app生命周期的封装,如果加上,基本就完美了。

如果那apie和restify、hapi等比较,明显apie更简单,但插件机制上,没有实现,算一个小缺憾吧。

总之,这是一次尝试行的实践,就目前的结果来看,还是非常值得投入的。apie写了不到半天,apik写了不到1个小时,目前代码还比较粗糙,大家可以先看用法和设计。

6 回复

就想知道狼叔是如何取发现和寻找项目的

如何实现由客户端自适应数据格式 ,同一个path 比如桌面浏览器 输出模板 ,ajax请求输出json api接口输出xml/json

@zy445566 需求呗,然后找github,有就用,没有就自己写

@yakczh 很简单

根据useragent判断来源,然后再body函数里处理,req可以取到ua的

module.exports = {
    "body": function(req, res){
       res.json("Hello world!")
    }
}

面向 body 编程?

回到顶部