5分鐘搞懂Monorepo

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"由於谷歌在Monorepo上的實踐,Monorepo受到了越來越多的關注。Monorepo意味着把所有項目的所有代碼統一維護在一個單一的代碼版本庫中,和多代碼庫方案相比,兩者各有優劣,需要根據公司文化和產品特性進行取捨。原文:What is monorepo? (and should you use it?)","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[1]","attrs":{}}],"marks":[{"type":"italic"}],"attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"Monorepos(單一代碼庫)有助於加快開發工作流程,在本文中,我們將幫助你認識這一代碼組織模型是否適合你的團隊和公司。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"什麼是monorepo?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"Monorepo","attrs":{}},{"type":"text","text":"的意思是在版本控制系統的單個代碼庫裏包含了許多項目的代碼。這些項目雖然有可能是相關的,但通常在邏輯上是獨立的,並由不同的團隊維護。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有些公司將所有代碼存儲在一個代碼庫中,由所有人共享,因此Monorepos可以非常大。例如,理論上谷歌擁有有史以來最大的代碼庫,每天有成百上千次提交,整個代碼庫超過80 TB。其他已知運營大型單一代碼庫的公司還有微軟、Facebook和Twitter。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Monorepos有時被稱爲","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"單體代碼庫(monolithic repositories)","attrs":{}},{"type":"text","text":",但不應該與","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"單體架構(monolithic architecture)","attrs":{}},{"type":"text","text":"相混淆,單體架構是一種用於編寫自包含應用程序的軟件開發實踐。這方面的一個例子就是Ruby on Rails,它可以處理Web、API和後端工作。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"單一代碼庫(monorepos) vs 多代碼庫(multirepos)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與單一代碼庫相反的是","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"多代碼庫(multirepos)","attrs":{}},{"type":"text","text":",每個項目都儲存在一個完全獨立的、版本控制的代碼庫中。多代碼庫是很自然的選擇——我們大多數人在開始一個新項目時都願意開一個新的代碼庫,畢竟,誰都喜歡從0開始.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從多代碼庫到單一代碼庫的變化就意味着將所有項目移到一個代碼庫中。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d8/d86a0baaf0a1bc0d9f44201dacd7071f.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,這只是開始。當我們開始重構和整合時,困難的工作就來了。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"$ mkdir monorepo\n$ git init\n$ mv ~/src/app-android10 ~/src/app-android11 ~/src/app-ios .\n$ git add -A\n$ git commit -m \"My first monorepo\"\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多代碼庫不是","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"微服務(microservices)","attrs":{}},{"type":"text","text":"的同義詞,兩者之間並沒有耦合關係。事實上,我們稍後將討論將單一代碼庫和微服務結合起來的例子。只要仔細設置用於部署的CI/CD流水線","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[2]","attrs":{}}],"attrs":{}},{"type":"text","text":",單一代碼庫就可以託管任意數量的微服務。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"單一代碼庫的好處","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"乍一看,單一代碼庫和多代碼庫之間的選擇似乎不是什麼大問題,但這是一個會深刻影響到公司開發流程的決定。至於單一代碼庫的好處,可以列舉如下:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"可見性(Visibility)","attrs":{}},{"type":"text","text":":每個人都可以看到其他人的代碼,這樣可以帶來更好的協作和跨團隊貢獻——不同團隊的開發人員都可以修復代碼中的bug,而你甚至都不知道這個bug的存在。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"更簡單的依賴關係管理(Simpler dependency management)","attrs":{}},{"type":"text","text":":共享依賴關係很簡單,因爲所有模塊都託管在同一個存儲庫中,因此都不需要包管理器。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"唯一依賴源(Single source of truth)","attrs":{}},{"type":"text","text":":每個依賴只有一個版本,意味着沒有版本衝突,沒有依賴地獄。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一致性(Consistency)","attrs":{}},{"type":"text","text":":當你把所有代碼庫放在一個地方時,執行代碼質量標準和統一的風格會更容易。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"共享時間線(Shared timeline)","attrs":{}},{"type":"text","text":":API或共享庫的變更會立即被暴露出來,迫使不同團隊提前溝通合作,每個人都得努力跟上變化。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"原子提交(Atomic commits)","attrs":{}},{"type":"text","text":":原子提交使大規模重構更容易,開發人員可以在一次提交中更新多個包或項目。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"隱式CI(Implicit CI)","attrs":{}},{"type":"text","text":":因爲所有代碼已經統一維護在一個地方,因此可以保證持續集成","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[3]","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"統一的CI/CD(Unified CI/CD)","attrs":{}},{"type":"text","text":":可以爲代碼庫中的每個項目使用相同的CI/CD","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[4]","attrs":{}}],"attrs":{}},{"type":"text","text":"部署流程。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"統一的構建流程(Unified build process)","attrs":{}},{"type":"text","text":":代碼庫中的每個應用程序可以共享一致的構建流程","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[5]","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"單一代碼庫的缺陷","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨着單一代碼庫的發展,我們在版本控制工具、構建系統和持續集成流水線方面達到了設計極限。這些問題可能會讓一家公司走上多代碼庫的道路:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"性能差(Bad performance)","attrs":{}},{"type":"text","text":":單一代碼庫難以擴大規模,像git blame這樣的命令可能會不合理的花費很長時間執行,IDE也開始變得緩慢,生產力受到影響,對每個提交測試整個repo變得不可行。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"破壞主線(Broken main/master)","attrs":{}},{"type":"text","text":":主線損壞會影響到在單一代碼庫中工作的每個人,這既可以被看作是災難,也可以看作是保證測試既可以保持簡潔又可以跟上開發的好機會。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"學習曲線(Learning curve)","attrs":{}},{"type":"text","text":":如果代碼庫包含了許多緊密耦合的項目,那麼新成員的學習曲線會更陡峭。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"大量的數據(Large volumes of data)","attrs":{}},{"type":"text","text":":單一代碼庫每天都要處理大量的數據和提交。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"所有權(Ownership)","attrs":{}},{"type":"text","text":":維護文件的所有權更有挑戰性,因爲像Git或Mercurial這樣的系統沒有內置的目錄權限。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Code reviews","attrs":{}},{"type":"text","text":":通知可能會變得非常嘈雜。例如,GitHub有有限的通知設置,不適合大量的pull request和code review。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可能已經注意到,這些問題中的大多數都和技術有關。在下面的章節中,我們將瞭解堅持使用單一代碼庫的公司是如何通過投資工具、添加集成以及編寫定製解決方案來解決大部分問題的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"這不僅僅是技術問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"選擇代碼庫策略不僅是一個技術問題,也是關於人們如何交流的問題。正如","attrs":{}},{"type":"link","attrs":{"href":"https://www.jianshu.com/p/c9ed9eeaac1f","title":null,"type":null},"content":[{"type":"text","text":"康威定律","attrs":{}}]},{"type":"text","text":"所言,溝通對於創造偉大的產品至關重要:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"任何組織所設計的系統(此處的定義比信息系統寬泛得多)架構,都不可避免的反映爲該組織溝通結構的副本。——康威定律","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然多代碼倉庫允許每個團隊獨立管理他們的項目,但同時也阻礙了協作。它們就像眼罩一樣,讓開發人員只關注自己所擁有的部分,而忽略了整體。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一方面,單一代碼庫就像一個樞紐、一個廣場,每個開發人員、工程師、測試人員和業務分析師都可以在這裏會面和交談。單一代碼庫鼓勵對話,幫助我們消除“豎井”。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Monorepo文化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Monorepos已經存在很長時間了。三十年來,FreeBSD一直使用CVS和後來的subversion monorepos","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[6]","attrs":{}}],"attrs":{}},{"type":"text","text":"進行開發和包分發。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"許多開源項目已經成功使用了單一代碼庫。例如:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Laravel","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[7]","attrs":{}}],"attrs":{}},{"type":"text","text":":一個用於Web開發的PHP框架。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Symfony","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[8]","attrs":{}}],"attrs":{}},{"type":"text","text":":一個用PHP編寫的MVC框架。有趣的是,他們已經爲每個Symfony工具和庫創建了只讀存儲庫,這種方法被稱爲","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"分庫(split-repo)","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"NixOS","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[9]","attrs":{}}],"attrs":{}},{"type":"text","text":":一個用單一代碼庫發佈包的Linux發行版","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Babel","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[10]","attrs":{}}],"attrs":{}},{"type":"text","text":":一個用戶Web開發的流行的JavaScript編譯器,其單一代碼庫包含了完整的項目及其所有插件。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,React","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[11]","attrs":{}}],"attrs":{}},{"type":"text","text":"、Ember","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[12]","attrs":{}}],"attrs":{}},{"type":"text","text":"和Meteor","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[13]","attrs":{}}],"attrs":{}},{"type":"text","text":"等前端框架都使用單一代碼庫。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然而,真正的問題是商業軟件是否能從單一代碼庫中獲益。考慮到這些優點和缺點,讓我們來看一些已經嘗試過的公司的經驗。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Segment,告別多代碼庫","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Alex Noonan講述了一個關於告別多代碼庫的故事","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[14]","attrs":{}}],"attrs":{}},{"type":"text","text":"。她所在的公司Segment.com提供活動收集和轉發服務,每個客戶都需要使用一種特殊格式的數據。因此,工程團隊最初決定混合使用微服務和多代碼庫。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這一策略效果很好——隨着客戶基數的增長,他們擴大了規模,沒有出現問題。但是,當轉發目的地的數量超過100個時,事情開始變得糟糕起來。維護、測試和部署超過140個代碼庫(每個代碼庫都有數百個日益分化的依賴關係)的管理負擔太高了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“最終,團隊發現他們無法取得進展,三個全職工程師花費了大部分時間來維持系統的運行。”","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於Segment來說,補救辦法就是合併,將所有的服務和依賴遷移到一個單一代碼庫中。他們必須協調共享庫並且測試所有內容,雖然花了很大的代價,但遷移非常成功,最終的結果是降低了複雜性,增加了可維護性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“更快的速度就是證據。[…] 我們在過去6個月裏對庫的改進比2016年全年都要多。”","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多年後,當一個小組詢問她在微服務方面的經驗時","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[15]","attrs":{}}],"attrs":{}},{"type":"text","text":",Alex解釋了她遷移到單一代碼庫的原因:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“這並沒有像我們想象的那樣成爲我們的優勢。我們改變的主要動機是因爲失敗的測試會影響到不同的東西。 [..] 把它們分成單獨的代碼庫只會讓情況變得更糟,因爲有可能你會過了6個月才集成某個庫的更新,結果測試完全被破壞了,而你又不想花時間去修復。我見過的成功分解爲獨立代碼庫而不是服務的案例之一是,當我們有一塊代碼在多個服務之間共享時,我們想讓它成爲一個共享庫。除此之外,我們發現,即使轉向微服務,我們仍然更喜歡單一代碼庫。”","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Airbnb和monorail","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Airbnb的基礎設施工程師延斯·範德海格(Jens Vanderhaeghe)也講述了微服務和單一代碼庫是如何幫助他們向全球擴張的","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[16]","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Airbnb最初的版本被稱爲“monorail”,是一個獨立的Ruby on Rails應用程序。當公司開始指數級增長時,代碼庫也隨之增長。當時,Airbnb實施了一項名爲“","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"民主發佈(democratic releases)","attrs":{}},{"type":"text","text":"”的新發佈政策,意味着任何開發者都可以在任何時候發佈產品。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨着Airbnb的擴張,民主程序的限制也受到了考驗,合併更改變得越來越困難。Jens的團隊實施了一些緩解措施,比如合併隊列和增強監控。這在一段時間內有幫助,但從長遠來看還是不夠。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Airbnb的工程師們爲維持monorail系統進行了英勇的鬥爭,但最終,經過數週的辯論,他們決定將該應用分割爲多個微服務。因此,他們創建了兩個單一代碼庫:一個用於前端,一個用於後端。兩者都包含數百個服務、文檔、用於部署的Terraform和Kubernetes資源以及所有維護工具。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當被問及單一代碼庫的優勢時,Jens說道:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“我們不想處理所有這些微服務之間的版本依賴關係。[使用單一代碼庫]你可以通過一個提交在兩個微服務之間做出改變。[..] 我們可以圍繞一個代碼庫構建所有工具。最大的賣點是你可以同時在多個微服務上做出改變。我們通過腳本檢測單一代碼庫中的哪些應用程序會受到影響,然後部署這些應用程序。我們獲得的主要好處是源代碼控制。”","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Uber,來來回回","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來自優步(Uber)的艾米•盧西多(Aimee Lucido)描述了從單一代碼庫到多代碼庫再切換回來的過程","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[17]","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當時,她正在Android客戶團隊工作。他們從一開始就使用單一代碼庫,但是經過5年的快速發展,單一代碼庫的問題開始顯現出來。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“我們開始遭遇可怕的IDE死鎖,我們甚至不能在Android Studio中翻看代碼,否則IDE就會沒有任何反應。”","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"問題並不只是發生在IDE,也影響到Git,構建過程一拖再拖。更糟糕的是,他們經常會破壞主線,這讓他們無法構建任何東西。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“公司規模越大,就越頻繁地遇到主線被破壞的情況。”","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當Uber達到中等規模時,團隊決定切換到多代碼庫,這解決了很多問題。Uber的工程師們喜歡這樣的狀態:他們可以擁有一部分代碼,並且只對這部分代碼負責。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“如果你只構建地圖應用,那麼你可以很快構建出來,這太棒了。”","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但故事並沒有到此結束。又過了一段時間,多代碼庫策略開始顯示出它的弱點。這一次不僅僅是關於技術問題,而是關於人們如何合作。團隊被分解成多個豎井,管理數千個代碼庫的開銷消耗了大量寶貴的時間。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個團隊都有自己的編碼風格、框架和測試實踐。管理依賴關係也變得更加困難,依賴關係的惡魔擡起了它醜陋的頭,這使得最終將所有內容整合到一個產品中變得非常困難。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就在這個時候,Uber的工程師們再次合作,決定再給單一代碼庫一次機會。有了更多的資源並且提前知道他們將面臨的問題,他們選擇投資於工具:他們修改了IDE,實現了合併隊列,並使用增量構建來加快部署。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“當你發展到一個大公司的規模時,可以投入資源,讓你的大公司感覺像一個小公司,把缺點變成優點。”","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Pinterest,全力投資單一代碼庫","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讓我們以一家正在經歷三年轉型的公司作爲總結:Pinterest。他們的努力包括了兩個方面。首先,將1300多個代碼庫切換到四個單一代碼庫中。其次,將數百個依賴項整合到一個完整的Web應用程序中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"他們爲什麼要這麼做?Eden JnBaptiste","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[18]","attrs":{}}],"attrs":{}},{"type":"text","text":"解釋說,多代碼庫使得他們很難重用代碼。情況是一樣的:代碼太過分散,每個團隊都有自己的倉庫,有自己的風格和結構。構建過程質量標準是高度可變的,構建和部署變得非常困難。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Pinterest發現,基於主幹開發","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[19]","attrs":{}}],"attrs":{}},{"type":"text","text":"配合單一代碼庫有助於取得進展。基於主幹的開發的基礎是隻使用臨時分支,儘可能頻繁的合併到主分支上,從而減少合併衝突的機會。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“將所有代碼放在一個倉庫中有助於我們減少(構建系統中的)反饋循環。”","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於Pinterest,單一代碼庫提供了一致的開發工作流。發佈實踐的自動化、簡化和標準化允許他們減少文檔,讓開發人員專注於編寫代碼。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"投資於工具","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果我們必須從所有這些故事中吸取一個教訓,那就是正確的工具是有效的單一代碼庫的關鍵——需要重寫思考構建和測試。我們可以使用智能構建系統,而不是在每次更新時重新構建完整的倉庫,它需要了解項目結構,並且只對自上次提交以來變更的部分進行操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/94/942135e269f5e89fb0ba16d7b05400c1.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們大多數人都沒有谷歌或Facebook的資源。那我們能做什麼呢?幸運的是,許多大公司的構建系統都是開源的:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Bazel","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[20]","attrs":{}}],"attrs":{}},{"type":"text","text":":由谷歌發佈,部分基於他們自己的構建系統(Blaze)。Bazel支持多種語言,並支持大規模構建和測試。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Buck","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[21]","attrs":{}}],"attrs":{}},{"type":"text","text":": Facebook開源的快速構建系統,支持在多種語言和平臺上進行不同的構建。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Pands","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[22]","attrs":{}}],"attrs":{}},{"type":"text","text":":Pands構建系統是與Twitter、Foursquare和Square合作創建的。目前只支持Python,後續還將支持更多的語言。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RushJS","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[23]","attrs":{}}],"attrs":{}},{"type":"text","text":":微軟用於JavaScript的可擴展的單一代碼庫管理器,能夠從一個代碼庫構建和部署多個包。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Monorepos正在獲得越來越多的關注,尤其是在JavaScript方面,如下項目所示:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Lerna","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[24]","attrs":{}}],"attrs":{}},{"type":"text","text":":JavaScript的單一代碼庫管理器,可以與React、Angular或Babel等流行框架集成。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Yarn workspace","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[25]","attrs":{}}],"attrs":{}},{"type":"text","text":":用一個命令在多個地方安裝和更新Node.js的依賴項。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ultra-runner","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[26]","attrs":{}}],"attrs":{}},{"type":"text","text":":JavaScript單一代碼庫管理腳本,支持Yarn、pnpm和Lerna插件,支持並行構建。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Monorepo builder","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[27]","attrs":{}}],"attrs":{}},{"type":"text","text":":通過單一代碼庫安裝和更新PHP包。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"擴展代碼庫","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"源代碼控制是單一代碼庫的另一個痛點,這些工具可以幫助您擴展存儲庫:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Virtual Filesystem for Git(VFS)","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[28]","attrs":{}}],"attrs":{}},{"type":"text","text":":爲Git添加流支持。VFS可以根據需要從Git倉庫下載對象。這個項目最初是爲了管理Windows代碼庫(最大的Git倉庫)而創建的,只能在Windows上使用,但已經宣佈要支持MacOS。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Large File Storage","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[29]","attrs":{}}],"attrs":{}},{"type":"text","text":":Git的開源擴展,爲大文件提供了更好的支持。安裝了這個擴展,就可以跟蹤任何類型的文件,並將它們無縫的上傳到雲存儲中,從而釋放代碼庫的空間,使push和pull的速度更快。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mercurial","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[30]","attrs":{}}],"attrs":{}},{"type":"text","text":":Git的替代方案,Mercurial是一個分佈式版本控制工具,專注於速度。Facebook使用Mercurial,多年來已經發布了許多提高速度的補丁","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[31]","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Git CODEOWNERS","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[32]","attrs":{}}],"attrs":{}},{"type":"text","text":":允許定義哪個團隊擁有代碼庫中的子目錄,當有人提交pull request或push代碼到受保護的分支時,代碼所有者會自動被要求進行審查。GitHub和GitLab都支持這一特性。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Monorepo管理最佳實踐","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於這些單一代碼庫的故事,我們可以定義一套最佳實踐:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義一個便於探索的統一的目錄組織。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"維護分支整潔,保持較小的分支,考慮採用基於主幹的開發。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲每個項目使用固定的依賴項,一次升級所有依賴項,迫使每個項目都跟上依賴項。只爲真正的例外情況保留例外。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果正在使用Git,學習如何使用shallow clone","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[33]","attrs":{}}],"attrs":{}},{"type":"text","text":"和filter-branch","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[34]","attrs":{}}],"attrs":{}},{"type":"text","text":"來處理大容量代碼庫。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"貨比三家,尋找像Bazel或Buck這樣的智能構建系統,以加速構建和測試。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果需要限制對某些項目的訪問,請使用CODEOWERS。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用Semaphore","attrs":{}},{"type":"sup","content":[{"type":"text","text":"[35]","attrs":{}}],"attrs":{}},{"type":"text","text":"等CI/CD雲平臺來大規模測試和部署應用程序。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"你應該使用單一代碼庫嗎?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看情況而定,沒有適合所有用例的答案,有些公司可能會暫時選擇單一代碼庫,然後決定需要轉向多代碼庫,或者相反,而其他人可能會選擇混合代碼庫。當你有疑問的時候,考慮一下從單一代碼庫到多代碼庫通常比反過來要更容易。但千萬不要忽視這一點,最終,這與技術無關,而是與工作文化和溝通有關。所以,根據你想要的工作方式來做決定。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"References:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[1] ","attrs":{}},{"type":"link","attrs":{"href":"https://semaphoreci.com/blog/what-is-monorepo","title":"","type":null},"content":[{"type":"text","text":"https://semaphoreci.com/blog/what-is-monorepo","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[2] ","attrs":{}},{"type":"link","attrs":{"href":"https://semaphoreci.com/blog/cicd-pipeline","title":"","type":null},"content":[{"type":"text","text":"https://semaphoreci.com/blog/cicd-pipeline","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[3] ","attrs":{}},{"type":"link","attrs":{"href":"https://semaphoreci.com/continuous-integration","title":"","type":null},"content":[{"type":"text","text":"https://semaphoreci.com/continuous-integration","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[4] ","attrs":{}},{"type":"link","attrs":{"href":"https://semaphoreci.com/cicd","title":"","type":null},"content":[{"type":"text","text":"https://semaphoreci.com/cicd","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[5] ","attrs":{}},{"type":"link","attrs":{"href":"https://semaphoreci.com/blog/build-stage","title":"","type":null},"content":[{"type":"text","text":"https://semaphoreci.com/blog/build-stage","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[6] ","attrs":{}},{"type":"link","attrs":{"href":"https://docs.freebsd.org/en_US.ISO8859-1/articles/committers-guide/article.html","title":"","type":null},"content":[{"type":"text","text":"https://docs.freebsd.org/en_US.ISO8859-1/articles/committers-guide/article.html","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[7] ","attrs":{}},{"type":"link","attrs":{"href":"https://laravel.com/","title":"","type":null},"content":[{"type":"text","text":"https://laravel.com/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[8] ","attrs":{}},{"type":"link","attrs":{"href":"https://symfony.com/","title":"","type":null},"content":[{"type":"text","text":"https://symfony.com/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[9] ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/NixOS/nixpkgs/","title":"","type":null},"content":[{"type":"text","text":"https://github.com/NixOS/nixpkgs/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[10] ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/babel/babel/blob/master/doc/design/monorepo.md","title":"","type":null},"content":[{"type":"text","text":"https://github.com/babel/babel/blob/master/doc/design/monorepo.md","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[11] ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/facebook/react/tree/master/packages","title":"","type":null},"content":[{"type":"text","text":"https://github.com/facebook/react/tree/master/packages","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[12] ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/emberjs/ember.js/tree/master/packages","title":"","type":null},"content":[{"type":"text","text":"https://github.com/emberjs/ember.js/tree/master/packages","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[13] ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/meteor/meteor/tree/devel/packages","title":"","type":null},"content":[{"type":"text","text":"https://github.com/meteor/meteor/tree/devel/packages","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[14] ","attrs":{}},{"type":"link","attrs":{"href":"https://segment.com/blog/goodbye-microservices/","title":"","type":null},"content":[{"type":"text","text":"https://segment.com/blog/goodbye-microservices/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[15] ","attrs":{}},{"type":"link","attrs":{"href":"https://www.infoq.com/articles/microservices-from-trenches-lessons-challenges/","title":"","type":null},"content":[{"type":"text","text":"https://www.infoq.com/articles/microservices-from-trenches-lessons-challenges/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[16] ","attrs":{}},{"type":"link","attrs":{"href":"https://www.youtube.com/watch?v=sakGeE4xVZs","title":"","type":null},"content":[{"type":"text","text":"https://www.youtube.com/watch?v=sakGeE4xVZs","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[17] ","attrs":{}},{"type":"link","attrs":{"href":"https://www.youtube.com/watch?v=lV8-1S28ycM","title":"","type":null},"content":[{"type":"text","text":"https://www.youtube.com/watch?v=lV8-1S28ycM","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[18] ","attrs":{}},{"type":"link","attrs":{"href":"https://www.youtube.com/watch?v=r5KHQnS6uP8","title":"","type":null},"content":[{"type":"text","text":"https://www.youtube.com/watch?v=r5KHQnS6uP8","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[19] ","attrs":{}},{"type":"link","attrs":{"href":"https://trunkbaseddevelopment.com/","title":"","type":null},"content":[{"type":"text","text":"https://trunkbaseddevelopment.com/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[20] ","attrs":{}},{"type":"link","attrs":{"href":"https://bazel.build/","title":"","type":null},"content":[{"type":"text","text":"https://bazel.build/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[21] ","attrs":{}},{"type":"link","attrs":{"href":"https://buck.build/","title":"","type":null},"content":[{"type":"text","text":"https://buck.build/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[22] ","attrs":{}},{"type":"link","attrs":{"href":"http://www.pantsbuild.org/","title":"","type":null},"content":[{"type":"text","text":"http://www.pantsbuild.org/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[23] ","attrs":{}},{"type":"link","attrs":{"href":"https://rushjs.io/","title":"","type":null},"content":[{"type":"text","text":"https://rushjs.io/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[24] ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/lerna/lerna","title":"","type":null},"content":[{"type":"text","text":"https://github.com/lerna/lerna","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[25] ","attrs":{}},{"type":"link","attrs":{"href":"https://classic.yarnpkg.com/en/docs/workspaces/","title":"","type":null},"content":[{"type":"text","text":"https://classic.yarnpkg.com/en/docs/workspaces/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[26] ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/folke/ultra-runner","title":"","type":null},"content":[{"type":"text","text":"https://github.com/folke/ultra-runner","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[27] ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/Symplify/MonorepoBuilder","title":"","type":null},"content":[{"type":"text","text":"https://github.com/Symplify/MonorepoBuilder","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[28] ","attrs":{}},{"type":"link","attrs":{"href":"https://vfsforgit.org/","title":"","type":null},"content":[{"type":"text","text":"https://vfsforgit.org/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[29] ","attrs":{}},{"type":"link","attrs":{"href":"https://git-lfs.github.com/","title":"","type":null},"content":[{"type":"text","text":"https://git-lfs.github.com/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[30] ","attrs":{}},{"type":"link","attrs":{"href":"https://www.mercurial-scm.org/","title":"","type":null},"content":[{"type":"text","text":"https://www.mercurial-scm.org/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[31] ","attrs":{}},{"type":"link","attrs":{"href":"https://engineering.fb.com/2014/01/07/core-data/scaling-mercurial-at-facebook/","title":"","type":null},"content":[{"type":"text","text":"https://engineering.fb.com/2014/01/07/core-data/scaling-mercurial-at-facebook/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[32] ","attrs":{}},{"type":"link","attrs":{"href":"https://help.github.com/articles/about-codeowners/","title":"","type":null},"content":[{"type":"text","text":"https://help.github.com/articles/about-codeowners/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[33] ","attrs":{}},{"type":"link","attrs":{"href":"https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/","title":"","type":null},"content":[{"type":"text","text":"https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[34] ","attrs":{}},{"type":"link","attrs":{"href":"https://git-scm.com/docs/git-filter-branch","title":"","type":null},"content":[{"type":"text","text":"https://git-scm.com/docs/git-filter-branch","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[35] ","attrs":{}},{"type":"link","attrs":{"href":"https://semaphoreci.com/","title":"","type":null},"content":[{"type":"text","text":"https://semaphoreci.com/","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你好,我是俞凡,在Motorola做過研發,現在在Mavenir做技術工作,對通信、網絡、後端架構、雲原生、DevOps、CICD、區塊鏈、AI等技術始終保持着濃厚的興趣,平時喜歡閱讀、思考,相信持續學習、終身成長,歡迎一起交流學習。微信公衆號:DeepNoMind","attrs":{}}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章