邊緣服務的一致性、耦合和複雜性

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"技術公司採用微服務架構已經十多年了,結果好壞參半。微服務之間的依賴關係導致在修改一個服務時也需要修改其他服務,微服務的優勢因此打了折扣。這就是所謂的緊密耦合。但組件之間的依賴關係是不可避免的。"}]},{"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":"如果微服務不能滿足用戶的需求,那它還有什麼用?本文重點討論如何維護有用的微服務,並能夠從微服務的遷移流程中得到好處。關鍵的是要在各個層保持清晰的關注點分離,並遵循最適合每一個層的設計原則。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"RESTful API設計"}]},{"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":"2000年,Apache HTTP服務器聯合創始人發表了一篇題爲“網絡軟件架構的架構風格和設計”的博士"},{"type":"link","attrs":{"href":"https:\/\/www.ics.uci.edu\/~fielding\/pubs\/dissertation\/fielding_dissertation.pdf","title":null,"type":null},"content":[{"type":"text","text":"論文"}]},{"type":"text","text":"。其中的第5章介紹了REST (Representational State Transfer),作爲分佈式超媒體系統的一種架構風格。這篇有關Richardson成熟度模型的"},{"type":"link","attrs":{"href":"https:\/\/martinfowler.com\/articles\/richardsonMaturityModel.html","title":null,"type":null},"content":[{"type":"text","text":"博文"}]},{"type":"text","text":"是瞭解REST在API設計中所起作用的一個很好的資源。"}]},{"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":"在API設計和HTTP標準之間存在着緊密的一致性。在RESTful API設計中,URI的路徑部分用於標識特定實體(也稱爲資源)。HTTP謂詞用於標識要對實體執行的操作類型。實體可以通過其他實體的URI路徑部分鏈接到其他實體。對於HTTP狀態代碼含義的解釋也存在一致性。"}]},{"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":"提供在線應用程序的公司將API設計成平臺,他們之所以這樣做有很多原因。或許,他們希望從第三方那裏獲得額外的收入來源,或者向高級用戶追加銷售。或許,他們希望讓不同的團隊更容易調用彼此的API。或許,他們希望以這樣一種方式來組織他們的API,讓它們可以很容易被相同產品族中的類似或相關的產品所重用。最後,他們希望設計出易於進行自動化測試的API。"}]},{"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":"就像提供在線應用程序的公司開發便於用戶理解的GUI應用程序一樣,平臺公司也應該開發便於開發人員理解的API。RESTful API非常適合這一需求。"}]},{"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":"在RESTful API出現之前,API是基於所謂的遠程過程調用(Remote Procedure Call,RPC)而設計的。這些API的設計不存在一致性,以致於難以看出它們是幹什麼用的。REST在API設計中引入了一致性。當你將REST與OpenAPI結合在一起時,開發人員很容易就知道如何使用你的API。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/e8\/e8c10d9c9dc78d8e5f35f4b9b2cf167e.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","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","marks":[{"type":"strong"}],"text":"一個基本的新聞源RESTful API Swagger規範。"}]},{"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":"後端服務可以很容易地調用RESTful API,但對於前端應用程序來說就不那麼容易了。這是因爲好的用戶體驗不那麼RESTful。用戶不想要背後滿是碎片化實體的GUI。他們希望一下子看到所有的東西,除非在設計上確實需要漸進顯示。例如,我不想在規劃旅行行程時打開多個頁面,我希望在下訂單之前能夠在一個頁面上看到所有的摘要信息(包括航班、汽車租賃和酒店預訂)。"}]},{"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":"當用戶導航到一個Web頁面或鏈接到單頁應用程序(SPA)或移動應用程序的某個視圖時,前端應用程序需要調用後端服務來獲取渲染視圖所需的數據。如果使用的是RESTful API,單個調用不太可能獲得所有的數據。通常是先執行一個調用,然後前端代碼遍歷該調用的結果,並對每個結果項進行更多的API調用,以獲得所需的所有數據。這不僅使前端開發複雜化,而且還增加了應用程序的頁面加載時間。稍後再詳細介紹。"}]},{"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":"這裏還有另一個問題,即RESTful API與前端GUI關注點不是很契合。RESTful API本身不支持推送通知,但支持回調(通過WebHook實現)。WebHook對推送通知的支持程度不如WebSocket。WebHook和WebSocket的不同之處在於,Web瀏覽器不支持WebHook,但支持WebSocket。這是因爲WebSocket是由前端發起的,並與後端保持連接,後端會向前端發送更新。WebHook是由後端發起的,但瀏覽器沒有一個固定的IP地址來接收這些請求。因爲路徑在RESTful API中被用於標識一個特定的實體,所以請求和響應的格式不應該發生明顯的變化。爲了節省連接資源,SPA可能會爲所有類型的推送通知打開單個WebSocket,允許每一條消息的格式之間存在巨大差異。"}]},{"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":"新的用戶需求(如增加額外的數據字段)可能需要前端和後端都作出修改,這是導致緊密耦合的根本原因。團隊之間的緊密耦合降低了開發速度,這個可以用康威定律來解釋。"}]},{"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":"跨團隊的溝通成本要高於單個團隊內部的溝通成本。同時擁有前端和後端開發人員的團隊也可能缺乏效率。雖然從理論上講,前後端開發人員處在同一個團隊中,但在前端和後端開發人員之間仍然存在分界線。這種“隱式”的子團隊隱藏了軟件開發的一些複雜性,可以說是康威定律的一個不成文的補充。"}]},{"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":"當後端服務和前端應用程序發生緊密耦合時,發佈管理也會變得複雜。微服務的最大優勢之一是你不必一次性發布所有的內容,但緊密耦合的組件通常需要在同一時間發佈,如果一個組件需要回滾,其他組件也都需要回滾。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"GraphQL API設計"}]},{"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":"2015年,Facebook採用了一種不一樣的API設計方法,即圖查詢語言(GQL)或GraphQL。它是一種包含層次結構類型的模式,該模式包含三種特殊類型:查詢、變異和訂閱。調用者發送一個命令,該命令提供查詢條件,並指定在響應中期望得到的數據格式。"}]},{"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":"最爲流行、功能齊全且成熟的GraphQL服務器端框架實現是由舊金山的一家名爲"},{"type":"link","attrs":{"href":"https:\/\/www.apollographql.com\/","title":null,"type":null},"content":[{"type":"text","text":"Apollo"}]},{"type":"text","text":"的小型初創公司開發的。有了他們的框架,在客戶端增加新功能就變得非常容易,且無需對服務器作出大量修改。"}]},{"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":"後端開發人員必須編寫schema和解析器。框架調用在請求中指定的解析器,然後將每個解析器的響應拼接在一起。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/51\/5174b6832cb459ca9f50a30c5d0f9282.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"類似的基本新聞提要的GraphQL schema。"}]},{"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":"由於解析器位於屬性級別,而且獲取底層數據的機制可能一次性獲取多個屬性,因此存在重複獲取相同數據的可能性,造成了浪費。這就是所謂的N+1問題。後端代碼應該用某種類型的請求緩存來緩解這個問題。"}]},{"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":"基於生存時間值(TTL)、最近最少使用原則(LRU)的緩存在GQL中的作用是有限的。因爲有效載荷是可以靈活指定的,所以很難實現高命中率和低髒讀率的高效緩存。因此,GQL緩存往往要比RESTful緩存大得多。Apollo GraphQL框架支持在schema中使用緩存提示註解或在解析器中動態設置,這可以通過瀏覽器端緩存或內存緩存或外部緩存(如Memcached或Redis)來實現。"}]},{"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":"變異部分與RPC相似,對於它們的處理方式並沒有定義好的標準,所以它們都不好理解。"}]},{"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":"在撰寫本文時,GraphQL的應用程序性能監控(APM)還沒有RESTful API那麼成熟。GraphQL沒有提供內建支持,但有一些插件或解決方案可用於"},{"type":"link","attrs":{"href":"https:\/\/newrelic.com\/blog\/nerdlog\/apollo-server-plugin","title":null,"type":null},"content":[{"type":"text","text":"New Relic"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https:\/\/docs.datadoghq.com\/integrations\/apollo\/","title":null,"type":null},"content":[{"type":"text","text":"Data Dog"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https:\/\/grafana.com\/docs\/grafana-cloud\/reference\/integrations\/integration-apolloserver\/","title":null,"type":null},"content":[{"type":"text","text":"Prometheus"}]},{"type":"text","text":"和"},{"type":"link","attrs":{"href":"https:\/\/docs.appdynamics.com\/21.7\/en\/application-monitoring\/configure-instrumentation\/transaction-detection-rules\/custom-match-rules\/node-js-business-transaction-detection","title":null,"type":null},"content":[{"type":"text","text":"App Dynamics"}]},{"type":"text","text":"。我相信,隨着時間的推移,Apollo風格的GraphQL APM監控將變得更加主流。"}]},{"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":"在RESTful API中,客戶端指定路徑,可能是查詢字符串參數,可能是身份驗證,僅此而已。而在GQL中,客戶端必須指定有效載荷是什麼樣子的。這種小程序增加了調用GQL服務的複雜性,從而增加了出現錯誤的可能性。這也提高了自動化測試的成本。通常的方法是在測試自動化中查詢所有的內容,這樣做應該足夠了,除非解析器需要通過上下文對象交換帶外數據。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"關注點分離"}]},{"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":"API設計該基於REST還是GQL,關鍵在於你要理解一個計算機科學概念,也就是關注點分離(SoC)。一個設計良好的軟件通常由多個層組成,每個層又分爲多個模塊。如果每個層和每個模塊都有清晰嚴格的關注點分離,那麼軟件就容易理解,複雜度也更低。爲什麼會這樣?如果你知道在哪裏可以找到某個功能的實現代碼,你很快就會知道該如何去閱讀它的代碼(很可能會跨多個代碼庫)。就像REST和GQL在API設計方面所提供的一致性一樣,清晰的SoC提供了一種一致性的方式用於找到每個功能的實現。開發人員很少會在他們瞭解得很透徹的軟件中引入bug。"}]},{"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":"SoC的標準由軟件架構師來設定。以下是不同層的分類以及每個層應該關注什麼。"}]},{"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":"通常來說,在現代商業軟件中,最主要的層是前端和後端。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端軟件的直接交互對象是用戶,通常運行在移動設備或筆記本電腦上。前端包括移動應用和Web應用,主要是關於渲染、綁定、交互和用戶體驗。其內部結構類似模型視圖控制器(MVC)的變種。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"後端軟件的交互對象是前端軟件。在生產環境中,後端軟件通常運行在數據中心(如公有云)的服務器上。"}]}]}]},{"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":"後端又被進一步分爲數據、邊緣和集成服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據服務爲數據庫提供保護、執行業務規則、維護一致性,並專注於可伸縮性、性能和潛在的彈性問題。其內部結構包括資源控制器、服務、模型和數據訪問對象(DAO)。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"邊緣服務負責處理推送通知、跨端點聚合和安全問題。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"集成服務應該作爲第三方應用的反應式抗腐蝕層,如電商網站(後端集成)和電子表格(前端集成)。"}]}]}]},{"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":"還有其他一些類型的服務這裏沒有提及。"}]},{"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":"一個功能全面的服務網格可用於處理彈性、發現、內部認證、加密和可觀察性問題。要實現可觀察性,需要與其他類型的服務發生交互,獲得監控、告警、日誌聚合,甚至是分佈式追蹤能力。"}]},{"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":"在架構師看來,當開發人員決定模糊這些SoC(通常是圖一時的方便),就是系統開始陷入麻煩的開始。例如,因爲DevOps的不完善,你決定讓數據服務來處理邊緣服務或集成服務應該處理的問題,或者讓前端應用做一些本該由後端完成的事情。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"現代技術棧"}]},{"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":"大約在六年前,我發現了一種所謂的BFF邊緣服務。客戶端應用程序不會直接調用數據服務,而是通過中間服務來調用,中間服務專門用來滿足客戶端應用程序的需求。如果設計得當,這種方式可以將數據服務與不斷變化需求的GUI解耦開來。雖然數據服務仍然與數據庫緊密耦合,但數據庫schema變化速度相對較慢。至少對於相對成熟的項目來說是這樣的。"}]},{"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":"那麼BFF涉及哪些有用的技術?它們作爲GraphQL暴露出來,需要基於RESTful數據服務提供一個聚合編排層,需要提供WebSocket或利用GQL的訂閱能力,應該由前端團隊負責開發維護,並採用前端開發人員比較熟悉的技術棧。"}]},{"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":"你可能傾向於認爲BFF會增加延遲,因爲多了額外的服務器跳轉,但事實卻相反。加重延遲的不是服務器跳轉,而是數據包的傳輸距離。爲了方便演示,請想象一下以下這種情況。假設一個網頁調用了一個API,這個API平均每次返回10個數據項,而每個數據項需要調用另外三個API,這樣才能獲得渲染頁面所需的數據。一位舊金山的用戶從亞馬遜us-east-1區域的服務器加載頁面,每個請求來回需要傳輸5600公里。因爲總共有31個請求,所以數據需要傳輸173600公里,這個距離可以繞地球7圈。如果你把這31個請求放在一個BFF裏,並且這個BFF與服務器位於同一個數據中心裏,那麼你總共需要32個調用,但數據只需要傳輸5600公里,是不使用BFF傳輸距離的3%。Web瀏覽器可以通過並行的方式調用API,但相比後端服務,在連接方面具有更強的約束。"}]},{"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":"另一種邊緣服務叫作API網關,用於認證、授權、速率限定、單點登錄和訪問權限管理。如果有必要,它也可以將查詢請求路由給GQL BFF,或者直接將請求路由給RESTful服務。對於不同類型的客戶端應用程序,需要使用不同的BFF,但你只需要一個API網關就可以滿足各種類型的客戶端。API網關有時候也作爲第三方的調用代理,讓它們可以訪問防火牆背後的數據服務。API網關是一種通用服務,可以由後端開發人員負責開發("},{"type":"link","attrs":{"href":"https:\/\/openresty.org\/","title":null,"type":null},"content":[{"type":"text","text":"OpenResty"}]},{"type":"text","text":"就是一種比較流行的方案),也可以是第三方提供的產品(比如"},{"type":"link","attrs":{"href":"https:\/\/konghq.com\/","title":null,"type":null},"content":[{"type":"text","text":"Kong"}]},{"type":"text","text":"),或者是由公有云供應商提供的PaaS服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/6e\/6e1680de108562f950812cdf9695f388.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現代Web應用程序幾乎都是SPA(單頁應用),而在以前,用戶用Web瀏覽器加載HTML頁面,這些頁面可能是由服務器端的Web應用程序生成的。當用戶單擊頁面上的一個鏈接,瀏覽器會渲染一個全新的HTML頁面,這個頁面也是由服務器端的Web應用程序生成的。在使用SPA時,用戶用Web瀏覽器加載一個Web頁面,這個頁面只包含最基本的HTML元素,同時也會下載很多JavaScript和CSS文件。API調用是通過執行JavaScript代碼來完成的,然後生成很多DOM元素,瀏覽器再用這些DOM來渲染GUI。當用戶單擊一個鏈接,頁面上的JavaScript會銷燬舊的DOM元素,並生成一些新的DOM元素。頁面看起來發生了變化,但瀏覽器並沒有加載全新的頁面。"}]},{"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":"現如今,大多數前端開發人員使用TypeScript(具備類型檢查特性的JavaScript變種)編程,以及Angular或React等框架。TypeScript被轉譯爲JavaScript和CSS,這個過程成爲項目構建的一部分。在進行本地開發時,開發人員將Node.js作爲JavaScript和CSS文件的服務器,也用它將請求路由給目標API。但如果不是在本地開發,我建議使用Nginx。這樣可以將配置了同源策略的文件與應用程序代碼放在一起。構建出來的Docker鏡像包含應用程序編譯後的文件以及與CORS或緩存控制問題相關的配置文件。如果你採用了這種方式,可能需要調整CDN的配置。"}]},{"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":"現如今大多數移動應用程序都是運行在iOS或安卓系統上。這些操作系統都有各自的技術要求,這裏就不贅述了。你可以選擇爲不同的操作系統單獨開發應用程序,也可以使用Ionic或React Native框架來開發同一套應用程序,然後爲不同的操作系統分別生成各自的二進制包。你可以爲分別爲iOS和安卓開發單獨的BFF,也可以簡單一點,開發一個移動BFF來滿足兩個平臺。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"結論"}]},{"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":"你不必糾結於是選擇REST還是GraphQL。REST更適合面向平臺的數據服務,GraphQL更適合面向GUI的邊緣服務。如果你的數據服務和邊緣服務位於不同的層,那麼完全可以同時保留REST和GQL,把二者的好處盡收囊中。"}]},{"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":"理清不同類型組件之間的關注點分離,有助於降低意外複雜性。有時候,你會爲了達成短期目標而模糊了這些關注點。架構師需要努力滿足各方的需求。爲了滿足緊急需求,考慮應用一些短期的解決方案,允許暫時模糊關注點邊界,提高意外複雜性。但後續需要馬上跟進(需要各方的參與),進行長期必要的重構工作,讓系統重新具備清晰的關注點分離,並從整體上降低意外複雜性。這就是技術負債給我們帶來的教訓。"}]},{"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":"如果可能的話,降低跨組件的耦合性。緊密耦合的組件應該由相同的團隊負責開發維護,並採用相似或互補的開發技術。你不需要爲此大幅改變團隊的結構。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"作者簡介:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Glenn Engstrand"},{"type":"text","text":"是Rally Health的軟件架構師。他的工作重點是與工程師合作,交付符合12 Factor標準的可擴展服務器端應用架構。在2017年和2018年的Adobe內部廣告雲開發者大會以及2012年在波士頓舉行的Lucene Revolution大會上,Glenn進行了突破性的演講。他擅長將單片應用分解爲微服務,並與實時通信基礎設施進行深度集成。"}]},{"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":"strong"}],"text":"原文鏈接"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.infoq.com\/articles\/consistency-coupling-complexity\/","title":null,"type":null},"content":[{"type":"text","text":"Consistency, Coupling, and Complexity at the Edge"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章