我們進入微服務世界的旅程-以及我們從中學到的東西。

本文爲翻譯發表,轉載需要註明來自公衆號EAWorld。

作者:Ignacio Salazar Williams

譯者:白小白

原題:

Our journey into the world of Microservices — and what we learned from it.

原文:

https://medium.freecodecamp.org/our-journey-into-the-world-of-microservices-and-what-we-learned-from-it-d255b9a2a654

全文7510字,閱讀約需要15分鐘

“舊地圖上的指南針”

by Himesh Kumar Behera on Unsplash

我知道,我知道每個人都在談論微服務。人們談起數字化轉型,就會說微服務模式將引導我們走向架構的未來。還有人說,微服務是“巨石應用的毀滅者”,是解決我們所有架構問題的靈丹妙藥。

要我說,微服務確實很了不起,但它不是說吹一口仙氣然後施些魔法、一片仙氣繚繞的解決我們所有的問題。我不打算從模式的角度再多講什麼,而是盡我最大的努力來講述這個故事,我的故事。我將討論這一概念和模式是如何在現實中、在某些情況下不斷演進和變化的。我稱之爲微服務的世紀決戰。

白小白:

微服務的世紀決戰,原文爲Micro-Armageddon,Armageddon是基督教《聖經》所述世界末日之時善惡對決的最終戰場。根據《聖經》記載,全能者會在此擊敗魔鬼和“天下衆王”,進而引申爲“傷亡慘重的戰役”、“毀滅世界的大災難”、“世界末日”等涵意。邁克爾•貝在1998年執導了同名的電影,由布魯斯•威利斯主演。

來源:GIPHY

在我的團隊裏,每天總有些事情是我們力所不能及的,並導致了一些問題。但是,如果從大局着眼,以良好的心態不斷改進我們的組件,總會讓軟件達到團隊所期望的應有的質量標準。

所以,請跟我一起走過這段好壞參半、喜淚交織的旅程,並一起回顧太多的悔不當初。

小結

這篇文章有點長,但我跟你講,如果想從他人關於微服務的錯誤中吸取教訓,我強烈建議你通讀這篇文章。當然,也可以跳着看看插畫,至少樂呵樂呵,是吧。

1.一點背景


來源: Innoview

讓我們從頭講起。我呢(大家好!),是一個剛畢業的計算機科學專業的學生,剛剛受僱做一些諮詢 (類似西部拓荒一類的) 工作。公司的一個客戶在他們的辦公室指派我進入了這個項目,我們的團隊負責對他們的業務進行數字化轉型。因此,需要引入微服務的理念。(隨着我在這個領域的經驗更加資深,我經常同時聽到這兩個概念一起出現。)

我們用Node.js作爲後端編程語言(爽),因此我們使用Express(基於 Node.js 平臺的 Web 應用開發框架)作爲暴露API的默認框架。另外,很重要的是我們在項目中應用了基於敏捷理念的Scrum研發過程(你很快就會明白我爲什麼單獨指出這一點)。

團隊分工

Photo by rawpixel on Unsplash

我們被分成兩個大組:我所在的第一組,是架構團隊。我們負責指導團隊方向並傳播有關微服務的理念。第二組是開發團隊,負責按照業務需求開發產品。有多個團隊同時服務於不同的產品,同時也在從事微服務的工作。

架構團隊的構成,類似如下:

  • 1名高級經理(我們的人)
  • 2名經理(1名我們的,1名客戶的)
  • 10名架構師(6名我們的,4名客戶的)
  • 2人負責大數據
  • 3人負責CI/CD
  • 3人負責安全
  • 2人負責前後端開發架構

每個開發團隊的構成類似如下:

  • 1 名Scrum Master(也負責諮詢)
  • 1名產品負責人(客戶方)
  • 4名開發人員(2名我們的,2名客戶的)
  • 1 人負責QA
  • 1人負責UX
  • 1名架構師(客戶方)

我知道這聽起來已經很糟糕了,一切成員角色混在一起了,但別擔心,對我們來說,這正是一個改正錯誤的機會。

開發人員背景技能

沒有人生來就是一個熟練的開發人員,我們所有人就像一隻猴子,試圖編譯一個基本的“HelloWorld”。 ——FelipeLazo

我們的團隊成員有各種各樣的背景,有些人對計算機一無所知,有些人來自美國宇航局。有些人使用過COBOL、Java、JavaScript、C、Python等,而其他人則完全沒寫過代碼。

因此,很容易理解,一些團隊成員不是特別擅長開發好的代碼和結構,因爲他們中的許多人就沒有這方面的背景。同樣,還有些人有一些經驗。對這些不同的背景情況心中有數是有好處的,這將取決於我們如何對這些差異性進行充分利用。不應把這看作是一個弱點,而應該看作團隊整體改進的機會(在敏捷環境中工作時尤其如此)。

2.目標

在這個項目裏,我們的目標是把微服務作爲一個後端解決方案,來集成客戶所擁有的遺留組件。我們打算將它們暴露爲簡單的API,以便相關團隊能夠將它們集成到他們的應用程序中。

以下是我們對微服務的初步需求:

  • 調用SOAP服務並返回JSON結果。對大多數人(包括我)來說,這聽起來會有點糟糕。但只能這樣,因爲微服務沒有權限直連數據層,所以他們必須通過SOAP來獲取數據【客戶方初始需求】。
  • 將產生微服務的所有數據記錄到全新的DataLake中。
  • 支持基本的認證機制。
  • 儘可能保證無故障運行。

在這些要求之外,我們還必須添加下面這些特性:

  • 通過單元測試達成期望的質量(此處有我們雄心勃勃的90%的覆蓋率標準)
  • 靜態代碼分析
  • 性能測試
  • 一些安全檢查。

所有這些都必須在本地手動檢查,然後通過嚴格的管道(CI/CD)進行檢查。說是嚴格的檢查,但也不是一票否決式的。即使有某項要求不滿足,仍然允許部署微服務。但我建議大家永遠不要這樣做,或者至少要了解這樣做的後果。

到目前爲止,我們沒有遇到太多的問題。作爲開發微服務所需的一個基本條件,這聽起來相當不錯。我們有DevOps,在同一個地方工作,我們有我們的方法和模式,我們有一個超棒的運行時(Node.js)環境,所有這一切讓我們可以一步步地構建和遵循規則,使這個項目成爲一個傑作。嗯,至少當時我們是這樣想的…

3.完蛋,出錯了

團隊試圖拯救微服務的精準描述

看看這張相當寫實的圖片,圖中的架構團隊試圖將微服務從水深火熱中拯救出來。你可能會問,爲什麼會發生這種事?在敏捷開發場景下,允許多個團隊自由開發自己的微服務時,就會發生這種情況。微服務是什麼?能幹什麼?爲什麼這麼幹?如何治理?重要的是,應該以怎樣的顆粒度進行切分?如果不對這些問題做出充分的解釋,麻煩就一定會出現。

而且,在項目開始時,除了Subversion之外,我們沒有任何可靠的版本控制軟件。其時,我們正試圖內部部署Git。

所以,基本上基本上就會導致下面這樣的場景:

  • 微服務:“客戶”(由A組、B組和C組負責)

-B組厭倦了所有的合併和部署工作,也厭倦了誰來爲此負責而進行的爭吵。

  • 微服務:“貸款客戶”(B組負責)

-B組把“客戶”微服務的所有狀態原封不動的複製過來。這就導致了伴隨有用的endpoint暴露了越來越多的無用的endpoint,並且帶來大量的維護工作。

白小白:

實際上微服務中的endpoint的概念類似MVC中的controller的概念,包括後面列出的代碼也展示了這一點。

我們經常看到的一個問題是,許多不成熟的團隊不僅沒有試圖撲滅這場大火,反而在不斷重複着構建這類微服務,並基於此開始更多的開發工作,從而使火勢進一步蔓延。這使得微服務體系越來越龐大,並且包含了無用和重複的內容。

大概情況就是這樣。我們到底該如何解決所有這些(地獄一樣)的問題?以下是我們的解決方案。

4.識別症狀

Photo by rawpixel on Unsplash

很明顯,我們不能任由這種亂相繼續下去,所以,宛如疾控中心控制埃波拉病毒的漫延一樣,我們穿上白大褂,消毒了房間,並對我們的現狀進行了檢視。就像前面所說的,只要是讓各團隊自由的開發自己的微服務而不做任何事先的規約,很多災難就是不可避免了。我們識別了可以產生災難的一系列症狀,並將最重要的症狀列爲優先事項,並試圖通過達成一些小目標,來逐漸控制局勢。

建立一些小目標,不僅有助於明確問題,而且還讓團隊成員知道在改善日常工作方面還是可以有所作爲的。

5.“微-巨石”(Mini-Monolith)

或曰

“宏服務”(Macroservices)

等一下!不是說微服務的事麼…

這是宏服務?

我相信你已經對S.O.L.I.D.原則( https://hackernoon.com/solid-principles-made-easy-67b1246bcdf )爛熟於心,講述智能、緊湊的單元應該具備著名的單一原則理念。

白小白:

我很懷疑有多少人能夠講得清SOLID原則,網上的文章,能夠清晰明瞭講清楚的也不多。有兩篇文章可以參考:

https://dwz.cn/QwndtvVu

https://dwz.cn/Qx4U1ijz

前一篇的例子較好,後一篇的解釋較通俗,結合在一起看,應該能瞭解個大概。

我們這裏講的不是這個。這也是爲什麼當我觀察到周遭所發生的事情時,我稱之爲宏服務

白小白:

按照作者後面的描述,其實問題的產生,恰恰在於沒有按照SOLID的單一職責和接口分離原則來進行微服務的拆分,才產生的大而無當的“宏服務”。

想象一下:在一個簡單的領域,比如用戶域中,對同一個“微服務”就有15種POST操作。所有的操作都在一個相同的領域中,但卻都有着不同的目標,並使用獨有的定製庫。此外,我們還要在這樣的場景下進行所有的單元和性能測試。這簡直是一場動亂。差不多是這樣的情景:

├── app --The whole MS is in here
│   ├── controllers --All the controllers of the domain
│   │   ├── dummies
│   │   │  └── ** All the dummies for each controller **
│   │   ├── xsl
│   │   │   └── ** All xsl configuration for each controller **
│   │   ├── Controller1.js
│   │   ├── Controller2.js
│   │   ├── Controller3.js
│   │   ├── Controller4.js
│   │   ├── Controller5.js
│   │   └── **Literally 20 more controllers**
│   ├── functions --All the functions of the MS
│   │   ├── function1.js
│   │   ├── function2.js
│   │   ├── function3.js
│   │   └── function4.js
│   ├── properties --All the properties of the MS
│   │   ├── propertie1.js
│   │   └── propertie2.js
│   ├── routes --All the routes of the MS
│   │   ├── routes_useSecurity.js
│   │   └── routes_withoutSecurity.js
│   ├── services --Extra services that were consumed
│   │   ├── service1.js
│   │   └── service2.js
│   └── xsl
│      └── **A bunch of XSL to do transformations**
├── config --"Global" configurations
│   ├── configSOAP.js
│   ├── configMS.js
│   ├── environments.js
│   ├── logging.js
│   ├── userForBussinessA.js
│   └── userForBussinessB.js
├── package.json
├── README.md
├── test--All the tests
│   ├── UnitTesting
│   │   └── Controllers
│   │       └── ** All the 25 tests in theory **
│   └── PerformanceTest
│       ├── csv_development.txt
│       ├── csv_QA.txt
│       ├── csv_production.txt
│       ├── performance1.jmx
│       └── performance2.jmx
├── server.js --Express Server
├── serverKey.keytab
├── sonarlint.json
├── encryptor
├── ** Around 10 more useless files **
└── Dockerfile

所以如果A組修改了Controller1,他們必須通過管道進行集成,很有可能失敗(然後部署也會失敗)。他們得把這個過程不斷地重複,直到他們成功爲止。因此,所有的隊伍都爭先恐後的不做最後部署那一組。因爲如果有什麼東西在部署中失敗了,就得背鍋,承認自己做錯了把事情搞糟了。

這基本上就是我的慘狀了

好玩吧?這對於所有開發者來說是一個多麼“健康”的環境。誰不想在那裏…反正不是我!

無論如何,這種現狀必須停止,因爲這樣根本無法治理。即使爲了在DEV環境中做一些測試(這是常規操作),也必須通過CI/CD管道進行構建和部署,團隊爲此心力交瘁。在項目的這個階段,這顯然是難言完美的。

6.是時候重新開始了

我宣佈:破產!

我們需要重新開始,讓事情迴歸正軌。把控誰在做什麼,讓大家各司其職。當然,我們也必須做到公平:我們不會讓一個團隊完全負責包含15種操作的整個領域,畢竟這裏有測試、部署等太多事情。沒人能一力承擔。

你知道,我們是搞敏捷的,敏捷人做敏捷事。我們不需要把寶貴的時間浪費在誰該負責的事情上,更沒有必要爲此大打出手、指指點點、*翻白眼*。

步驟1:調整微型服務的規模

在此,我要做一個大膽斷言:所有微服務的最大操作數量必須遵循CRUD標準。以這個爲前提,不需要再考慮微服務應該有多大顆粒。

遵循這條規則會讓你在夜晚獲得心靈的寧靜,因爲你知道,在任何給定的時間任何子域中最多只需要有4個操作。僅此而已。

就是說:

  • POST - Create

CREATE過程是插入新數據作爲微服務的持久化。

  • GET - Read

READ過程,讀取客戶端所需的數據。

  • PUT - Update

UPDATE過程修改記錄而不覆蓋。

  • DELETE – Delete

DELETE過程刪除指定的數據。

採用這樣的規則使我們能夠開發更加緊湊、更智能和更標準的微服務。當需要劃分微服務的時候,這會讓我們更加從容。

假設銀行域裏有一個“客戶”微服務,突然我發現我不僅需要表達信用卡客戶而且需要貸款用戶,很簡單。只需將客戶域劃分爲兩個子域:“信用卡客戶”和“貸款客戶”,由此,你可以看到一切都是如何迴歸正軌。

完美!我們現在有了合適的微服務。接下來需要做的就是讓客戶和團隊找到一種方式來了解如何拆分域,並瞭解它們的子域。

有沒有現成的方法呢?…(敲黑板)當然有,那就是領域驅動設計(Domain Driven Design)

(地址:

https://medium.com/withbetterco/what-is-domain-driven-design-bcf81fc4fdc1 )

步驟2:確定負責人

我們解決了一個問題,可以長出一口氣,但還沒完,現在的情況是,有一堆微服務,每個人都在參與開發,但出了問題卻沒人負責。

我的觀點是:“誰寫的代碼,誰負責”。對這句至理名言,你可能會說:“這我早就知道,大家也都知道。”並不是這樣,不僅不是每個人都知道這一點,而且這是一個常見的錯誤。要學聰明一點,把它作爲一項規則。

(圖)來自D. Keith Robinson 的文章

Learn to love Git

只要把Git用好,可以讓你安心開發 (參見上面 D. Keith Robinson 的文章鏈接 https://medium.com/designing-atlassian/learn-to-love-git-part-one-the-basics-90429f456ace ),因爲你知道你的代碼永遠都是最新的。如果有人想要改進它,提出改進的建議,或者只是需要一個更新,所有這些都必須經過代碼的負責人。就本文的例子來說,負責人是開發代碼的DEV團隊的架構師。這在敏捷場景下非常有效。

步驟3:API端點(命名)和版本控制

正確處理命名API的方式可以節省開發人員的大量時間和精力。命名API不是遊戲。它可以拯救生命。

良好的命名可以令微服務增值。如果你實在不會起名字,就問問業務人員,並與你的團隊一起討論確定。設計驅動的開發在這裏可能會有所幫助。

瞭解更多,可以看看RESTful API 設計指南與最佳實踐這本書。( https://hackernoon.com/restful-api-designing-guidelines-the-best-practices-60e1d954e7c9 )畢竟,我不能把整篇文章都引用過來。

步驟4:重構

(圖)“一個小朋友在玩Jenga積木塔的遊戲” by Michał Parzuchowski on Unsplash

建立正確的理念固然重要,但如何將理念付諸實踐?

我將向您展示的下一個文件樹將體現我作爲微服務概念的追隨者的看法。我將在服務的設計理念上遵循鬆耦合和高內聚的概念:

├── config
│   ├── artillery.js
│   ├── config.js
│   ├── develpment.csv
│   ├── processorArtillery.js
│   ├── production.csv
│   └── qa.csv
├──index.js
├──package.json
├──package-lock.json
├──README.md
├──service
│   ├── getLoans --Theoperation
│   │  ├── getLoans.config.json --Configuration of theresource
│   │  ├── getLoans.contract.js --Contract test
│   │  ├── getLoans.controller.js --Controller
│   │  ├── getLoans.performance.json --Performance test config
│   │  ├── getLoans.scheme.js --Scheme validator
│   │  ├── getLoans.spec.js --Unit Tests
│   │   └── Util --Localfunctions
│   │      ├── trimmer.js
│   │      └── requestHandler.js
│   ├── postLoans
│   │  ├── postLoans.config.json
│   │  ├── postLoans.contract.js
│   │  ├── postLoans.controller.js
│   │  ├── postLoans.performance.json
│   │   ├──postLoans.scheme.js
│   │  └── postLoans.spec.js
│   └── notFound
│       ├── notFound.js
│       ├── notFound.performance.json
│       └── notFound.spec.js
├──Util --Global functions
│   ├── headerValidator.js
│   ├── bodyValidator.js
│   ├── DBConnector.js
│   └── BrokerConnector.js
├──sonarlint.json
└──sonar-project.properties

這一理念意味着在實施DDD的過程中,不僅使域或子域的概念可替換或可拆分,同時在文件和目錄的層面也是如此。當然,在本例中,是一個Node.js的例子。

按照上面的設計,微服務的每個操作(就像getLoans和postLoans一樣)都有滿足其開發、配置、單元測試、性能測試、契約測試、方案驗證要求的所有組件和控制器。當我們的微服務的體積鼓脹的過大,必須作分割時,以操作爲單位來考慮有利於我們對分割過程施加控制。比如,我們只需要將整個文件夾移動到相應的新服務當中去,僅此而已,既不需要找到正確的依賴組件,也不需要調來調去以使其再次工作。

注:我們動態地生成API路由,因此每個操作都具有足夠的自描述性,可以和項目的package.json一起,來建立我們所需要暴露的路由。這讓我們有了我們想要的靈活性:不再手工編輯路由(這也是經常會出錯的地方,必須儘量避免)。例如:

  • 動詞/{Name of Artifact}}/{{Domain}}/{{Version}}/{{Subdomain}}/

-- Name of Artifact:你在暴露什麼樣的工件(微服務,BFF,或任何其他)?

 -- Domain:如字面含義,即操作所屬的域。

 -- Version:當前可用的資源的主版本。

 -- Subdomain:微服務在CRUD的原則下執行的操作。

  • GET/Microservice/Client/v1/loan/ -- 獲取所有客戶所做的所有貸款。

這聽起來真的很神奇,也是我強烈推薦的方式。基於這樣的方法,你將發現在組織微服務時遇到的大部分問題將大大減少。

步驟5:文檔

“我們要試試一個叫什麼敏捷編程的東西”

“聽說不用計劃不寫文檔,就是寫代碼加抱怨就行了”

“還好有名字”

“培訓的事就交給你了”

(圖)Dilbert的精彩漫畫,來自這裏

一如這漫畫中的場景一旦發生,我不得不說,那真得嚇我一跳。我可以想象所有的敏捷實踐者,竭聲尖叫着 “Scrum之魂”來爲這一方法論正名的樣子。但別擔心,這件事我幫你搞定了。

我將介紹兩個概念:第一,也是最重要的,因爲我們暴露了API,讓我們都來試試API優先開發方法(API First Development)

API First Development是一種策略,意味着開發一個API的首要任務是考慮目標開發者的利益,然後纔在此基礎上構建產品,無論是網站、移動應用程序還是SaaS軟件。如果可以在基於API進行應用開發的過程中,始終考慮開發者的利益,您和您的開發人員不僅節省了大量的工作,同時爲其他人在此基礎上構建自己的應用奠定了基礎。(參見來自Restcase的 API-優先開發方法 https://blog.restcase.com/an-api-first-development-approach/ )。

白小白:

API first不只是指出API的重要性,更重要的是體現了一種時間上的建議順序。由於API編程的理念出現較晚,很多網站先是有Web接口,而後有API接口,同時兩種接口並存。按照API First的理念,應該是API在先,而應用程序在後。也就是說,應用程序的功能還沒完備,先要設計API,然後再基於API來構建應用程序,這樣會就強迫開發者自己先“消費”自己的API,從而設計出真正開發者友好的API。By Lee Provoost From Quora

你可能會問,怎麼實踐這一策略呢?輪到第二個重要概念登場了:Swagger,構建API的衆多工具之一。這個工具將打開以一扇以乾淨和有組織的方式設計和建模API的大門。

我想,這個工具會超出你的期待。Swagger不僅解決了我們通常遇到的有關文檔的敏捷性問題,而且還改進了團隊開發微服務的方式。這一工具爲團隊間提供了正確的交互工具,因此,任何進一步的迭代都將圍繞文檔良好的API進行。並且,也不會聽到別的團隊有這樣的抱怨:“我的團隊想要…這些輸出結果,想要API有…那些特性,你看看你給我們設計了個啥?”對此,你就可以理直氣壯地說:“這是我們API的文檔,由架構師設計和批准,也滿足業務的需求”。話題到此爲止。

白小白:

Swagger是爲了描述一套標準的而且是和語言無關的REST API的規範。對於外部調用者來說,只需通過Swagger文檔即可清楚Server端提供的服務,而不需去閱讀源碼或接口文檔說明。關於Swagger,EAWorld此前發過一篇文章,可供參考《微服務架構實戰:Swagger規範RESTful API

步驟6:培訓

正如我早些時候所說,如何充分利用我們的開發人員和團隊的成員差異性,這取決於我們自己。慢慢來,找出缺點並加以改進!

(圖)李小龍

我知道每個人在訓練他們的團隊時都有不同的偏好,但如果是在敏捷開發的環境下,而目標是優化團隊時間的話,我強烈推薦Coding Dojo(http://codingdojo.org/ )。這種培訓技術使我們能夠培訓所有的團隊,使他們在每個方面擁有相同的基本專業水平,無論是Node.js、微服務、單元測試、性能測試或是其他。這種技術也改善了信息傳遞給團隊的方式--我們都玩過打電話的遊戲,多數時候遊戲的結果沒有什麼不同。沒有人有時間從頭閱讀經年累月的文檔。我們甚至可以將來自團隊的反饋應用到我們的日常工作中。所以每個人都贏了!

白小白:

關於打電話的遊戲。我理解,其實就是“傳話遊戲”,一堆人站一排,給第一個人一個描述,讓第一個人用動作表演給第二個人,往往到最後一個人的時候,最初的描述已經面目全非了。這個過程體現的是傳遞過程中信息的衰減和失真,每個人的表達都會喪失一部分精準性。但我也經歷過這樣的場景,前面給傳的亂七八糟,然後到了某個人不知怎麼就靈光一現的把樓給正回來了。關於coding dojo,簡單講就是一堆人在一起做一個小項目,相當於一種會議式的訓練方法,大家可以取長補短並迅速獲得知識的共享。這裏有一篇中文介紹 https://dwz.cn/m8O5dD7w,這裏還有一個在線方式參與的虛擬的線上dojo https://dwz.cn/Rx0CWRRD

7.吸取的教訓和最後的話

(圖)Photo by Hello I'm Nik on Unsplash

對我來說,這是關於生態系統中的個體之間是如何相互作用的話題。這也是關於學習如何對它們作出反應的過程。因爲我可以向你保證,總有一天,你會前腳想出一個解決問題的辦法,後腳就爲了適應需求最終採取一些完全不同的方法。這就是微服務的美麗之處。微服務帶來一定的靈活性,不管開始時情況看起來有多糟,只要遵循組件可替換鬆耦合,和高內聚的原則,相信我,一切都會好起來的。

微服務的實現是爲勇士準備的旅程。他們願意每天不斷改進,也意識到哪些事情可以做得更好,他們具備大局觀,也具備把事情做好的能力。

就像我之前說過的,我開始的時候並不是專家,而且也犯了錯誤。但這並沒有阻止我做正確的事。對於你們所有的人來說,我可以告訴你們:暫停,深呼吸,做你自己的診斷和改善。做對事還不晚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章