乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      前端如何正確使用中間件?

       漢無為 2020-08-31


      文末福利:下載《大促背后的前端核心業(yè)務實踐》電子書。


      一  先簡單講講中間件

      const compose = (middlewares) => { const reduce = (pre, cur) => { if (pre) { return (ctx) => cur(ctx, pre) } else { return (ctx) => cur(ctx, () => ctx) } } return [...middlewares].reverse().reduce(reduce, null);}

      這是一段非常簡潔的中間件代碼,通過傳入的類似這樣的函數(shù)的列表:

      const middlware = async (ctx, next) => {  /**   * do something to modify ctx   */  if (/* let next run */true) {    await next(ctx)  }  /**   * do something to modify ctx   */}

      得到一個新的函數(shù),這個函數(shù)的執(zhí)行,會讓這些中間件逐個處理并且每個中間件可以決定:

      • 在下個中間件執(zhí)行之前做些什么?

      • 是否讓下個中間件執(zhí)行?

      • 在下個中間件執(zhí)行之后做些什么?


      現(xiàn)在的中間件都是使用的洋蔥模型,洋蔥模型的大致示意圖是這樣的:


      按照這張圖,中間件的執(zhí)行順序是:

      middleware1 -> middleware2 -> middleware3 -> middleware2 -> middleware1

      處理順序是先從外到內(nèi),再從內(nèi)到外,這就是中間件的洋蔥模型。

      在中間件的應用上,開發(fā)者可以將統(tǒng)一邏輯做成一個中間件,這樣就能在其他地方復用這個邏輯。我覺得這其實是中間件這種模式的初心吧,好,那我們先把這個初心放一放。

      但實際上這個模式就是一個空殼,通過不同的中間件,就可以實現(xiàn)各種自定義的邏輯。比如:

      const handler = compose([(ctx, next) => {
      if (ctx.question === 'hello') {
      ctx.answer = 'hello';
      return
      }
      if (next) [
      next(ctx)
      ]
      }, (ctx, next) => {
      if (/age/.test(ctx.question)) {
      ctx.answer = 'i am 5 yours old';
      return
      }
      if (next) [
      next(ctx)
      ]
      }])
      const ctx = { question: 'hello' };
      handler(ctx)
      console.log(ctx.answer) // log hello
      ctx.question = 'how about your age?'
      handler(ctx)
      console.log(ctx.answer) // log i am 5 yours old

      這樣看起來我們甚至可以去實現(xiàn)一個機器人,把中間件這么拿來用,相當于是把中間件作為一個 if 語句展開了,通過不同的中間件對ctx的劫持來分離邏輯,看起來好像也不錯?

      得益于中間件的靈活性,每個中間件可以實現(xiàn):1)實現(xiàn)獨立的某個邏輯;2)控制后續(xù)的流程是否執(zhí)行。

      二  聊聊幾個栗子

      今年有參與做個小程序的Bridge,先簡單的介紹一下Bridge的功能。

      • 從支付寶小程序的視角來抹平其他小程序的JSAPI。

      • Bridge擁有擴展能力,能夠擴展JSAPI。


      看到“擴展能力”,熟練的同學應該就知道我可以切入正題了。

      Bridge現(xiàn)在的設計采用插件的形式來注入一系列API,每個插件都有插件名、API名、中間件三個屬性,注入Bridge后,Bridge會將相同API名的插件整合在一起,讓這個API的實現(xiàn)指向這些插件帶有的中間件的 compose ,用這種方式來實現(xiàn)自定義API。


      這種方式其實看起來是非常美妙的,因為所有的API都可以通過插件的形式注入到Bridge中,可以很靈活地擴展API。

      眾所周知,有得必有失。這種模式其實有自己的缺點,具體的缺點我們可以從“面向開發(fā)者”和“面向使用者”兩方面來整理,面向開發(fā)者指的是面向寫插件(也就是寫中間件)的開發(fā)者,面向使用者(用戶)指的是最終使用Bridge的開發(fā)者。

      1  面向開發(fā)者

      API的不確定性

      多個中間件注冊在同一個API上面,開發(fā)者自己的API是否能夠運行正常有的時候是依賴上下文的,而零散的中間件被載入Bridge,對于上下文的修改是未知的,因此會對API的執(zhí)行帶來很多不確定性。

      從洋蔥模型的圖上面,我們可以發(fā)現(xiàn),內(nèi)層往往會受外部的影響,當然在回流的時候,外部中間件也會受內(nèi)部中間件的影響,在開發(fā)中間件的時候,我們需要考慮自己的依賴,在已知依賴沒有問題的情況下去做開發(fā),才會比較穩(wěn)妥,但是當前Bridge這種散裝載入Plugin的方式,讓依賴關系沒有辦法穩(wěn)定的描述。

      API的維護成本高

      由于有多個插件注冊到單個API上,維護某個API的情況下就會有比較高的成本,就有點像是現(xiàn)在服務端排查問題的情況了,多個插件的情況下最差情況可能要逐個開發(fā)者去做排查,最終才能分鍋,雖然實際情況可能沒有這么糟糕,但還是要考慮一下最差的情況。

      那么為什么服務端這種架構是合理的呢,因為服務端的微服務架構確實能夠將多個業(yè)務邏輯拆分來解耦比較復雜的邏輯,但是Bridge這里只是想要實現(xiàn)某個API的實現(xiàn),也很明顯的發(fā)現(xiàn)實際在使用過程中,基本都采用了單插件的注冊方式。所以感覺用中間件來實現(xiàn)某個API,有點過渡設計了,反而造成了維護成本的提高。

      2  面向使用者

      面向使用者其實要分為兩種不同的場景:直接使用插件和通過preset來使用插件的集成。

      3  直接使用插件


      這種模式下,使用者要自己去引用插件,通過引用一系列插件來獲得一個可以正常使用的API,可是使用者往往期望的是能夠開箱即用,也就是說拿到這個Bridge,看一下文檔,就能夠調(diào)用某個API了,如今需要Bridge的使用者通過自己注冊一個Plugin這樣的東西來獲得一個可用的API,顯然是不合理的,不合理的地方主要體現(xiàn)在:

      API難理解

      Bridge使用者原本只需要理解一下Bridge的文檔就能夠輕松使用API,現(xiàn)在需要理解plugin的運作機制以及如果有若干個插件的話,還要理解插件單獨的運作和相互運作的實現(xiàn)。這些都很難讓一個Bridge使用者接受,對于業(yè)務開發(fā)來講,成本變高了。

      問題排查難度上升

      這點和之前提到的使用中間件這種方式會造成API的邏輯不連貫的情況是類似的,Bridge在使用API的時候如果發(fā)現(xiàn)有問題,那么排查問題的時候就會因為有多個Plugin實現(xiàn)而增加難度,總的來說他還是需要簡單的去理解每個插件基本實現(xiàn)和插件間的運作機制,對于業(yè)務開發(fā)來講,成本較高。

      4  通過Preset來使用插件的集成

      由于上述Bridge使用者直接使用Bridge的問題,其實通過preset的封裝可以解決一部分的痛點,而Bridge的preset的概念就是,通過編寫一個preset,這個preset去維護一個API和多個插件的關系,然后給到用戶的是一個集成好的Bridge,上述的兩個問題都可以被解決。


      這個模式看起來形式上就是之前的Bridge用戶選了一個“最懂插件的人”來做他們的替身,做了之前的那個User的角色,讓這個人來理解所有的Plugin,并維護這些API,這個'最懂'趨向極限,基本就等于開發(fā)Plugin的人了,那么饒了這么大一圈,做的這么靈活,最后維護插件的人是同一個人,也是這個人對外輸出API,那么這個東西真的有復雜到要這么拆分么。就我個人來講覺得還是直接簡單明了的的實現(xiàn)一個API來的方便。那是中間件這種模式辣雞嗎?

      5  抬走,我們來看下一個

      除了Bridge,老生常談的還有類似Fetch這樣的基礎庫,F(xiàn)etch是另一波同學做的了,但是我也是小撇了幾眼代碼,發(fā)現(xiàn)居然也用了中間件來做,正好可以看看他們在設計API的時候使用中間件的合理性。先說說Fetch為啥走了這條路吧,看看訴求:

      因為實在是有太多種不同的請求類型了,因此想實現(xiàn)在相同的入?yún)⑾拢ㄟ^adaptor參數(shù)來區(qū)分最終走怎樣的請求邏輯。

      因此Fetch在設計的時候,是這么使用中間件的:

      fetch.use(commonMiddleware)fetch.use('adaptor-xxx', [middleware]) // 比如adaptor-jsonfetch({ ...requestConfig, adaotpr: 'adaptor-xxx' })


      Fetch的中間件使用會相對合理一點,通過利用中間件的特性,對外輸出了相同的出入?yún)ⅲ俳柚煌闹虚g件對請求的過程做流式處理。

      但實際的使用過程中,也要很多同學反饋,有類似Bridge的使用問題。

      6  調(diào)用過程排查困難

      和Bridge類似,業(yè)務在使用過程中如果遇到問題,排查難度會比較高,首先業(yè)務開發(fā)同學的理解能力就很難了,因為要同時理解這套中間件+每個中間件的實現(xiàn)原理,而adaptor開發(fā)同學也比較難排查問題,首先他需要知道業(yè)務開發(fā)同學本地是如何使用這些適配器的,在知道了之后再零散的逐個插件去排查,相比于直接看某個類型的請求的實現(xiàn),難度會較高。

      三  引出觀點

      那么回頭看看這兩個Bridge和Fetch究竟有必要使用中間件么,有沒有更好的選擇。

      先考慮假如我們不使用中間件來做,是不是現(xiàn)在的困境都會不存在了,就比如:

      fetch.rpc = () => {}
      fetch.mtop = () => {}
      fetch.json = () => {}

      這樣實現(xiàn)不同類型的請求,每個請求的實現(xiàn)就會比較直觀的收斂在具體的函數(shù)中,隨之帶來的應該有如下的問題:

      不同請求實現(xiàn)之間的共享邏輯會不那么直觀,說白了就是將中間件前置后置那堆東西拿放到各自的實現(xiàn)中,哪怕是抽了公共函數(shù)然后再放到各自函數(shù)的實現(xiàn)中,這些共享邏輯都不直觀,而中間件那種共享邏輯的處理,可以減少一定的維護成本。

      那么會杠的同學就要開始問了:剛才你說多個中間件會加大維護的成本,現(xiàn)在又說共享的邏輯做成中間件能夠減少維護成本,你這前后矛盾啊!

      這波流程Q的不錯。

      那終于,要在這里拋一個觀點:

      中間件的這種模式,應該作為某個函數(shù)的裝飾者模式來使用。

      那么既然提到裝飾者模式,我們可以引用一本《維基百科》中的描述:

      the decorator pattern is a design pattern) that allows behavior to be added to an individual object), dynamically, without affecting the behavior of other objects from the same class).

      裝飾者模式是一個可以在不影響其他相同類的對象的情況下,動態(tài)修改某個對象行為的設計模式。

      其實這段描述的體感不是很強,因為其實中間件本身已經(jīng)不是一個對象了,而維基百科中的設計模式針對面向對象的語言做了描述。

      為了更有體感一點,附上一張《Head First設計模式》中的一圖:


      可以發(fā)現(xiàn)幾點:

      • 裝飾器和我們需要擴展的Class都是實現(xiàn)了同一個接口。


      • 裝飾器是通過接收一個Component對象來運作的。


      看到上面這兩點就會發(fā)現(xiàn)其實裝飾器模式和中間件的概念是大致相同的,只不過在Javascript中,通過一個compose的函數(shù)將幾個毫不相干的函數(shù)串了起來,但最終的模式是和這個裝飾者模式基本一致的。

      另外《Head First設計模式》中還有一張圖:


      這是他舉的咖啡計算價格的例子,看到這張圖不是特別眼熟么,這和我們最開始說的洋蔥模型非常相近,這也再一次證明了其實我們用的“中間件設計模式”其實就是“裝飾者模式”。

      那么聊了一下裝飾者模式,其實是為了說明我之前闡述的“中間件的這種模式,應該作為某個函數(shù)的裝飾者模式來使用”的觀點,因為裝飾器本身是為了解決繼承帶來的類的數(shù)量爆炸的問題的,而使用場景正如同它的名字一般,是有裝飾者和被裝飾者的區(qū)分的,盡管裝飾者最終也能成為一個被裝飾者,就如同例子中,計算咖啡的價格,裝飾者可以根據(jù)加奶或者加奶泡等等來計算收費,但是其實著這個場景下,去做對加奶的裝飾,就沒什么意義了,也很難懂。反推我覺得中間件這種模式,亦是如此。

      四  回應

      通過如上的分析,我們得知,我們在運用中間件的時候,起碼要有一個主要的函數(shù),而其他的中間件,都是用于裝飾使用。

      就比如我們在使用Koa做Node開發(fā)的時候,常常把業(yè)務邏輯放到某個中間件中,其他的都是一些攔截或者預處理的中間件,在egg中主要的業(yè)務邏輯被做成了一個controller,當然他最后肯定還是一個中間件,這是一種API的美化,非常科學。

      再比如我們在使用redux的時候,中間件往往都是做一些簡單的預處理或者action監(jiān)聽等等,當然也有另類的做法,比如redux-saga整個將邏輯接管掉的,這塊另說,我們這次先只聊常規(guī)用法。

      那回過頭來,想比如Bridge這類如何做修改呢?

      我覺得Bridge底層使用中間件來做API的處理流完全沒有問題,但造成現(xiàn)在這樣的問題主要是他的API,就如同egg做了koa的API的美化一般,Bridge也應該在API的設計上美化一下,限制二次開發(fā)者的腦洞,API不是越自由就越好,有句話說的好“你在召喚多強大的自由,就是在召喚多強大的奴役”。

      那么我們應該如何限制API呢?

      依照之前闡述過的說法“中間件的這種模式,應該作為某個函數(shù)的裝飾者模式來使用”,因此,首先要有一個顯式申明的主函數(shù),這塊我們的API應該如下設計:

      bridge.API('APINAME', handler)// 或者更加直接的bridge.APINAME = handler

      這樣一來,開發(fā)者在查找API實現(xiàn)的時候,就能夠比較明確的找到這塊的實現(xiàn),而最底層Bridge還是會吧這個handler丟到一個中間件中去做處理,這樣就能做到對這個handler的裝飾。

      在這個的基礎上,再設計一個能夠支持中間件的API:

      bridge.use(middleware) // 對所有的API生效bridge.use('APINAME', middleware) // 對某個API生效

      再回顧一下之前列出來的問題:

      API的不確定性

      API的實現(xiàn)都會放到handler中,且僅有這個handler會做主要邏輯處理,開發(fā)者明確的知道這里寫的就是主邏輯。

      API的維護成本高

      API的主要實現(xiàn)就在handler中,只需要維護handler就行,有特殊的問題,再去看使用的中間件。

      API難理解

      用戶明確的知道只需要理解handler的實現(xiàn)就行,中間件的邏輯大部分是用于公共使用,只要統(tǒng)一理解就行。

      到這里,會杠的同學還是會問,其實你這好像問題也沒有完全解決,只要開發(fā)者想搞你,還是會出現(xiàn)之前的問題,比如就會有騷的人把邏輯寫到中間件里面,不寫到handler里面,你這種設計不還是一樣。

      這說的一點都沒錯,因為設計這個API難免的就是要開放給開發(fā)者這樣的能力,也就是:1)自定義API;2)對若干API做一些個性化的統(tǒng)一邏輯。API的設計者能夠做到的就是在API上傳達給開發(fā)者一種規(guī)范,就比如 bridge.plugin() 這種開放性的API,就沒有 bridge.API() 這種好,因為后者很明確的讓開發(fā)者申明一個API,而前者不明確,前者讓開發(fā)者覺得中間件就是API的實現(xiàn)。

      五  結語

      本篇我們從中間件聊到中間件的使用實例,再聊到了裝飾器模式,最后聊到了使用中間件的API的設計。在日常API設計中,我不僅會面對底層設計的選型,還會面對對外開放API的設計,兩者都同樣重要。不過本篇僅代表個人觀點,歡迎在評論區(qū)指教、討論。



      電子書免費下載
      《大促背后的前端核心業(yè)務實踐》


      618作為淘系每年重要的大促活動,前端在其中扮演著什么樣的角色?如何保證其平穩(wěn)進行?2020年又應用了哪些新技術?本書將為大家全方位展示618中的前端身影,另附6000+字圖文版前端學習秘籍和面試官直達簡歷投遞地址!

        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
        轉藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多