<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手摸手實(shí)現(xiàn)基于 Koa 的微服務(wù)框架(實(shí)用!)

          共 8179字,需瀏覽 17分鐘

           ·

          2021-12-10 02:54

          作者: 米澤,抖音前端團(tuán)隊(duì)國際化工具核心開發(fā)者。

          Koa 官網(wǎng)的介紹是這樣介紹自己的:

          Koa 是一個(gè)新的 web 框架,由 Express 幕后的原班人馬打造, 致力于成為 web 應(yīng)用和 API 開發(fā)領(lǐng)域中的一個(gè)更小、更富有表現(xiàn)力、更健壯的基石。通過利用 async 函數(shù),Koa 幫你丟棄回調(diào)函數(shù),并有力地增強(qiáng)錯(cuò)誤處理。Koa 并沒有捆綁任何中間件, 而是提供了一套優(yōu)雅的方法,幫助您快速而愉快地編寫服務(wù)端應(yīng)用程序。

          從上面的描述中我們可以知道,Koa 是一種簡單好用的 Web 框架。它的特點(diǎn)是優(yōu)雅、簡潔、表達(dá)力強(qiáng)、自由度高。其本身代碼只有 1000 多行,所有功能都可以通過插件的方式擴(kuò)展,很符合 KISS 原則與 Unix 哲學(xué)。比較有名的 Node.js 業(yè)務(wù)框架 egg.js 就是是繼承自Koa。

          但 Koa 的劣勢也很明顯,就是太過自由,并沒有內(nèi)置過多的功能,比如常見的請求體解析、路由、模板渲染等功能都沒有,需要加載第三方中間件來實(shí)現(xiàn)。另外 Koa 只支持 Http 服務(wù),無法滿足業(yè)務(wù)方對于 RPC 服務(wù)的需求。

          本文將對基于 Koa 的微服務(wù) Node.js 框架設(shè)計(jì)思路做一些思考與探究,并且對實(shí)現(xiàn)方面做一些簡單補(bǔ)充。讓我們先從 Koa 的核心思想與原理開始。

          Koa的核心思想與最簡實(shí)現(xiàn)

          核心思想:AOP 面向切面編程

          AOP技術(shù)的誕生并不算晚,早在1990年開始,來自Xerox Palo Alto Research Lab(即PARC)的研究人員就對面向?qū)ο笏枷氲木窒扌赃M(jìn)行了分析。他們研究出了一種新的編程思想,借助這一思想或許可以通過減少代碼重復(fù)模塊從而幫助開發(fā)人員提高工作效率。隨著研究的逐漸深入,AOP也逐漸發(fā)展成一套完整的程序設(shè)計(jì)思想,各種應(yīng)用AOP的技術(shù)也應(yīng)運(yùn)而生。

          這個(gè)名詞聽起來很高大上,可能很多人都聽過,但是又沒有徹底搞懂,到底什么叫面向切面編程?這里先不解釋 AOP 的具體含義,而是舉個(gè)簡單的例子。

          • 農(nóng)場的水果包裝流水線一開始只有 采摘 - 清洗 - 貼標(biāo)簽


          • 為了提高銷量,想加上兩道工序 分類包裝 但又不能干擾原有的流程,同時(shí)如果沒增加收益可以隨時(shí)撤銷新增工序。

          • 最后在流水線的中的空隙插上兩個(gè)工人去處理,形成采摘 - 分類 - 清洗 - 包裝 - 貼標(biāo)簽 的新流程,而且工人可以隨時(shí)撤回。

          上面所說的每一道工序,都可以看作是一個(gè)切面。

          回到 AOP 的含義:就是在現(xiàn)有代碼程序中,在程序的生命周期橫向流程中,加入或減去一個(gè)或多個(gè)功能,使原本功能不受影響。

          核心原理:koa-compose + Node.js http

          Koa 可以被拆解為如下公式:

          Koa = Node.js原生http服務(wù) + 中間件引擎koa-compose

          通過把中間件用 Promise + async/await 的方式嵌套組合,Koa 實(shí)現(xiàn)了比 Express 的線性模型中間件多了一倍切面的洋蔥模型中間件,所以 Koa 能非常方便地實(shí)現(xiàn)類似響應(yīng)時(shí)間計(jì)算、日志打印、鑒權(quán)等等常用功能。

          下面舉一個(gè) Koa 官網(wǎng)的 demo 例子,可以看到這些功能的具體實(shí)現(xiàn)是多么的簡單:

          const?Koa?=?require('koa');
          const?app?=?new?Koa();

          //?x-response-time
          app.use(async?(ctx,?next)?=>?{
          ??const?start?=?Date.now();
          ??await?next();
          ??const?ms?=?Date.now()?-?start;
          ??ctx.set('X-Response-Time',?`${ms}ms`);
          });

          //?logger
          app.use(async?(ctx,?next)?=>?{
          ??const?start?=?Date.now();
          ??await?next();
          ??const?ms?=?Date.now()?-?start;
          ??console.log(`${ctx.method}?${ctx.url}?-?${ms}`);
          });
          //?response
          app.use(async?ctx?=>?{
          ??ctx.body?=?'Hello?World';
          });

          app.listen(3000);

          Node.js 原生 http 不用多說,下面著重講一下中間件引擎 koa-compose 的實(shí)現(xiàn),源碼非常精簡,核心代碼只有 30 行左右:

          function?compose(middleware)?{
          ??//?如果middleware不是數(shù)組,或者元素不是函數(shù),則拋異常
          ??if?(!Array.isArray(middleware))?{
          ????throw?new?TypeError('Middleware?stack?must?be?an?array!');
          ??}
          ??for?(const?fn?of?middleware)?{
          ????if?(typeof?fn?!==?'function')?{
          ??????throw?new?TypeError('Middleware?must?be?composed?of?functions!');
          ????}
          ??}
          ??//?返回一個(gè)閉包函數(shù)
          ??return?function?(context,?next)?{
          ????//?last?called?middleware?#

          ????let?index?=?-1;

          ????return?dispatch(0);

          ????function?dispatch(i)?{
          ??????if?(i?<=?index)?{
          ????????return?Promise.reject(new?Error('next()?called?multiple?times'));
          ??????}

          ??????index?=?i;

          ??????let?fn?=?middleware[i];

          ??????if?(i?===?middleware.length)?{
          ????????fn?=?next;
          ??????}

          ??????if?(!fn)?{
          ????????return?Promise.resolve();
          ??????}

          ??????try?{
          ????????//?將每一個(gè)?middleware?函數(shù)作為前一個(gè)函數(shù)的?next?參數(shù)

          ????????return?Promise.resolve(fn(context,?dispatch.bind(null,?i?+?1)));
          ??????}?catch?(err)?{
          ????????return?Promise.reject(err);
          ??????}
          ????}
          ??};
          }

          簡化掉判斷邏輯,compose執(zhí)行后就是類似下面這樣的結(jié)構(gòu):

          //?這樣就可能更好理解了。
          //?simpleKoaCompose
          const?[fn1,?fn2,?fn3]?=?stack;

          const?fnMiddleware?=?function?(context)?{
          ??return?Promise.resolve(
          ????fn1(context,?function?next()?{
          ??????return?Promise.resolve(
          ????????fn2(context,?function?next()?{
          ??????????return?Promise.resolve(
          ????????????fn3(context,?function?next()?{
          ??????????????return?Promise.resolve();
          ????????????})
          ??????????);
          ????????})
          ??????);
          ????})
          ??);
          };

          實(shí)際上 koa-compose 返回的是一個(gè) Promise,從中間件(傳入的數(shù)組)中取出第一個(gè)函數(shù),傳入context和第一個(gè)next函數(shù)來執(zhí)行。

          第一個(gè) next 函數(shù)也返回一個(gè) Promise,從中間件(傳入的數(shù)組)中取出第二個(gè)函數(shù),傳入context和第二個(gè)next函數(shù)來執(zhí)行。

          第二個(gè) next 函數(shù)也返回一個(gè) Promise,從中間件(傳入的數(shù)組)中取出第三個(gè)函數(shù),傳入context和第三個(gè)next函數(shù)來執(zhí)行。

          第三個(gè)...

          以此類推。最后一個(gè)中間件中如果調(diào)用了 next 函數(shù),則返回 Promise.resolve()。這樣就把所有中間件串聯(lián)起來了。類似棧的先進(jìn)后出,每個(gè)中間件都有兩個(gè)切面,這就是洋蔥模型的實(shí)現(xiàn)原理。

          Koa 最簡實(shí)現(xiàn)

          const?http?=?require('http');

          const?Emitter?=?require('events');

          const?compose?=?require('koa-compose');?//?上面的?compose

          /**
          ?*?通用上下文
          ?*/


          const?context?=?{
          ??_body:?null,

          ??get?body()?{
          ????return?this._body;
          ??},

          ??set?body(val)?{
          ????this._body?=?val;

          ????this.res.end(this._body);
          ??},
          };

          class?MiniKoa?extends?Emitter?{
          ??constructor()?{
          ????super();

          ????this.middleware?=?[];

          ????this.context?=?Object.create(context);
          ??}

          ??/**
          ????*?服務(wù)事件監(jiān)聽
          ????*?@param?{*}?args
          ??*/


          ??listen(...args)?{
          ????const?server?=?http.createServer(this.callback());

          ????return?server.listen(...args);
          ??}

          ??/**
          ????*?注冊使用中間件
          ????*?@param?{Function}?fn

          ??*/


          ??use(fn)?{
          ????this.middleware.push(fn);
          ??}

          ??/**
          ????*?中間件總回調(diào)方法
          ????*/


          ??callback()?{
          ????if?(this.listeners('error').length?===?0)?{
          ??????this.on('error',?this.onerror);
          ????}

          ????const?handleRequest?=?(req,?res)?=>?{
          ??????let?context?=?this.createContext(req,?res);
          ??????let?{?middleware?}?=?this;
          ??????//?執(zhí)行中間件
          ??????compose(middleware)(context).catch(err?=>?this.onerror(err));
          ????};

          ????return?handleRequest;
          ??}

          ??/**
          ???*?異常處理監(jiān)聽
          ???*?@param?{EndOfStreamError}?err
          ???*/

          ??onerror(err)?{
          ????console.log(err);
          ??}
          ??/**
          ????*?創(chuàng)建通用上下文
          ????*?@param?{Object}?req
          ????*?@param?{Object}?res
          ????*/

          ??createContext(req,?res)?{
          ????let?context?=?Object.create(this.context);
          ????context.req?=?req;
          ????context.res?=?res;
          ????return?context;
          ??}
          }

          /**
          ?*?測試一下
          ?*/

          const?app?=?new?MiniKoa();
          const?PORT?=?3001;
          app.use(async?ctx?=>?{
          ??ctx.body?=?'hello';
          });
          app.listen(PORT,?()?=>?{
          ??console.log(`started?at?port?${PORT}`);
          });

          基于 Koa 的微服務(wù) Node.js 框架設(shè)計(jì)

          然而,Koa只是一個(gè)HTTP框架,在實(shí)際的業(yè)務(wù)場景中,業(yè)務(wù)方除了要編寫HTTP服務(wù),還可能要編寫其他類型的服務(wù),比如 Thrift 服務(wù)、WebSocket 服務(wù)、消息隊(duì)列的 Consumer 服務(wù)等等。應(yīng)該如何設(shè)計(jì)這樣一個(gè)不僅支持HTTP,還支持其他服務(wù)類型的微服務(wù) Node.js 框架呢?我們先從這些服務(wù)的共性出發(fā)。

          設(shè)計(jì)思想

          HTTP、Thrift、WebSocket 等服務(wù)雖然應(yīng)用層協(xié)議不同,但歸根結(jié)底都是 C/S 結(jié)構(gòu)的軟件系統(tǒng),其工作流程都可以劃分為請求響應(yīng)兩個(gè)階段,如下圖所示:

          如果把整個(gè)客戶端與服務(wù)端之間的交互過程看成是一個(gè)完整流水線的話,那么請求響應(yīng)自然就可以作為整個(gè)請求過程中的兩個(gè)切面,因此 Koa 的洋蔥模型也同樣適用于除 HTTP 之外其他類型的服務(wù)。所以我們可以基于 Koa進(jìn)行封裝和改造,構(gòu)造一個(gè)通用的服務(wù)中間件處理模型,這樣我們就可以用 Koa 的形式來編寫任意類型的服務(wù)程序。

          框架的基本架構(gòu)如下圖所示:

          image.png

          簡單實(shí)現(xiàn)

          我們可以根據(jù)上述架構(gòu)圖做一個(gè)簡單的實(shí)現(xiàn)(基于 AbstractServer 構(gòu)建 HttpServer 與 ThriftServer ):

          某些方法的細(xì)節(jié)部分這里先不做展開,感興趣的同學(xué)可以自行查閱更多資料。

          AbstractServer

          import?compose?from?'koa-compose';
          import?http?from?'http';

          export?abstract?class?AbstractServer?extends?EventEmitter?{
          ????public?middlewares:?any[];
          ????public?context;
          ????public?request;
          ????public?response;

          ????/**
          ?????*?Initialize?a?new?application.
          ?????*
          ?????*?@constructor
          ?????*/

          ????constructor(options)?{
          ????????super();
          ????????this.middlewares?=?[];
          ????????this.context?=?Object.create(options.context);
          ????????this.request?=?Object.create(options.request);
          ????????this.response?=?Object.create(options.response);
          ????}
          ????/**
          ?????*?Listen?to?specific?port.
          ?????*/

          ????public?listen(...args)?{
          ????????const?server?=?this.createServer(this.callback());
          ????????return?server.listen(...args);
          ????}

          ????/**
          ?????*?Use?the?given?middleware?`fn`.
          ?????*
          ?????*?@param?fn?-?middleware
          ?????*/

          ????public?use(fn):?this?{
          ????????if?(typeof?fn?!==?'function')?{
          ????????????throw?new?Error('middleware?must?be?a?function!');
          ????????}
          ????????this.middlewares.push(fn);
          ????????return?this;
          ????}

          ????/**
          ?????*?Return?a?request?handler?callback.
          ?????*/

          ????public?callback()?{
          ????????const?fn?=?compose(this.middlewares);
          ????????return?(req,?res)?=>?{
          ????????????const?ctx?=?this.createContext(req,?res);
          ????????????return?this.handleRequest(ctx,?fn);
          ????????};
          ????}

          ????/**
          ?????*?Handle?request?in?callback.
          ?????*
          ?????*?@param?ctx
          ?????*?@param?fn
          ?????*/

          ????public?handleRequest(ctx,?fn):?Promise<void>?{
          ????????return?fn(ctx)
          ????????????.then(()?=>?this.handleResponse(ctx))
          ????????????.catch((err)?=>?ctx.onerror(err));
          ????}

          ????/**
          ?????*?Initialize?a?new?context.
          ?????*
          ?????*?@param?{Object}?req?-?request
          ?????*?@param?{Object}?res?-?response
          ?????*/

          ????public?createContext(
          ????????req,
          ????????res,
          ????)?{
          ????????const?context?=?Object.create(this.context);
          ????????const?request?=?Object.create(this.request);
          ????????const?response?=?Object.create(this.response);
          ????????context.app?=?this;
          ????????context.request?=?request;
          ????????context.response?=?response;
          ????????context.req?=?req;
          ????????context.res?=?res;
          ????????context.state?=?{};

          ????????request.app?=?this;
          ????????request.ctx?=?context;
          ????????request.req?=?req;
          ????????request.res?=?res;
          ????????request.response?=?response;

          ????????response.app?=?this;
          ????????response.ctx?=?context;
          ????????response.req?=?req;
          ????????response.res?=?res;
          ????????response.request?=?request;
          ????????return?context;
          ????}

          ????/**
          ?????*?Default?error?handler
          ?????*
          ?????*?@param?err?-?error
          ?????*/

          ????public?onerror(err:?Error):?void?{
          ????????const?msg?=?err.stack?||?err.toString();
          ????????console.error();
          ????????console.error(msg.replace(/^/gm,?'??'));
          ????????console.error();
          ????}

          ????/**
          ?????*?Create?server
          ?????*
          ?????*?@param?callback?-?server?request?callback
          ?????*/

          ????public?abstract?createServer(callback);
          ????/**
          ?????*?Handle?response?after?all?middlewares?have?been?executed
          ?????*
          ?????*?@param?ctx?-?context
          ?????*/

          ????public?abstract?handleResponse(ctx):?void;
          }

          HttpServer

          export?class?HttpServer?extends?AbstractServer?{
          ???/**
          ?????*?initialize?http?server
          ?????*?@param?options
          ?????*/

          ??constructor(options)?{
          ????//?more?detail...
          ????const?{?context,?request,?response?}?=?options;
          ????super({
          ??????context,
          ??????request,
          ??????response,
          ????});
          ??}
          ??/**
          ?????*?Handle?request.
          ?????*
          ?????*?@param?ctx?-?context
          ?????*?@param?fn?-?composed?middleware
          ?????*/

          ??handleRequest(ctx,?fn)?{
          ????//?more?detail...

          ????return?super.handleRequest(ctx,?fn);
          ??}

          ??/**
          ?????*?Create?context.
          ?????*
          ?????*?@param?req?-?raw?request
          ?????*?@param?res?-?raw?response
          ?????*/

          ??createContext(req,?res)?{
          ????const?context?=?super.createContext(req,?res);
          ????//?more?detail...
          ????return?context;
          ??}
          ??/**
          ?????*?Handle?response?after?all?middlewares?have?been?executed.
          ?????*
          ?????*?@param?ctx
          ?????*/

          ??handleResponse(ctx)?{
          ????let?{?body?}?=?ctx;
          ????const?{?res?}?=?ctx;
          ????const?code?=?ctx.status;
          ????//?more?detail...
          ????body?=?JSON.stringify(body);
          ????return?res.end(body);
          ??}

          ??/**
          ?????*?Error?handler.
          ?????*
          ?????*?@param?err
          ?????*/


          ??onerror(err)?{
          ????super.onerror(err);
          ??}

          ??/**
          ?????*?Create?a?http?server.
          ?????*
          ?????*?@param?callback?-?request?handler
          ?????*/

          ??createServer(callback,?options?)?{
          ????//?more?detail...

          ????return?http.createServer(callback)?as?any;
          ??}
          }

          總結(jié)

          本文對 Koa、基于 Koa 的微服務(wù) Node.js 框架在思想、原理、實(shí)現(xiàn)方面做了一些探討。

          Koa 的核心思想是 AOP,AOP 中切面的概念可以類比于流水線上可以自由增加或減少的“環(huán)節(jié)”,對于這樣有固定流程的“環(huán)節(jié)”,我們都可以把它們當(dāng)做AOP的切面,利用洋蔥模型的思想去處理。

          參考資料

          Koa設(shè)計(jì)模式:https://chenshenhai.github.io/koajs-design-note/

          最后



          如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

          2. 歡迎加我微信「qianyu443033099」拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          3. 關(guān)注公眾號「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。


          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了


          瀏覽 192
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  三级AV久久久 | 国产中文字幕在线 | 成人十八禁网站 | 尻尻穴视频 | 欧美激情亚洲无码 |