原文標題:How to break a Monolith into Microservices
原文連接: https://martinfowler.com/arti...
注:每一段譯文後跟做者原文,原文中可能包含着做者所提到的內容的跳轉超連接。
隨着整個系統變得過於龐大而難以應對,許多企業開始傾向於將其分解開來,轉變爲微服務架構。這是一個頗有價值的旅程,但也並非一件很容易完成的事。咱們瞭解到,爲了實現這個目標,須要從一個簡單的服務開始,而後根據各部分的垂直業務功能,不斷抽出對於系統業務來講相當重要並須要常常變動的各類服務。在最初階段,這些服務應該會很大,而且最好不依賴於還沒有分離的大型單體系統本體。咱們應該確保每一步遷移都是對整個架構的原子性演進。html
As monolithic systems become too large to deal with, many enterprises are drawn to breaking them down into the microservices architectural style. It is a worthwhile journey, but not an easy one. We've learned that to do this well, we need to start with a simple service, but then draw out services that are based on vertical capabilities that are important to the business and subject to frequent change. These services should be large at first and preferably not dependent upon the remaining monolith. We should ensure that each step of migration represents an atomic improvement to the overall architecture.前端
從一整塊單體系統遷移到微服務生態系統簡直是一段史詩般的旅程。走上這條征途的人們指望能擴張業務規模,加快改變步伐,而且避免高成本的變革。他們但願擴張團隊的數量,而且使各個團隊可以並行且獨立地產出價值。他們但願快速對其核心業務功能點進行嘗試,並更快地產生價值。 他們也但願擺脫與修改和維護現有大型單體系統相關的高成本。ios
Migrating a monolithic system to an ecosystem of microservices is an epic journey. The ones who embark on this journey have aspirations such as increasing the scale of operation, accelerating the pace of change and escaping the high cost of change. They want to grow their number of teams while enabling them to deliver value in parallel and independently of each other. They want to rapidly experiment with their business's core capabilities and deliver value faster. They also want to escape the high cost associated with making changes to their existing monolithic systems.web
在向微服務架構遷移的過程當中,決定在何時以什麼樣的方式進行增量遷移是一個架構上的挑戰。 在這篇文章中,我將分享一些技巧,能夠指導交付團隊(包括開發人員、架構師和技術經理 )作出這些決定。後端
Deciding what capability to decouple when and how to migrate incrementally are some of the architectural challenges of decomposing a monolith to an ecosystem of microservices. In this write-up, I share a few techniques that can guide the delivery teams - developers, architects, technical managers - to make these decomposition decisions along the journey.api
爲了闡述本文所關注的技術,我將以一個三層架構的在線零售系統爲例,這個系統的UI層、業務邏輯層和數據層牢牢地耦合在了一塊兒。我選擇它的緣由,是由於這個架構具備不少企業正在運營的大型單體系統的表明性,而且其所採用的技術棧足夠現代,能夠說明咱們應該對其解耦,而不是重寫或者直接替換。緩存
To clarify the techniques I use a multitier online retail application. This application tightly couples user facing, business logic and data layer. The reason I have chosen this example is that its architecture has the characteristics of monolithic applications that many businesses run and its technology stack is modern enough to justify decomposition instead of a complete rewrite and replacement.安全
在咱們開始以前,每一個人都對微服務生態系統有一個共同的理解是很重要的。所謂微服務生態系統,是指封裝了各個業務功能的服務平臺。而所謂業務功能,是指企業在特定領域爲實現特定目標和責任所作的事情。每一個微服務都將公開一個API,開發人員能夠以"自助"的方式發現和使用這些API。微服務具備獨立的生命週期,開發人員們能夠獨立構建、測試和發佈各個微服務。在微服務生態系統的組織結構內,各個團隊都是獨立並長期負責一個或多個服務的。與廣泛認知的微服務的「微」字可能不一樣的是,各個服務的規模可能因組織的運營成熟度而異。正如Martin Fowler所說,「微服務是一種標籤,而不是描述」。網絡
Before embarking, it is critical that everyone has a common understanding of a microservices ecosystem. Microservices ecosystem is a platform of services each encapsulating a business capability. A business capability represents what a business does in a particular domain to fulfill its objectives and responsibilities. Each microservice expose an API that developers can discover and use in a self-serve manner. Microservices have independent lifecycle. Developers can build, test and release each microservice independently. The microservices ecosystem enforces an organizational structure of autonomous long standing teams, each responsible for one or multiple services. Contrary to general perception and ‘micro’ in microservices, the size of each service matters least and may vary depending on the operational maturity of the organization. As Martin Fowler puts it, "microservices is a label and not the description".session
圖1:服務封裝了業務功能,並經過自助的服務API公開數據和功能
Figure 1: Services encapsulate business capabilities, expose data and functionality through self-serve APIs
在深刻研究以前,重要的是要知道將現有系統分解爲微服務可能會帶來很高的整體成本,而且可能須要通過不少次迭代才能實現。 開發人員和架構師必須仔細評估是否應該對當前系統進行分解,以及判斷對於當前系統來講微服務自己是不是正確的發展方向。在想清楚這個問題以後,我們開始吧。
Before diving into the guide, it is important to know that there is a high overall cost associated with decomposing an existing system to microservices and it may take many iterations. It is necessary for developers and architects to closely evaluate whether the decomposition of an existing monolith is the right path, and whether the microservices itself is the right destination. Having cleared that out, let’s go through the guide.
只要開始了微服務,就至少須要有最低水平的運維。這須要部署按需訪問的環境、須要構建新的持續集成系統以實現服務的獨立構建、測試和部署,以及須要一個安全的、可調試的、可監控的分佈式體系結構。不管咱們正在構建新的服務,仍是在對現行系統進行解耦,對於運維能力都是有要求的。有關這一點的更多信息,請參閱Martin Fowler關於微服務的先決條件的文章。好消息是,自從Martin撰文以來,微服務架構的運維技術有了迅速發展,其中包括建立被稱爲「服務網絡」(Service Mesh)的專用基礎設施層,能夠運行快速、可靠和安全的微服務網絡,能夠建立提供更高級別的基礎設施抽象的「容器編排系統"、以及諸如GoCD等在容器中構建、測試和部署微服務的持續集成系統的發展。
Starting down a microservices path requires a minimum level of operational readiness. It requires on demand access to deployment environment, building new kinds of continuous delivery pipelines to independently build, test, and deploy executable services, and the ability to secure, debug and monitor a distributed architecture. Operational readiness maturity is required whether we are building greenfield services or decomposing an existing system. For more on this operational readiness see Martin Fowler’s article on Microservices prerequisites. The good news is that since Martin’s article, the technology to operate a microservices architecture has evolved rapidly. This includes creation of Service Mesh, a dedicated infrastructure layer to run fast, reliable and secure network of microservices, container orchestration systems to provide a higher level of deployment infrastructure abstraction, and evolution of continuous delivery systems such as GoCD to build, test and deploy microservices as containers.
個人建議是,開發人員和運維團隊首先能夠從微服務的底層基礎設施,諸如持續集成系統和API管理系統開始構建。從這些與老系統分離的功能點開始構建微服務,不須要對當前正在運行的面向用戶的業務進行更改,甚至可能都不須要進行數據存儲。對於交付團隊來講,此時須要不斷優化的是對他們的交付方法進行驗證、對團隊成員能力進行提高、構建出最基本的能夠獨立部署的安全服務的基礎設施,從而能夠對外公開服務的API。例如對於在線零售系統來講,首先能夠從老系統中分離出來的是用戶認證鑑權服務,以及對於新的客戶程序來講能夠提供更好的直觀展示的「客戶信息」服務。
My suggestion is for developers and operation teams to build out the underlying infrastructure, continuous delivery pipelines and the API management system with the first and second service that they decompose or build new. Start with capabilities that are fairly decoupled from the monolith, they don’t require changes to many client facing applications that are currently using the monolith and possibly don’t need a data store. What the delivery teams are optimizing for at the point is validating their delivery approaches, upskilling the team members, and building out minimum infrastructure needed to deliver independently deployable secure services that expose self-serve APIs. As an example, for an online retail application, the first service can be the ‘end user authentication’ service that the monolith could call to authenticate the end users, and the second service could be the ‘customer profile’ service, a facade service providing a better view of the customers for new client applications.
首先,我建議從簡單的邊緣服務開始解耦。在此以後,再採用不一樣的方法深刻單體系統解耦其他功能。我建議先作邊緣服務開始的緣由,是由於在剛開始時,交付團隊的最大風險是沒法正確地掌握微服務的運維。所以,爲了掌握必須的「運維先決條件」,從邊緣服務開始練手是一個很好的選擇。而 一旦這個問題獲得解決,剩下的問題就能夠迎刃而解。
First I recommended decoupling simple edge services. Next we take a different approach decoupling capabilities deeply embedded in the monolithic system. I advise doing edge services first because at the beginning of the journey, the delivery teams' biggest risk is failing to operate the microservices properly. So it’s good to use the edge services to practice the operational prerequisites they need. Once they have addressed that, they can then address the key problem of splitting the monolith.
圖2:從簡單功能開始鍛鍊運維能力
Figure 2: Warming up with a simple capability that has a small radius of change to build our operational readiness
將新的微服務系統與老系統之間的依賴關係最小化是一個基本原則。微服務的一個主要優勢是具備快速和獨立的發佈週期。一旦與老系統之間有任何依賴,如數據、邏輯或API,都將致使微服務系統與老系統的發佈週期相耦合,從而使得微服務的發佈優點不復存在。一般,擺脫老系統的主要動機就是其成本之高昂,以及深鎖於其中的業務功能的更新之緩慢。因此,咱們但願逐步經過減小對老系統的依賴關係的方式,將核心業務解耦。若是團隊可以遵循這些原則,將業務功能寫進本身的服務中,依賴關係就能從對單體系統的依賴轉變爲對微服務的依賴。這是一個理想的依賴方向,由於它將不會拖慢新服務的更新速度。
As a founding principle the delivery teams need to minimize the dependencies of newly formed microservices to the monolith. A major benefit of microservices is to have a fast and independent release cycle. Having dependencies to the monolith - data, logic, APIs - couples the service to the monolith's release cycle, prohibiting this benefit. Often the main motivation for moving away from the monolith is the high cost and slow pace of change of the capabilities locked in it, so we want to progressively move in a direction that decouples these core capabilities by removing dependencies to the monolith. If the teams follow this guideline as they build out capabilities into their own services, what they find is instead, dependencies in the reverse direction, from the monolith to the services. This is a desired dependency direction as it does not slow down the pace of change for new services.
在在線零售系統中,購買和促銷是核心功能。購買功能將在結算過程當中使用促銷功能,爲顧客提供他們能夠得到的最佳促銷方案,並給他們所購買的商品。若是咱們須要決定在這兩種功能之中哪個先解耦,個人建議是先解耦促銷,再解耦購買。由於經過這個順序,咱們能夠減小對老系統的依賴。在這個順序中,購買功能將會先被鎖定在總體結構中,並依賴於新的促銷微服務。
Consider in a retail online system, where ‘buy’ and ‘promotions’ are core capabilities. ‘buy’ uses ‘promotions’ during the checkout process to offer the customers the best promotions that they qualify for, given the items they are buying. If we need to decide which of these two capabilities to decouple next, I suggest to start with decoupling ‘promotions’ first and then 'buy'. Because in this order we reduce the dependencies back to the monolith. In this order ‘buy’ first remains locked in the monolith with a dependency out to the new ‘promotions’ microservice.
下一條準則提供了另外一些方法來決定服務的解耦順序。這意味着,可能並非總能找到一個能夠完全避免對老系統的依賴的方案。若是新服務最終依然仍是調用到了老系統,我建議在老系統中開發新的API,並經過新服務的防腐層來訪問這些API,以確保老系統中的概念不會直接暴露出來。就算老系統的內部可能實現並非這樣的,也應該致力於定義一個可以反應領域的明確概念和其結構的API。在這種不幸的狀況下,交付團隊可能須要直面困難,承擔修改老系統的成本,進行測試和發佈與老系統耦合在一塊兒的新服務。
Next guidelines offer other ways for deciding the order in which developers decouple services. This means that they may not be always able to avoid dependencies back to the monolith. In cases where a new service ends up with a call back to the monolith, I suggest to expose a new API from the monolith, and access the API through an anti-corruption layer in the new service to make sure that the monolith concepts do not leak out. Strive to define the API reflecting the well defined domain concepts and structures, even though the monolith’s internal implementation might be otherwise. In this unfortunate case the delivery teams will be bearing the cost and difficulty of changing the monolith, testing and releasing the new services coupled with the monolith release.
圖3:解耦沒有依賴關係的服務,並儘可能減小對老系統的修改
Figure 3: Decouple the service that doesn’t require a dependency back to the monolith first and minimize changes to the monolith
首先咱們假設,交付團隊樂意於構建微服務,並準備解決遇到的棘手的問題。然而,交付團隊極可能會發現本身缺少將服務解耦到對老系統再也不有依賴關係的能力。形成這種狀況的根本緣由,每每是由於在總體架構中的某個功能模塊的設計上存在問題,沒有被很好地定義爲領域概念,致使系統中大量的功能都依賴於它。爲了可以將解耦進行下去,開發人員須要識別出這些「粘性功能(Sticky Capabilities)」,將其解構爲擁有良好定義的領域概念,並將這些領域概念轉化爲單獨的服務。
I am assuming that at this point the delivery teams are comfortable with building microservices and ready to attack the sticky problems. However they may find themselves limited with the capabilities that they can decouple next without a dependency back to the monolith. The root cause of this, is often a capability within the monolith that is leaky, not well defined as a domain concept, with many of the monolith capabilities depending on it. In order to be able to progress, the developers need to identify the sticky capability, deconstruct it into well defined domain concepts and then reify those domain concepts into separate services.
好比在一個網站的單體系統中,會話(Session)是最多見的耦合因素之一。在在線零售系統的示例中,Session中一般存放着許多東西,從跨越了多個域邊界的用戶偏好(好比物流和支付的偏好設置)到用戶的意圖和用戶交互(好比最近訪問的頁面,瀏覽過的產品和願望清單)。若是咱們不進行解耦、重構當前的會話概念,咱們將很難解耦剩下的更多功能,由於它們經過四處瀰漫着的Session與老系統牢牢地耦合在了一塊兒。 同時,我也不鼓勵在總體框架以外另外建立一個「會話」服務,由於它只會致使相似的強耦合。相比之下,目前這種耦合關係僅存在於總體的業務流程之中,更糟糕的是,讓它散佈到業務流程以外乃至整個網絡中。
For example in a web based monolith, the notion of ‘(web) session’ is one of those most common coupling factors. In the online retail example, the session is often a bucket for many attributes ranging from user preferences across different domain boundaries such as shipping and payment preferences, to user intentions and interactions such as recently visited pages, clicked products, and wish list. Unless we tackle decoupling, deconstructing and reifying the current notion of ‘session’, we will struggle to decouple many of the future capabilities as they will be entangled with the monolith through the leaky session concepts. I also discourage creating a ‘session’ service outside of the monolith, as it will just result in a similar tight coupling that currently exist within the monolith process, only worse, out of process and across the network.
開發人員能夠逐步地從粘性功能中將微服務提取出來,即一次只提供一個服務。例如,首先重構「願望清單」並將其提取到新服務中,而後將「默認付款方式」重構爲另外一個微服務,並以此類推。
Developers can incrementally extract microservices from the sticky capability, one service at time. As an example, refactor 'customer wish list' first and extract that into a new service, then refactor 'customer payment preferences' into another microservice and repeat.
圖4:找到最多的耦合概念並將其解耦重構爲具體的領域服務
Figure 4: Identify the most coupling concept and decouple, deconstruct and reify into concrete domain services
使用依賴性和結構化代碼分析工具(如Structure101)來肯定總體結構中最具耦合性和約束性的業務功能。Use dependency and structural code analysis tools such as Structure101 to identify the most coupling and constraining factor capabilities in the monolith.
解耦業務功能的主要驅動力就是讓它們能夠獨立發佈。它做爲第一原則,指導開發人員如何進行解耦的每個決定。單體系統一般由多個緊密集成的層結構甚至是多個子系統組成,而這些系統須要一塊兒發佈,而且具備很脆弱的相互依賴性。例如,一個在線零售系統可能由若干個的直接面向客戶的前端應用程序,和一個實現了許多業務功能的集中式數據存儲的後端系統組成。
The main driver for decoupling capabilities out of a monolith is to be able to release them independently. This first principle should guide every decision that developers make around how to perform the decoupling. A monolithic system often is composed of tightly integrated layers or even multiple systems that need to be released together and have brittle interdependencies. For example, in an online retail system, the monolith composed of one or multiple customer facing online shopping applications, a back-end system implementing many of the business capabilities with a centrally integrated data store to hold state.
大多數的所謂解耦嘗試,只是試圖將一些面向用戶的組件分離出來,並經過外觀模式爲前端提供易於使用的API,而數據依然被深鎖於單體系統之中。這種方法雖然能快速取得一些成效,好比使得系統能夠更頻繁地更改UI,可是一旦涉及到系統的核心功能,更新速度便馬上受限於整個系統中更新最慢的部分——單體系統自己及它的數據存儲。簡而言之,沒有對數據自己進行分離的架構並非微服務。把全部數據都存儲在一塊兒的思想自己就與微服務的「分佈式數據管理」思想相悖。
Most decoupling attempts start with extracting the user facing components and a few facade services to provide developer friendly APIs for the modern UIs, while the data remains locked in one schema and storage system. Though this approach gives some quick wins such as changing the UI more frequently, when it comes to core capabilities the delivery teams can only move as fast as the slowest part, the monolith and its monolithic data store. Simply put, without decoupling the data, the architecture is not microservices. Keeping all the data in the same data store is counter to the Decentralized Data Management characteristic of microservices.
解決方案:垂直地分離各個業務功能,連業務邏輯帶數據一塊兒進行解耦,而且各個前端應用的調用重定向到新的API上。
The strategy is to move out capabilities vertically, decouple the core capability with its data and redirect all front-end applications to the new APIs.
對於這種數據加服務一塊兒解耦的方法而言,其解耦過程當中的主要障礙就是那些須要對集中存儲的共享數據進行併發讀寫的各個應用程序。針對這種狀況,交付團隊須要提供合適的數據遷移策略,具體取決於他們可否在同時對全部數據的讀寫作重定向和遷移。Stripe所寫的「數據遷移的四階段策略」適用於許多須要進行數據增量遷移的環境,而且能夠保證全部正在進行遷移的系統能夠不中斷地運做。
Having multiple applications writing and reading to and from the centrally shared data is the main blocker to decoupling the data along with the service. The delivery teams need to incorporate a data migration strategy that suits their environment depending on whether they are able to redirect and migrate all the data readers/writers at the same time or not. Stripe’s four phase data migration strategy is one that applies to many environments that require to incrementally migrate the applications that integrate through the database, while all the systems under change need to run continuously.
圖5:將數據和服務遷移到微服務中,並將全部調用方調整和重定向至新API
Figure 5: Decouple capability with its data to a microservice exposing a new interface, modify and redirect consumers to the new API
避免只解耦調用接口或後端服務而不解耦數據的反模式Avoid the anti pattern of only decoupling facades, only decoupling the backend service and never decoupling data.
從單體系統中將業務功能解耦出來是一件很難的事情。我據說,Neal Ford將其比做「一臺當心翼翼的器官手術」。從在線零售系統中提取業務功能時,須要仔細地將業務邏輯、數據、UI組件提取出來並將它們重定向到新服務。由於其所需的工做量不是一點兩點,因此開發者們須要不斷地評估進行解耦的成本與其可以帶來的好處,好比,是想要追求更快的開發速度,仍是要追求規模上的成長。舉個例子,若是交付團隊的目標是想在修改單體系統中的現有業務功能時耗時更少,那麼他們必須找到修改最耗時的業務功能並將其分離。把代碼中不斷髮生變化的部分單獨分離出來,能夠從開發人員那裏獲得不少的愛,還可讓他們能夠最快速地產出價值。交付團隊還能夠經過對代碼的提交記錄作分析,找到那些變化得最多的部分,並將其與產品路線圖、產品組合一塊兒分析,以肯定在未來最指望的功能,並將其分離出來。他們須要與業務經理、產品經理一塊兒交談,以瞭解對他們而言真正重要的差別化能力。
Decoupling capabilities from the monolith is hard. I’ve heard Neal Ford use the analogy of a careful organ surgery. In the online retail application, extracting a capability involves carefully extracting the capability’s data, logic, user facing components and redirecting them to the new service. Because this is a non-trivial amount of work, the developers need to continuously evaluate the cost of decoupling against the benefits that they get, e.g. going faster or growing in scale. For example, if the delivery teams' objective is to accelerate the modifications to existing capabilities locked in a monolith, then they must identify the capability that is being modified the most to take out. Decouple parts of the code that are continuously undergoing change and getting a lot of love from the developers and are constraining them most to deliver value fast. The delivery teams can analyse the code commit patterns to find out what has historically changed most, and overlay that with the product roadmap and portfolio to understand the most desired capabilities that will be getting attention in near future. They need to talk to the business and product managers to understand the differentiating capabilities that really matter to them.
例如,在一個在線零售系統中,「客戶個性化」是一項爲了提供最佳用戶體驗的、通過了大量實驗的功能。由於它是一項事關客戶體驗的很是重要的業務功能,而且常常須要修改,因此它是一個進行解耦的良好目標。
For example in an online retail system, ‘customer personalization’ is a capability that goes under a lot of experimentation to provide the best experience to the customer and is a good candidate for decoupling. It is a capability that matters to business a lot, customer experience, and gets modified frequently.
圖6:找到最重要的業務功能並解耦:在按期迭代中建立最多的業務和用戶價值
Figure 6: Identify and decouple the capability that matters most: creates most value for business and customer, while changing regularly.
利用社交代碼分析工具(如CodeScene)來查找代碼中最活躍更改的組件。若是自動構建系統會在每次提交時觸發或自動生成代碼,請確保過濾掉這些噪聲。將常常發生更改的代碼與產品路線圖上預計進行的更改疊加,並找到進行解耦的交點。Use social code analysis tools such as CodeScene to find the most lively components. Make sure to filter signal from the noise if the build system happens to touch or auto-generate code on every commit. Overlay the frequently changed code with the product roadmap upcoming changes and find the intersection to decouple.
不管什麼時候,只要開發人員想要從系統中解耦一項服務出來,他們都有兩種可行的辦法:提取代碼,或是直接重寫。
Whenever developers want to extract a service out of an existing system, they have two ways to go about it: extract code or rewrite capability.
通常來講,服務的提取,或是單體系統的解構都默認被設想爲是經過重用現有的實現方案並將其提取到單獨的服務中來實現。其中的部分緣由是由於,咱們對咱們所親自設計和編寫的出來的代碼有着認知偏好。對於咱們所辛苦付出了勞動而收穫的結果,不管過程有多麼痛苦,結果有多麼不完善,咱們都會懷有熱愛。這實際上被稱爲「宜家效應」。不幸的是,這種認知偏好會阻礙咱們在分解單體系統所付出的努力。它使得開發人員和更重要的技術管理人員忽略了提取和重用現有代碼的高成本和低價值。
Often by default the service extraction or monolith decomposition is imagined as a case of reusing the existing implementation as-is and extracting it into a separate service. Partly because we have a cognitive bias towards the code we design and write. The labor of building, no matter how painful the process or imperfect the result, make us grow love for it. This is in fact known as the IKEA Effect. Unfortunately this bias is going to hold the monolith decomposition effort back. It causes the developers and more importantly technical managers to disregard the high cost and low value of extracting and reusing the code.
好比說,在零售系統中,「訂價和促銷」功能是一段至關複雜的高難度代碼,它能夠動態地配置和應用促銷規則,而且根據各類參數(例如客戶行爲,忠誠度,產品捆綁等)提供折扣和優惠。
For example in the retail system, the ‘pricing and promotion’ capability is an intellectually complex piece of code. It enables dynamic configuration and application of pricing and promotion rules, providing discounts and offers based on a variety of parameters such as customer behavior, loyalty, product bundles, etc.
像「訂價和促銷」這樣的業務功能,就是進行「重用及提取」的完美選擇。而相比之下,「客戶信息」就是一個很簡單的CRUD功能點,主要也就包括序列化、數據存儲以及相關配置之類的一些模板功能,所以,它是進行「重寫和淘汰」的理想對象。
This capability is arguably a good candidate for reuse and extraction. In contrast, ‘customer profile’ is a simple CRUD capability that is mostly composed of boilerplate code for serialization, handling storage and configuration, hence, it is a good candidate for rewrite and retire.
根據個人經驗,在大多數系統解構的狀況中,考慮到重用的高成本和低價值,交付團隊應當將業務功能重寫爲新的服務並淘汰掉舊代碼。理由以下:
In my experience, in majority of the decomposition scenarios, the teams are better off to rewrite the capability as a new service and retire the old code. This is considering the high cost and low value of reuse, due to reasons such as below:
除非待解耦的業務功能與清晰的領域概念相一致,而且邏輯精巧複雜,不然我強烈建議進行重寫,並淘汰老代碼。
Unless the capability is relevant, aligned with a clear domain concept and has high intellectual property, I strongly recommend a rewrite and retiring of the old code.
圖7:重用和提取低毒性的高價值代碼,重寫和淘汰高毒性的低價值代碼
Figure 7: Reuse and Extract high value code with low toxicity, Rewrite and Retire low value code with high toxicity
使用代碼毒性分析工具(如CheckStyle)來作出關於重寫與重用的決策。Use code toxicity analysis tools such as CheckStyle to make decisions around rewrite vs. reuse.
從遺留單體系統中尋找領域邊界,既是一門藝術,又是一門科學。而做爲一個具備普適性的規則,利用領域驅動設計的概念來尋找「上下文邊界」來定義微服務的邊界是一個很好的開始。我認可,我常常看到一些單體系統的解耦在粒度上「矯枉過正」,將一個過大的系統切分爲了太小的服務,而這些太小的服務經常是從現有數據的視角上出發的。而這種的微服務識別方法,幾乎老是會致使大量與資源的CURD直接相關的貧血服務的「寒武紀生物大爆發」。對於新的微服務架構來講,這將建立一個高度摩擦的環境,而且最終沒法獨立發佈測試,也沒法提供服務。這種方式建立了一個難以調試的分佈式系統,一個跨越事務邊界的分佈式系統,所以很難保持一致,這一切對於運維來講,太複雜了。儘管存在一些關於微服務粒度的「啓發方法」,諸如團隊大小,重寫服務的時間,必須包括多少行爲等等,個人建議是,微服務的規模將取決於運維團隊能夠獨立發佈、監控和運營的服務數量。首先,圍繞領域概念構建一個大型服務,待團隊的微服務運維能力提高以後再將其分解爲多個小服務。
Finding the domain boundaries in a legacy monolith is both an art and science. As a general rule applying domain driven design techniques to find the bounded contexts defining microservices boundaries is a good place to start. I admit, far too often I see an overcorrection from large monolith to really small services, really small services whose design is inspired and driven by the existing normalized view of the data. This approach to identifying service boundaries almost always leads to a cambrian explosion of large number of anemic services for CRUD resources. For many new to the microservices architecture, this creates a high friction environment that ultimately fails the test of independent release and execution of the services. It creates a distributed system that is hard to debug, a distributed system that is broken across transactional boundaries and hence difficult to keep consistent, a system that is too complex for the operational maturity of the organization. Though there are some heuristics on how ‘micro’ should be the microservice: the size of the team, the time to rewrite the service, how much behavior it must encapsulate, etc. My advice is that the size depends on how many services the delivery and operation teams can independently release, monitor and operate. Start with larger services around a logical domain concept, and break the service down into multiple services when the teams are operationally ready.
例如,在解耦零售系統的過程當中,開發者能夠從「購買」服務開始解耦。「購買」服務內封裝了「購物袋」的功能,還封裝了購買一個真正的購物袋的功能,即「結帳」。而隨着他們組建團隊併發布更多服務的能力不斷加強,他們能夠將「購物袋」功能從「結帳」中解耦出來,成爲一個單獨的服務。
For example, on the journey decoupling the retail system, developers may start with one service ‘buy’ that encapsulates both the content of a ‘shopping bag’ as well as capability of buying the shopping bag, i.e ‘check out’. As their ability to form smaller teams and release larger number of services grows then they can decouple ‘shopping bag’ from ‘check out’ into a separate service.
圖8:圍繞豐富的領域概念從宏觀角度上進行解耦,在準備就緒後,將服務細分爲更小的領域概念
Figure 8: Decouple macro services around rich domain concepts and when ready, breakdown services to smaller domain concepts
使用Richardson L3成熟度模型和超連接,能夠在不影響調用方的狀況下實現將來的服務分離,即調用方能夠發現如何結帳,而且須要提早知道。Use Richardson Maturity Model L3 and hyperlinks to enable future decoupling of services without impacting callers, i.e. caller discovers how to checkout and does not know in advanced.
想直接將一個巨型單體系統解構成一個精心設計的微服務系統,從而讓其在空氣中消失,是一件是不可能的事。任何一個經驗豐富的工程師均可以分享一些關於嘗試進行遺留系統的升級和遷移的故事。而這些嘗試,在最開始都是以很是樂觀的方式被計劃和啓動的,然而它們最好的結局通常都是「在一個足夠好的時間點被及時放棄」。因爲一個宏觀條件的變化,這些系統遷移的長期計劃會被放棄,好比項目資金耗盡、高層的關注重點轉移,或者支持這個項目的領導走人了。因此,現實是,團隊應該如何作規劃,來開始這場龐大的微服務解耦之旅。這種方法,我稱之爲「架構演進的原子性按步遷移」。遷移的每一步,都應使架構更接近其目標狀態。而每一次的架構演進,都應該是原子性的——不管是一個小步驟仍是一個大飛躍,要麼完成,要麼回滾。這是很是重要的,由於咱們正在採起迭代和漸進的方法來改進總體架構和進行服務解耦。就完成架構目標而言,每一個改變都必須讓咱們處於更好的位置。使用「演進架構」的適應度函數進行隱喻的話,演進的每一個原子步驟以後的架構適應度函數都應該對架構的目標產生更高價值。
The idea of vanishing a legacy monolith into thin air by decoupling it into beautifully designed microservices is somewhat of a myth and arguably undesirable. Any seasoned engineer can share stories of legacy migration and modernization attempts that got planned and initiated with over optimism of total completion, and at best got abandoned at a good enough point in time. Long term plans of such endeavors get abandoned because the macro conditions change: the program runs out of money, the organization pivots its focus to something else or leadership in support of it leaves. So this reality should be designed in how the teams approach the monolith to microservices journey. I call this approach 'migration in atomic steps of architecture evolution', where every step of the migration should take the architecture closer to its target state. Each unit of evolution might be a small step or a large leap but is atomic, either completes or reverts. This is specially important as we are taking an iterative and incremental approach to improving the overall architecture and decoupling services. Every increment must leave us in a better place in terms of the architecture goal. Using the evolutionary architecturefitness function metaphor, the architecture fitness function after every atomic step of migration should generate a closer value to the architecture’s goal.
讓我舉一個例子來講明這一點。 想象一下,微服務架構的目標是提升開發人員的修改系統以提供價值的速度。團隊決定將最終用戶身份驗證解耦爲一個基於OAuth 2.0協議的單獨服務。此服務旨在替代現有老架構中的用戶身份驗證系統,並以微服務的新體系結構來進行身份驗證。 咱們將這種增量改變稱爲「導入鑑權服務」。導入這項新服務的一種方法是:
(1)構建鑑權服務,實現OAuth 2.0協議。
(2)在老系統後端添加一個新的認證路徑,以調用鑑權服務來認證最終用戶。
Let me illustrate this point with an example. Imagine the microservice architecture goal is to increase the speed of developers modifying the overall system to deliver value. The team decides to decouple the end user authentication into a separate service based on OAuth 2.0 protocol. This service is intended to both replace how the existing (old architecture) client application authenticates the end user, as well as new architecture microservices validate the end user. Let's call this increment in the evolution, ‘Auth service introduction’. One way to introduce the new service is to go through these steps first:
(1) Build the Auth service, implementing OAuth 2.0 protocol.
(2) Add a new authentication path in the monolith back end to call Auth service for authenticating the end user on whose behalf it is processing a request.
若是團隊至此而止,轉而去開發一些其餘服務或功能的話,他們會將總體架構置於熵增狀態。由於在此狀態下,有兩種實現用戶身份驗證的方式,即新的OAuth 2.0路徑和舊客戶端的基於密碼/會話的路徑。在這一點上,團隊實際上遠離了實現更快更改的整體目標。對於新來的老系統開發人員來講,如今須要處理兩條代碼路徑了。這實際上增長了熟悉代碼的工做量,還致使更改和測試代碼的過程變得更慢了。
If the team stops here and pivots into building some other service or feature, they leave the overall architecture in a state of increased entropy. In this state there are two ways of authenticating the user, the new OAuth 2.0 base path, and old client’s password/session based path. At this point the teams are actually further away from their overall goal of making changes faster. Any new developer to the monolith code needs to deal with two code paths, increased cognitive load of understanding the code, and slower process of changing and testing it.
相反,團隊能夠在咱們的原子演進單元中包含如下步驟:
(3)將舊客戶端的基於密碼/會話的身份驗證替換爲OAuth 2.0路徑
(4)從老系統中刪除舊的驗證路徑
Instead the team can include the following steps in our atomic unit of evolution:
(3) Replace old client’s password/session based authentication with OAuth 2.0 path
(4) Retire the old authentication code path from the monolith
在這一點上,咱們能夠爭辯說,在這一點上,團隊已經接近目標架構。
At this point we can argue that the teams have gotten closer to the target architecture.
圖9:使用原子演進步驟將體系結構演進爲微服務。即便中間代碼更改可能會致使其遠離其目標,但每步完成後,總體體系結構都朝向目標方向改進
Figure 9: Evolve the architecture towards microservices with atomic steps of architecture evolution where after each step the overall architecture is improved towards its goal even though intermediary code changes might take it further away from its fitness objective
單體系統解構的原子單元包括:
- 解耦新服務
- 將調用方重定向至新服務
- 從單體系統中刪除老代碼
- 反模式:解耦新服務給新的調用方調用,可是卻不淘汰舊服務
The atomic unit of monolith decomposition includes:
- Decouple the new service
- Redirect all consumers to new service
- Retire the old code path in the monolith.
- The anti-pattern: Decouple the new service, use for new consumers and never retire the old.
我常常發現,團隊完成了一個業務功能的遷移,而且在新的業務功能創建以後當即宣告勝利,而不淘汰舊的代碼路徑,即上述的反模式。論其主要緣由,一是注重引入新功能帶來的短時間收益,二爲淘汰舊實現所需的整體工做量,同時還面臨着開發新功能的優先級競爭。爲了作正確的事情,咱們須要使演進的原子步驟儘量小。
I often find teams end migration of a capability out of the monolith and claim victory as soon as the new capability is built without retiring the old code path, the anti-pattern described above. The main reasons for this are (a) the focus on short-term benefits of introducing a new capability and (b) the total amount of effort required to retire the old implementations while facing competing priorities for building new features. In order to do the right thing, we need to strive for making the atomic steps as small as possible.
經過這種方式進行遷移,咱們能夠在解耦的旅途中小憩,也能夠安全地停下來休養生息,並在這漫長的旅程中倖存下來,完全消滅舊系統。
Migrating with this approach we can break up the journey to shorter trips. We can safely stop, revive and survive this long journey, slaying the monolith.