我開(kāi)源的代碼竟然有人說(shuō)看不懂
大家好,我是3y

今天繼續(xù)更新austin項(xiàng)目,如果還沒(méi)看過(guò)該系列的同學(xué)可以點(diǎn)開(kāi)我的歷史文章回顧下,在看的過(guò)程中不要忘記了點(diǎn)贊喲!建議不要漏了或者跳著看,不然這篇就看不懂了,之前寫(xiě)過(guò)的知識(shí)點(diǎn)和業(yè)務(wù)我就不再贅述啦。
今天要做的就是實(shí)現(xiàn)austin-api和austin-api-impl模塊的部分代碼,這塊完成了之后模塊之間的一整條鏈路就打通咯

01、接口設(shè)計(jì)
在austini-api模塊下定義發(fā)送消息的接口,在austin-api-impl下實(shí)現(xiàn)具體的邏輯。我的接口實(shí)現(xiàn)定義:
public?interface?SendService?{
????/**
?????*?單模板單文案發(fā)送接口
?????*?@param?sendRequest
?????*?@return
?????*/
????SendResponse?send(SendRequest?sendRequest);
????/**
?????*?單模板多文案發(fā)送接口
?????*?@param?batchSendRequest
?????*?@return
?????*/
????SendResponse?batchSend(BatchSendRequest?batchSendRequest);
}
對(duì)外提供的接口,除了需要提供Single接口,最好還提供個(gè)Batch接口。因?yàn)楹苡锌赡軜I(yè)務(wù)方是需要一次批量執(zhí)行的(如果只有Single接口,那就需要多次遠(yuǎn)程調(diào)用,這樣對(duì)業(yè)務(wù)而言就不太合適了)
我所定義的接口參數(shù)如下:
public?class?SendRequest?{
????/**
?????*?執(zhí)行業(yè)務(wù)類(lèi)型
?????*/
????private?String?code;
????/**
?????*?消息模板Id
?????*/
????private?Long?messageTemplateId;
????/**
?????*?消息相關(guān)的參數(shù)
?????*/
????private?MessageParam?messageParam;
????
}
通過(guò)messageTemplateId可以去數(shù)據(jù)庫(kù)查出整個(gè)模板的信息,而MessageParam則是業(yè)務(wù)自行傳入的參數(shù)(重要的是接收者以及文案的參數(shù)信息),而code則代表著當(dāng)前請(qǐng)求要執(zhí)行什么業(yè)務(wù)類(lèi)型的(可基于該code擴(kuò)展,后面會(huì)繼續(xù)聊到)

02、代碼實(shí)現(xiàn)
從流程可以看到,austin-api接收到請(qǐng)求之后,是把消息發(fā)到MQ的

這樣做有什么好處呢?假設(shè)某消息的服務(wù)超時(shí),austin-api如果是直接調(diào)用下發(fā)接口服務(wù),那可能會(huì)存在超時(shí)風(fēng)險(xiǎn),拖垮整個(gè)接口性能。MQ在這是為了做異步和解耦,并且在一定程度上抗住業(yè)務(wù)流量。
對(duì)于絕大多數(shù)發(fā)送的消息而言,業(yè)務(wù)方也不太關(guān)心是不是能在接口調(diào)用時(shí)就知道發(fā)送結(jié)果,并且某些渠道在發(fā)送的時(shí)候也不知道發(fā)送的結(jié)果(最后的結(jié)果是異步告知的,比如短信和PUSH推送)
基于以上的原因,引入MQ來(lái)承載接口的流量以及做異步,是非常合理的事。

前兩天我在博客平臺(tái)上發(fā)了一篇文章《面試官:系統(tǒng)需求多變時(shí)如何設(shè)計(jì)?》,有網(wǎng)友評(píng)論了一把:
面試官:我懂了,回去等通知吧。……?
leader:小王,咱們那個(gè)可變系統(tǒng)的重構(gòu)計(jì)劃寫(xiě)的怎么樣了?
小王:沒(méi)問(wèn)題了,首先按找咱們的業(yè)務(wù)區(qū)分出責(zé)任鏈,然后在每個(gè)具體的步驟中部署腳本,上層再增加一個(gè)服務(wù)編排的接口統(tǒng)一管理……?
leader:聽(tīng)起來(lái)有點(diǎn)意思,今天的候選人怎么樣?小王:別提了,嘴上說(shuō)5年經(jīng)驗(yàn)有大型系統(tǒng)設(shè)計(jì),連redis都沒(méi)用過(guò)。這不是快招聘季了嗎,招兩個(gè)實(shí)習(xí)生工具人進(jìn)來(lái)給我打打下手就夠了。
leader:好,把時(shí)間節(jié)點(diǎn)和里程碑劃分一下,confluence上立項(xiàng)開(kāi)干吧。小王:好嘞。
在這次實(shí)現(xiàn)中,我也是用了責(zé)任鏈模式,具體完整的代碼大家就去Gitee拉就好了。很多同學(xué)拉完代碼發(fā)現(xiàn)看不懂了,大家可以按照下面的圖去梳理下責(zé)任鏈的各個(gè)角色。如果實(shí)在看不懂,建議翻下我以前寫(xiě)過(guò)的責(zé)任鏈文章(已經(jīng)投稿過(guò)兩篇了)

回到代碼實(shí)現(xiàn)吧,這次我實(shí)現(xiàn)的業(yè)務(wù)是:參數(shù)前置檢查->參數(shù)拼裝->發(fā)送消息


呀,都畫(huà)了這么多圖了,先點(diǎn)個(gè)贊,關(guān)注一波先咯。
在這幾個(gè)流程中,可能你下次拉代碼的時(shí)候,會(huì)看到有“后置檢查”,或者別的什么的。但不管怎么樣,加這種邏輯我再也不用在同一個(gè)類(lèi)上寫(xiě)各種if else啦。只要在某個(gè)節(jié)點(diǎn)處添加一個(gè)Action就完事了。
(注:這是第一版實(shí)現(xiàn),后面肯定會(huì)在基礎(chǔ)上添加邏輯或注釋的,其實(shí)已經(jīng)在寫(xiě)了,但我一般是有個(gè)小階段再push代碼,所以記得star下gitee方便看最新的代碼)
先來(lái)說(shuō)前置檢查吧,主要就判斷模板ID是否有傳入,消息參數(shù)是否有傳入(對(duì)參數(shù)的常規(guī)檢查,如果有問(wèn)題,直接break掉鏈路,返回告訴調(diào)用方有問(wèn)題)

接著來(lái)看參數(shù)拼裝,這塊主要就是通過(guò)模板ID去查整個(gè)模板的內(nèi)容,然后根據(jù)業(yè)務(wù)入?yún)?/strong>拼裝出自己的TaskInfo(任務(wù)消息)。

可能有同學(xué)會(huì)有疑問(wèn)?:為什么不能直接用模板的POJO呢?反而需要拼裝成TaskInfo?
其實(shí)還是比較好理解的,模板是作為給用戶(hù)去配置該消息的信息,這是最最原始的信息。但是我們發(fā)送的時(shí)候是需要做處理的。比如,我要在用戶(hù)寫(xiě)好的URL鏈接上拼接參數(shù),我要對(duì)占位符進(jìn)行替換真實(shí)的值,我要在模板的基礎(chǔ)上增加業(yè)務(wù)ID進(jìn)而追蹤數(shù)據(jù) 等等等。
說(shuō)白了,TaskInfo是基于模板的,在模板的基礎(chǔ)上添加了某些平臺(tái)性的字段(businessId),解析出用戶(hù)設(shè)置的模板而想要發(fā)送的真實(shí)內(nèi)容等等。

在這里,值得要說(shuō)明的是msgContent該字段的說(shuō)明。在模板中,該字段我在數(shù)據(jù)庫(kù)注釋所下的定義是(這個(gè)字段存入數(shù)據(jù)庫(kù)一定是JSON格式的):
`msg_content`???varchar(600)?COLLATE?utf8mb4_unicode_ci?NOT?NULL?DEFAULT?''?COMMENT?'消息內(nèi)容?占位符用{$var}表示',
不同的渠道的JSON結(jié)構(gòu)還不一樣:
- 短信:{"content":"","url":""}
- 郵件:{"content":"","subTitle":""}
- Push:{"content":"","subTitle":"","phoneImgUrl":""}
- 小程序:{"content":"","pagePath":"" .......}
第一反應(yīng),我是想把所有渠道可能用到的字段都定義在TaskInfo下。后來(lái)感覺(jué)這樣不太好看,于是我就定義了各種Model(不同的發(fā)送渠道擁有著自己的內(nèi)容模型)

于是,我在組裝TaskInfo的時(shí)候利用反射來(lái)進(jìn)行映射,替換占位符則借助的是PropertyPlaceholderHelper

而發(fā)送則很簡(jiǎn)單了,我是直接把TaskInfo序列化為JSON,然后讀取的時(shí)候再反序列化就好了。
值得注意的是,因?yàn)門(mén)askInfo用的是ContentModel來(lái)存儲(chǔ)著內(nèi)容模型,所以我們?cè)谛蛄谢疛SON的時(shí)候需要把"類(lèi)信息"寫(xiě)進(jìn)去,不然在反序列的時(shí)候是拿不到子類(lèi)的數(shù)據(jù)的。

03、總結(jié)
對(duì)于有源碼的項(xiàng)目,其實(shí)我是不太愿意每一步講解我寫(xiě)的代碼的。因?yàn)槲艺J(rèn)為我本身寫(xiě)得也沒(méi)那么復(fù)雜,也沒(méi)有炫技的成分在內(nèi)。
但自從push了代碼以后,在群里提醒各位跟著做項(xiàng)目的小伙伴后,有好幾位向我反饋看不太懂,所以這篇我就單獨(dú)拎出來(lái)講講。
再回過(guò)頭看,其實(shí)在austin-api層接收到請(qǐng)求之后,在發(fā)送消息至MQ之前,在這里的操作都是非常簡(jiǎn)單。其實(shí)是可以把通用業(yè)務(wù)做在這(比如平臺(tái)去重的功能),但經(jīng)我考慮之后,認(rèn)為不太合適。
austin-api算是一個(gè)接入層,到目前為止它只是通過(guò)id去數(shù)據(jù)庫(kù)讀取配置,就沒(méi)有耗時(shí)的操作(這意味著該系統(tǒng)能承載的并發(fā)是極大的)。假設(shè)通過(guò)ID去數(shù)據(jù)庫(kù)讀取將來(lái)存在瓶頸,我們還可以考慮將配置從Redis甚至本地內(nèi)存里取。
這是由業(yè)務(wù)可以決定的:一個(gè)模板的變更往往并不多,即便緩存存在強(qiáng)一致性的問(wèn)題,但就那點(diǎn)點(diǎn)時(shí)間是完全可接受的??赡?span>到最后,接口的瓶頸可能就在網(wǎng)絡(luò)消耗上
點(diǎn)贊?? ? 星標(biāo)? ?在看?? 讓我看見(jiàn)你們還在支持我寫(xiě)下去。austin項(xiàng)目閱讀原文即可獲取源碼

《對(duì)線(xiàn)面試官》公眾號(hào)還在持續(xù)分享面試題,沒(méi)關(guān)注的同學(xué)可以關(guān)注一波!這是austin項(xiàng)目的上一個(gè)系列,質(zhì)量桿桿的
