<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>

          保姆級(jí)教程!Golang微服務(wù)簡潔架構(gòu)實(shí)戰(zhàn)

          共 5572字,需瀏覽 12分鐘

           ·

          2022-03-10 02:40


          導(dǎo)語?|?本文從簡潔架構(gòu)的理論出發(fā),依托trpc-go目錄規(guī)范,簡單闡述了整體代碼架構(gòu)如何劃分,具體trpc-go服務(wù)代碼實(shí)現(xiàn)細(xì)節(jié),和落地步驟,并討論了和DDD的區(qū)別。文章源于我們組內(nèi)發(fā)起的go微服務(wù)最佳實(shí)踐的第一部分,希望從開發(fā)和閱讀學(xué)習(xí)中總結(jié)出一套go微服務(wù)開發(fā)的方法論,互相分享一下在尋求最佳的實(shí)踐過程中的思考和取舍的過程。本次主要討論目錄如何組織,目錄的組織其實(shí)就是架構(gòu)的設(shè)計(jì),一套通用架構(gòu)的設(shè)計(jì),可以讓開發(fā)專注于邏輯設(shè)計(jì)和具體場(chǎng)景的代碼設(shè)計(jì),好的架構(gòu)設(shè)計(jì)可以預(yù)防代碼的腐敗,并且相關(guān)的規(guī)范操作簡單,可以按步驟根據(jù)情況分步落地,可操作性強(qiáng)。


          引言


          現(xiàn)在有了高效的go語言和成熟的trpc-go框架和一系列的中臺(tái)SDK,發(fā)布平臺(tái),一個(gè)新手也可以通過教程快速寫出簡單功能的微服務(wù),以此入門,開始go的微服務(wù)開發(fā),并應(yīng)對(duì)大部分開發(fā)需求。


          但是一旦開始了就會(huì)發(fā)現(xiàn)隨著需求的增加我們常常不得不去花很多時(shí)間去維護(hù)代碼,變更已有的邏輯,不斷的抽象,提高部分常用能力的可擴(kuò)展性,但往往隨著多個(gè)人在同一份微服務(wù)代碼里協(xié)作,維護(hù)這件事情越來越難做了,不僅僅是因?yàn)榇蠹业某橄箫L(fēng)格不同,對(duì)于抽象的標(biāo)準(zhǔn),模塊的分割,數(shù)據(jù)的流向,分層的邏輯都是不同的,看每個(gè)服務(wù)都像是一個(gè)新的生命,千姿百態(tài)。


          千姿百態(tài)的代碼庫不是我們希望的,我們希望在代碼的架構(gòu)上保持易讀性可擴(kuò)展性可維護(hù)性,這樣除了對(duì)于代碼細(xì)節(jié)的一致性(代碼標(biāo)準(zhǔn))外,還希望有架構(gòu)上的標(biāo)準(zhǔn),讓開發(fā)專注于邏輯設(shè)計(jì)和具體場(chǎng)景的代碼設(shè)計(jì),在海量之道的知識(shí)下把服務(wù)相關(guān)內(nèi)容做好,而不是將時(shí)間和精力浪費(fèi)在糾結(jié)如何重新組織和解亂麻、重構(gòu)等工作上,如果每個(gè)服務(wù)的架構(gòu)都足夠簡潔清晰,團(tuán)隊(duì)內(nèi)部每個(gè)倉庫都像是自己寫的,上手也會(huì)很快,團(tuán)隊(duì)的效率就會(huì)幾何的速度提升。



          一、 開發(fā)現(xiàn)狀


          不同的業(yè)務(wù)場(chǎng)景不太一樣,在增值的業(yè)務(wù)場(chǎng)景下,大部分的需求邊界或者服務(wù)全部職能一開始并不能確定,一般就是一個(gè)小需求開始的微服務(wù),后續(xù)可能隨著業(yè)務(wù)的增長慢慢變得復(fù)雜的,仿佛是從一顆小樹苗漸漸長成一顆枝繁葉茂的大樹,可能一開始這個(gè)服務(wù)的職責(zé)很單一,很簡單,一個(gè)service搭配一個(gè)logic就ok了,但后面加入了各種依賴,logic就開始變復(fù)雜,更可怕的是,因?yàn)閬硪粋€(gè)需求做一個(gè)需求(假設(shè)最壞的情況下無法預(yù)測(cè)產(chǎn)品的需求),對(duì)于落后的開發(fā)模式,或者沒有架構(gòu)概念來說,多一個(gè)需求,無非就是加個(gè)函數(shù),加個(gè)分支,需要啥,import啥就完事了,漸漸地,絕大多數(shù)服務(wù)成為:


          • 沒有合理分包,或者僅邏輯職責(zé)分包(分目錄)


          • 面向過程編程,函數(shù)調(diào)用鏈很長,在各種包之間穿插。


          • 沒有依賴注入,依賴是以全局引入的方式存在,作用域很大,潛在的并發(fā)問題。


          最終導(dǎo)致


          • 不通用,一切都不通用,每次修改一個(gè)邏輯部分可能要牽扯到多個(gè)函數(shù)調(diào)用的地方去修改參數(shù)關(guān)系。


          • 測(cè)試用例難寫,import進(jìn)來的函數(shù),是mock呢還是不mock呢,mock函數(shù)一個(gè)個(gè)寫,monkey mock是否合理?


          • 每個(gè)模塊不獨(dú)立,看似按邏輯分了模塊,比如order_hanlder,conf,XXX_helper,database等,但沒有明確的上下層關(guān)系,每個(gè)模塊里可能都存在配置讀取,外部服務(wù)調(diào)用,協(xié)議轉(zhuǎn)換等。


          先看目前的微服務(wù)代碼狀態(tài),截幾個(gè)常見的微服務(wù)目錄組織風(fēng)格:



          四種目前常見的微服務(wù)目錄組織方式,從左至右分別為1,2,3,4,可以看到:


          • 服務(wù)1除了main全部都放在logic中,logic實(shí)際上已經(jīng)職責(zé)不清了。


          • 服務(wù)2全部平鋪式的,為什么作者要這么干,因?yàn)樗麑懥撕芏鄊onkey func mock,因?yàn)闆]有抽象,不同函數(shù)之間調(diào)用導(dǎo)致很多函數(shù)的mock需要復(fù)用,但測(cè)試文件中的內(nèi)容不支持import,所以為了避免底層邏輯函數(shù)要重復(fù)在不同包里寫mock,干脆平鋪了。


          • 服務(wù)3常見組織方式,以邏輯為單元進(jìn)行模塊分包解耦,基本符合單一職責(zé)原則,但這種微服務(wù)隨著需求的增長會(huì)產(chǎn)生網(wǎng)狀調(diào)用的問題。


          • 服務(wù)4對(duì)外部調(diào)用有一定抽象的目錄設(shè)計(jì),但組織方式并不一眼清晰,沒有合理的分包,邏輯代碼寫在接入層。


          (一)沒有架構(gòu)



          如上述例子中,大多數(shù)服務(wù)沒有架構(gòu)上的概念,多數(shù)業(yè)務(wù)是以邏輯單元的方式去分包(分目錄),每個(gè)包之間關(guān)系是平級(jí)關(guān)系,每個(gè)包的邏輯獨(dú)立,理論上使用包功能時(shí)import進(jìn)來即可,隨著服務(wù)的成長:


          • 服務(wù)不同包函數(shù)之間的調(diào)用慢慢演變成網(wǎng)狀結(jié)構(gòu)。


          • 數(shù)據(jù)流的流向和邏輯的梳理變得越來越復(fù)雜。


          • 很難不看代碼調(diào)用的情況下搞清楚數(shù)據(jù)流向。


          這是目前常見的一個(gè)實(shí)際問題,業(yè)務(wù)增長過程中,微服務(wù)很容易長成一個(gè)垃圾山,開發(fā)心累,改不動(dòng)的情況出現(xiàn)。


          所謂的代碼腐敗即在代碼增量到一定程度后,服務(wù)內(nèi)部的函數(shù)調(diào)用組織是網(wǎng)狀結(jié)構(gòu),沒有層級(jí)結(jié)構(gòu),即使微觀上可能是解耦的,但宏觀上是亂成一團(tuán)的,DDD等設(shè)計(jì)思想都是為了解決這樣的問題。



          (二)沒有分層


          常見的微服務(wù)只有分包沒有分層的概念,數(shù)據(jù)流沒有分層,因?yàn)闆]有合理的分層,自然沒有上下調(diào)用的關(guān)系,最多就是邏輯上分個(gè)包而已,用到啥import進(jìn)來就完善,沒有層次的系統(tǒng)就是一盤散沙,一盤散沙的接口,互相隨意調(diào)用,關(guān)系亂成一團(tuán),這就是日后維護(hù)和調(diào)試的噩夢(mèng)。



          二、 探索最佳架構(gòu)實(shí)踐


          (一)簡潔架構(gòu)



          出自《架構(gòu)整潔之道》,此架構(gòu)模型是不區(qū)分前后端的廣義上的抽象架構(gòu)我們希望每個(gè)微服務(wù)的代碼在微觀上也是符合簡潔架構(gòu)。


          在后臺(tái)服務(wù)的場(chǎng)景下,以trpc-go目錄規(guī)范可以抽象出一種金字塔結(jié)構(gòu)的架構(gòu):



          這種結(jié)構(gòu)的優(yōu)勢(shì)體現(xiàn)在:


          • 標(biāo)準(zhǔn)結(jié)構(gòu):層+模塊


          1. 結(jié)構(gòu)分層,每層之間劃分模塊。

          2. 數(shù)據(jù)流向固定,自上而下單一方向。

          3. 架構(gòu)清晰,需求代碼增長是結(jié)構(gòu)化的,組織關(guān)系不是網(wǎng)狀。


          • 一致性


          1. 架構(gòu)通用,可以統(tǒng)一規(guī)范。

          2. 協(xié)作開發(fā)時(shí)不同服務(wù)的架構(gòu)一樣,無理解成本。


          • 易于操作


          1. 相關(guān)概念簡單,易于操作,符合開發(fā)直覺,便于正確分類代碼。

          2. 不涉及領(lǐng)域建模等額外問題。


          • 減緩代碼膨脹


          1. 分層將代碼上升或下層,以三層的結(jié)構(gòu)可以一定程度上降低每一層的代碼膨脹的速度。



          (二)目錄規(guī)范



          分層按數(shù)據(jù)流向分為接口層(網(wǎng)關(guān)層)、邏輯層,外部依賴層,劃分方式和理解成本都不會(huì)很高,詳細(xì)如下:


          • gateway


          • 接口實(shí)現(xiàn)的地方,服務(wù)接口入口處,對(duì)應(yīng)trpc-go的service。


          • 只進(jìn)行協(xié)議解析轉(zhuǎn)換,協(xié)議的整理,不涉及業(yè)務(wù)邏輯。



          • logic


          • 服務(wù)核心業(yè)務(wù)邏輯實(shí)現(xiàn)的地方。


          • 內(nèi)部實(shí)現(xiàn)分模塊分包。



          • repo


          • 外部依賴層,包括外部數(shù)據(jù)庫、RPC調(diào)用等。


          • 每個(gè)包提供抽象接口,將外部數(shù)據(jù)調(diào)用并整理后以接口的方式提供給logic。


          • 僅做外部調(diào)用和數(shù)據(jù)整理,不包含業(yè)務(wù)邏輯。



          • entity


          • 貫穿整個(gè)服務(wù)的數(shù)據(jù)結(jié)構(gòu),類似常量,錯(cuò)誤碼。


          • 貧血模型,即僅包含數(shù)據(jù)結(jié)構(gòu)讀寫方法的對(duì)象。



          • 防腐層


          • 每層對(duì)外暴露的都以抽象接口方式,用依賴倒置的方式實(shí)現(xiàn)每層之間的防腐。


          • 抽象接口天然可以gomock生成樁代碼,上層單測(cè)時(shí)只需要用下層對(duì)應(yīng)的樁代碼mock下層依賴即可。



          三、 實(shí)現(xiàn)規(guī)范



          在實(shí)踐過程將代碼目錄按標(biāo)準(zhǔn)劃分歸類只是第一步,重要的是層與層之間的隔離和模塊與模塊之間的解耦,所以需要用到依賴倒置、依賴注入、封裝、測(cè)試規(guī)范來實(shí)現(xiàn)具體的代碼,其中測(cè)試規(guī)范是反向校驗(yàn)代碼設(shè)計(jì)是否合格的一把尺子,如果每個(gè)接口無法使用gomock打樁,那么依賴倒置就是沒有做好。


          (一)依賴倒置、接口隔離


          • 依賴倒置


          • 上層模塊不應(yīng)該依賴底層模塊,它們都應(yīng)該依賴于抽象。


          • 抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。



          • 接口隔離


          • 客戶端不應(yīng)該依賴它不需要的接口。


          • 模塊間的依賴應(yīng)該建立在最小的接口之上。


          實(shí)現(xiàn)要求:不同層之間對(duì)外的接口一律以interface的方式提供,并且單一職責(zé)的設(shè)計(jì),接口盡可能簡單清晰,接口文件單獨(dú)存放,不放在具體實(shí)現(xiàn)的文件中,依賴參數(shù)定義和接口聲明放在一起。


          例,msg包下api.go定義消息接口:




          (二)依賴注入


          依賴注入(DI,Dependency Injection)指的是實(shí)現(xiàn)控制反轉(zhuǎn)(IOC,Inversion of Control)的一種方式,其實(shí)很好理解就是內(nèi)部依賴什么,不要在內(nèi)部創(chuàng)建,而是通過參數(shù)由外部注入。例:



          • 內(nèi)部封裝


          • 高內(nèi)聚低耦合。


          • 合理的抽象函數(shù),分子函數(shù),聚類等。


          例:




          (三)不引入gomock以外的mock包


          如果一定要monkey mock來對(duì)函數(shù)打樁時(shí), 說明代碼沒有符合接口原則。并且Monkey mock的mock函數(shù)不可導(dǎo)出 在每個(gè)調(diào)用的此函數(shù)的包內(nèi)單測(cè)時(shí),都需要重新寫一遍mock。


          Gomock樁代碼可自動(dòng)生成,上層需要mock下層依賴時(shí),只需要將mock的樁作為依賴注入即可。




          (四)配置(遠(yuǎn)程配置)


          現(xiàn)在幾乎每個(gè)服務(wù)除了框架配置外,會(huì)接入遠(yuǎn)程配置(七彩石配置),讀取遠(yuǎn)程配置的邏輯幾乎每個(gè)服務(wù)都要重新實(shí)現(xiàn)一遍,因?yàn)榕渲玫淖罱K輸出一定是一個(gè)個(gè)性化的結(jié)構(gòu)體(每個(gè)服務(wù)的配置肯定都不一樣),所以很難用一套代碼解決,這里采用了一個(gè)包替換的方式,將出口的結(jié)構(gòu)體通過引入不同的config entity定義,來實(shí)現(xiàn)代碼的通用(僅是通用,還實(shí)現(xiàn)不了零copy)


          • 每個(gè)服務(wù)一個(gè)遠(yuǎn)程配置。


          • 遠(yuǎn)程配置為json格式(yaml一樣,內(nèi)部統(tǒng)一即可)


          • 遠(yuǎn)程配置定義在entity/config包中,結(jié)構(gòu)體為Config。


          這樣可以復(fù)用如下遠(yuǎn)程配置實(shí)現(xiàn):




          這里如果服務(wù)有多個(gè)配置:


          例:這個(gè)服務(wù)是重構(gòu)過的,之前沒有規(guī)范,所以弄了三個(gè)不同的遠(yuǎn)程配置(實(shí)際上一個(gè)即可):



          因?yàn)镚et返回的結(jié)構(gòu)不同,所以不同配置使用不同的接口實(shí)例來實(shí)現(xiàn),每個(gè)不同結(jié)構(gòu)的配置在解析時(shí)是固定的結(jié)構(gòu)體,get返回也是固定的結(jié)構(gòu)體,在go模板特性未支持的情況下每個(gè)不同文件的配置,以不同實(shí)現(xiàn)impl來完成解析, 看起來代碼上有一些重復(fù),但這樣表達(dá)能保證清晰易懂,一般情況一個(gè)服務(wù)業(yè)務(wù)配置放在一個(gè)文件中。


          一個(gè)服務(wù)一個(gè)配置,對(duì)于配置初始化等代碼的減少,有很大的幫助。



          (五)配置的使用


          接口化的配置很方便實(shí)現(xiàn)依賴注入,摒棄之前那種引入配置包,讀取全局配置的方式,通過依賴注入來實(shí)現(xiàn)配置作用域減小,避免很多并發(fā)問題:




          四、落地方法


          理想很豐滿現(xiàn)實(shí)很骨感,需求進(jìn)度和代碼質(zhì)量的矛盾,如果要一步到位,在實(shí)踐中等于一步也實(shí)行不下去。


          實(shí)際情況往往是需求很緊急,并沒有太多時(shí)間給開發(fā)用來設(shè)計(jì)和優(yōu)化代碼,所以我們希望走第一步的時(shí)候不會(huì)占用開發(fā)太多的時(shí)間,最好時(shí)間分配可以從1:9的方式開始,并且在任何階段都可以以需求快速完成為優(yōu)先(即容忍一定程度的不遵守也不會(huì)破壞整體),即一開始你可以在90%的自由度上保持你自己舊的風(fēng)格,抽出10%的時(shí)間來設(shè)計(jì),這樣落實(shí)規(guī)范并不會(huì)很痛苦。



          整體落地的步驟可以分為三個(gè)階段(不是必要經(jīng)歷的,時(shí)間不緊張可以直接按標(biāo)準(zhǔn)實(shí)現(xiàn)來)



          根據(jù)當(dāng)前需求的緊急程度和個(gè)人時(shí)間安排來分階段實(shí)踐即可。



          五、總結(jié)


          微服務(wù)代碼架構(gòu)的一致性和實(shí)現(xiàn)規(guī)范的一致性可以帶來很多好處:



          (一)為什么不是DDD


          其實(shí)之所以要提DDD,是因?yàn)檫@是個(gè)避不開的問題,但答案其實(shí)已經(jīng)有了DDD是把控中大型項(xiàng)目的殺手锏,但使用DDD并不能使開發(fā)新項(xiàng)目變得更快,更方便,而是為了今后考慮,讓一個(gè)龐大的系統(tǒng)可以更快的迭代,更新,也就是說新的項(xiàng)目不用太在意領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),甚至新項(xiàng)目開始可以不用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)


          DDD的優(yōu)勢(shì)和劣勢(shì)



          不同的業(yè)務(wù)可能面臨不一樣的問題,很多實(shí)踐中的需求往往不是一開始就有頂層設(shè)計(jì)的大需求、大項(xiàng)目,甚至很多微服務(wù)還沒確定自己領(lǐng)域內(nèi)的元素,就伴隨著業(yè)務(wù)死亡了,創(chuàng)建服務(wù)之初領(lǐng)域模型和邊界并不清楚,一個(gè)一個(gè)接口的新服務(wù),從一開始設(shè)計(jì)時(shí)就去事件風(fēng)暴、劃分元素、子域等也是不切實(shí)際的,所以越是微小的服務(wù),越是不需要DDD。很多時(shí)候我們不得不考慮團(tuán)隊(duì)的新成員快速成長的問題,一個(gè)新同學(xué)或者實(shí)習(xí)生同學(xué)很難快速上手DDD,并把DDD落地到每個(gè)服務(wù)里,不能全部落地,這樣就會(huì)存在不同需求服務(wù)之間的不一致性,接手同事的服務(wù)時(shí),還是會(huì)存在理解結(jié)構(gòu)的心智負(fù)擔(dān)。



          后記


          整體的規(guī)則描述了大概,但是實(shí)踐的過程中,對(duì)于內(nèi)部具體細(xì)節(jié),函數(shù)的抽象,聚類,子模塊的劃分,都是經(jīng)驗(yàn)和實(shí)踐的積累,還是很考驗(yàn)一個(gè)人的代碼功底,這點(diǎn)架構(gòu)規(guī)范并不能給予幫助。


          好的架構(gòu)或者說目錄設(shè)計(jì)像是垃圾分類的垃圾桶,預(yù)先設(shè)置好分類規(guī)則,垃圾就可以很輕松的進(jìn)行分類,分類好的垃圾就可以變廢為寶,成為可利用的資源,所以面對(duì)垃圾山一樣的代碼,重構(gòu)時(shí)我們首先要遵循正確的架構(gòu)進(jìn)行垃圾分類。


          雖然進(jìn)行了有效的分層,但是對(duì)于logic層里面的模塊拆分并不要求嚴(yán)格,即提供了抽象接口之后,具體實(shí)現(xiàn)是細(xì)節(jié)問題,隨著需求的增長實(shí)際上還是面臨增長之后帶來復(fù)雜度關(guān)系,但由于拆分了外部調(diào)用在repo和數(shù)據(jù)實(shí)例在entity,微服務(wù)最終logic的代碼并不會(huì)膨脹的很快,三層結(jié)構(gòu)可以一定程度的減緩復(fù)雜度膨脹的速度,如果有一天膨脹大了,那么使用DDD進(jìn)行重構(gòu)可能是另一種解法。


          本文是記錄一下在尋求最佳的實(shí)踐過程中的思考和取舍的過程,畢竟對(duì)于微服務(wù)代碼架構(gòu)的實(shí)踐沒有銀彈,不存在哪一種更好的情況,只有相對(duì)容易落地和簡單有效的方案才比較通用


          參考資料:

          1.《架構(gòu)整潔之道》

          2.《tRPC-Go目錄規(guī)范》

          3.《go-clean-arch》



          ?作者簡介


          楊帥

          騰訊后臺(tái)開發(fā)工程師

          騰訊后臺(tái)開發(fā)工程師,深圳大學(xué)畢業(yè),目前負(fù)責(zé)社交增值商業(yè)廣告相關(guān)后臺(tái)開發(fā),主要開發(fā)語言為go語言,在渠道投放系統(tǒng)管理端建設(shè),和增值商業(yè)廣告中臺(tái)建設(shè)等領(lǐng)域積累了豐富的開發(fā)經(jīng)驗(yàn)。



          ?推薦閱讀


          來,5W1H分析法幫你系統(tǒng)掌握緩存!(圖文并茂)

          模型也可以上網(wǎng)課?手把手教你在query-doc匹配模型上實(shí)現(xiàn)蒸餾優(yōu)化!

          2種方式!帶你快速實(shí)現(xiàn)前端截圖

          C++反射:深入探究function實(shí)現(xiàn)機(jī)制!



          瀏覽 88
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  一区二区三区精品毛片 | 久久露脸国语精品国产 | 欧美日韩一级免费看 | 影音先锋资源你懂的 | 操丝袜熟女骚逼 |