Kalec的网络日志

漂浮的灵魂

HOME 首页 设计耦合度更低的koa路由

设计耦合度更低的koa路由

技术
2018-04-20 17:06:00

平时用koa设计一个路由会怎么做呢?
koa已经帮我们封装好了路由的大部分实现,而且用了async和await已经把异步回调进行了极大的简化,已经让写路由变得如此轻松。

import Router from 'koa-router' import mongoose from 'mongoose' const router = new Router() router.get('/movies/all', async (ctx, next) => { const Movie = mongoose.model('Movie') try{ const movies = await Movie.find().sort({ 'meta.createdAt' : -1 }) ctx.body = { movies } } catch (e) { console.log(e); } })

这里面还进行了数据库的查询,然后返回数据api,看着还好,当中的数据处理什么的并不是很多,当以后要修改的时候感觉也挺好哒。对,毕竟自己开发的嘛?
这么说就这样好了,给你们看看前面写的像屎一样的博客后台的路由大概就会害怕了。

屏幕快照 20180420 下午9.29.14.png
想想看,一个博客路由这能有多少逻辑,一个路由模块居然就1200+行了。我也蛮不服的,但是事实就是这样,我把查询数据库,排序,数据处理等逻辑全写在这里面了,写的时候还蛮爽的,直到前面遇到几个问题之后我才觉得并没有那么简单,首先容易引起混乱,自己去找都不方便,接着就是改bug的时候简直等于把整个书签路由重构了。想想都操蛋诶!谁叫当时写的时候那么随意呢。

那有没有什么解决办法呢?不仅方便扩展,而且还可以满足开闭原则呢?答案当然不用说,设计模式登场。

因为正好js的装饰器是新的标准,并且大体上是照搬python的,那咱们就用这个装饰器模式来试试看。
首先我们来考虑下我们的需求,一般我们设计个后台api的时候,首先先是路由,有哪些路由,这些路由下又有些什么子路由,考虑好后我们就可以来设计啦。
既然用装饰器,我们可以先设计一个主路由的控制器来修饰这个类,子路由设计相应的控制器来修饰里面的方法,这样层次结构就很鲜明了。例如

import { controller, post } from '../lib/decorator' import { checkPassword } from '../service/admin' @controller('/api/v0/user') export class User { @post('/') async loadControl (ctx, next) { const { email, password } = ctx.request.body const matchData = await checkPassword(email, password) if(matchData.data) { ctx.body = { succsee: true } } else { ctx.body = { success: false, errcode: '用户名或者密码不正确' } } } }

其中@get修饰,则说明以get方式获得,@post则以post方式提交。紧接着就看装饰器如何实现了。
主路由的控制器,主要作用就是取得主路由,并且把它放到原型上作为一个静态的属性供由类里面的进行调用。

export const controller = path => target => (target.prototype[symbolPrefix] = path)

对方法修饰的装饰器就稍微麻烦一点点,这里我们得进行区分网络请求的方式,然后对不同的请求方式进行配置的存储,存储这里我们用到es6的map数据结构。例如

const normalizePath = path => path.startsWith('/') ? path : `/${path}` const router = conf => (target, key, descriptor) => { conf.path = normalizePath(conf.path) console.log(...conf) routerMap.set({ target: target, ...conf }, target[key]) } export const get = path => router({ method: 'get', path }) export const post = path => router({ method: 'post', path }) export const put = path => router({ method: 'put', path }) //...

这里我们把路由配置项都存起来,里面用请求方式和子路由内容为键,类里面的方法提取出来为值。
接着我们要做的就是初始化路由,我们新建一个路由类,里面建一个初始化路由的方法。这里咱们把每一个主路由的类进行加载,,因为装饰器对类的行为在编译时候就已经发生,而不在运行的时候。所以我们直接加载那几个类,就能拿到它们的方法以及装饰器发生完后存储的路由配置项。对配置项进行遍历,我们在初始化这里就能轻松的进行每一个路由拼接,加载,并开始侦听。

export class Route { constructor(app, apiPath){ this.app = app this.apiPath = apiPath this.router = new Router() } init(){ glob.sync(resolve(this.apiPath, './**/*.js')).forEach(require) for(let [conf, controller] of routerMap) { const controllers = isArray(controller) let prefixPath = conf.target[symbolPrefix] if(prefixPath) prefixPath = normalizePath(prefixPath) const routerPath = prefixPath + conf.path this.router[conf.method](routerPath, ...controllers) } this.app.use(this.router.routes()) .use(this.router.allowedMethods()) } }

这么一搞,猛地就能发现,这个模块可以直接不需要修改就可以在大部分场景中直接运用,而且路由模块进行了拆分。耦合度很低,可以随心所欲的加路由,对路由修改,对路由业务逻辑修改。

哈哈,没想到吧!装饰器居然这么好用,,(几乎和py的一毛一样)!

后面呢,好奇心驱使,估计会去折腾一段时间的函数式编程,那玩意和数学走的就更近了,貌似又是一段漫长的停笔期。好啦这次就水到这里!咱下次再见!

留言

  • 暂时没有留言,来留下你的留言吧!

评论

看不清?换一个