自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開發(fā) 開發(fā)工具
中間件可以算是一種前端中常用的”設(shè)計(jì)模式“了,有的時(shí)候甚至可以說,整個(gè)應(yīng)用的架構(gòu)都是使用中間件為基礎(chǔ)搭建的。那么中間件有哪些利弊?什么才是中間件正確的使用姿勢?本文將分享作者在實(shí)際使用中的一些想法,歡迎同學(xué)們共同討論。

中間件可以算是一種前端中常用的”設(shè)計(jì)模式“了,有的時(shí)候甚至可以說,整個(gè)應(yīng)用的架構(gòu)都是使用中間件為基礎(chǔ)搭建的。那么中間件有哪些利弊?什么才是中間件正確的使用姿勢?本文將分享作者在實(shí)際使用中的一些想法,歡迎同學(xué)們共同討論。

一 先簡單講講中間件

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
*/
}

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

  • 在下個(gè)中間件執(zhí)行之前做些什么?
  • 是否讓下個(gè)中間件執(zhí)行?
  • 在下個(gè)中間件執(zhí)行之后做些什么?

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

??

??

 

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

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

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

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

但實(shí)際上這個(gè)模式就是一個(gè)空殼,通過不同的中間件,就可以實(shí)現(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

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

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

二 聊聊幾個(gè)栗子

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

  • 從支付寶小程序的視角來抹平其他小程序的JSAPI。
  • Bridge擁有擴(kuò)展能力,能夠擴(kuò)展JSAPI。

看到“擴(kuò)展能力”,熟練的同學(xué)應(yīng)該就知道我可以切入正題了。

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

??

??

 

這種方式其實(shí)看起來是非常美妙的,因?yàn)樗械腁PI都可以通過插件的形式注入到Bridge中,可以很靈活地?cái)U(kuò)展API。

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

1 面向開發(fā)者

API的不確定性

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

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

API的維護(hù)成本高

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

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

2 面向使用者

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

3 直接使用插件

??

??

 

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

API難理解

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

問題排查難度上升

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

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

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

??

??

 

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

5 抬走,我們來看下一個(gè)

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

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

因此Fetch在設(shè)計(jì)的時(shí)候,是這么使用中間件的:

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

??

??

 

Fetch的中間件使用會(huì)相對(duì)合理一點(diǎn),通過利用中間件的特性,對(duì)外輸出了相同的出入?yún)?,再借助不同的中間件對(duì)請求的過程做流式處理。

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

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

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

三 引出觀點(diǎn)

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

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

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

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

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

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

這波流程Q的不錯(cuò)。

那終于,要在這里拋一個(gè)觀點(diǎn):

中間件的這種模式,應(yīng)該作為某個(gè)函數(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).

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

其實(shí)這段描述的體感不是很強(qiáng),因?yàn)槠鋵?shí)中間件本身已經(jīng)不是一個(gè)對(duì)象了,而維基百科中的設(shè)計(jì)模式針對(duì)面向?qū)ο蟮恼Z言做了描述。

為了更有體感一點(diǎn),附上一張《Head First設(shè)計(jì)模式》中的一圖:

??

??

 

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

  • 裝飾器和我們需要擴(kuò)展的Class都是實(shí)現(xiàn)了同一個(gè)接口。
  • 裝飾器是通過接收一個(gè)Component對(duì)象來運(yùn)作的。

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

另外《Head First設(shè)計(jì)模式》中還有一張圖:

??

??

 

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

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

四 回應(yīng)

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

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

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

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

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

那么我們應(yīng)該如何限制API呢?

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

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

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

在這個(gè)的基礎(chǔ)上,再設(shè)計(jì)一個(gè)能夠支持中間件的API:

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

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

API的不確定性

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

API的維護(hù)成本高

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

API難理解

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

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

這說的一點(diǎn)都沒錯(cuò),因?yàn)樵O(shè)計(jì)這個(gè)API難免的就是要開放給開發(fā)者這樣的能力,也就是:1)自定義API;2)對(duì)若干API做一些個(gè)性化的統(tǒng)一邏輯。API的設(shè)計(jì)者能夠做到的就是在API上傳達(dá)給開發(fā)者一種規(guī)范,就比如 bridge.plugin() 這種開放性的API,就沒有 bridge.API() 這種好,因?yàn)楹笳吆苊鞔_的讓開發(fā)者申明一個(gè)API,而前者不明確,前者讓開發(fā)者覺得中間件就是API的實(shí)現(xiàn)。

五 結(jié)語

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

【本文為51CTO專欄作者“阿里巴巴官方技術(shù)”原創(chuàng)稿件,轉(zhuǎn)載請聯(lián)系原作者】

 

??戳這里,看該作者更多好文??

 

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2024-02-06 14:05:00

Go中間件框架

2024-12-09 00:00:15

Gin框架中間件

2011-05-24 15:10:48

2021-02-11 08:21:02

中間件開發(fā)CRUD

2021-06-15 10:01:02

應(yīng)用系統(tǒng)軟件

2016-11-11 21:00:46

中間件

2018-07-29 12:27:30

云中間件云計(jì)算API

2018-02-01 10:19:22

中間件服務(wù)器系統(tǒng)

2022-11-18 07:54:02

Go中間件項(xiàng)目

2023-10-24 07:50:18

消息中間件MQ

2009-06-16 15:55:06

JBoss企業(yè)中間件

2023-06-29 10:10:06

Rocket MQ消息中間件

2012-11-30 10:21:46

移動(dòng)中間件

2019-08-12 08:00:00

ASP.NetASP.Net Cor編程語言

2013-08-08 10:34:16

云計(jì)算中間件

2021-01-26 14:57:00

中間件應(yīng)用模塊化

2023-12-06 07:14:28

前端API中間件

2009-06-16 10:53:01

JBoss中間件JBoss架構(gòu)

2015-02-07 21:52:45

PaaS中間件

2021-04-22 06:13:41

Express 中間件原理中間件函數(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)