我在架構設計和代碼開發中的一些常用原則

不管我一生中取得了多大的成功,其主要原因都不是我知道多少事情,而是我知道在無知的情況下自己應該怎麼做。我一生中學到的最重要的東西是一種以原則爲基礎的生活方式,是它幫助我發現真相是什麼,並據此如何行動。
——瑞·達利歐(Ray Dalio)

在日常的開發和設計過程中,大家對技術設計上的一些問題往往會面臨很多的選擇,不同的人會有不同的選擇,每每如此,我都會嘗試着問自己:我做出選擇和判斷背後的原則是什麼?

經過這麼多年的發展,在軟件設計過程,目前沉澱下來的原則有很多,但很多情況下,很多原則爲了普適性,總結得會比較抽象,一旦太過抽象,對原則的解釋和理解就會因人而異,譬如:高內聚低耦合原則,大家都懂,但是如何落地和執行卻是很難說完全達成一致。因此,需要針對一些實際的場景中的問題去總結和補充,在大的原則下具化形成大家容易理解一致的相對明確原則。

本文介紹的就是我在工作中遇到的一些問題而總結和使用到的一些常用原則。

一 常用原則總結

1 分層設計相關原則

單向依賴原則

原則上只允許較高層次依賴較低層次,不允許反向依賴。

我們部門是爲B類企業提供金融解決方案的技術部門,針對我們部門,在金融平臺層系統不能反向依賴業務產品層系統。同一層的金融平臺層系統之間的依賴不進行限制,但會盡量減少同層依賴。

另外,我們在解決底層依賴的高層中沉澱了幾種基本方式:

  1. 系統依賴轉換爲數據依賴;
  2. 接口依賴,通過底層定義SPI,業務層實現,這種做法其實是不得已爲之,同時,我們在設計過程中還是儘可能避免走這條路;
  3. 通過事件機制解耦依賴。

無循環依賴原則

系統設計時,儘量減少系統之間的依賴,同時需要避免系統之間出現循環調用。

這是微服務場景下最容易出現的一個問題,尤其是同層的領域系統之間的調用,導致系統容易出現循環調用,循環依賴帶來的一個嚴重的問題是影響系統的發佈和部署問題。

避免跨層調用原則

較高層次不允許之間跨層調用底層。

軟件設計中進行分層的一個重要目的是通過分層屏蔽底層的實現細節,如果出現跨層相當於把底層的實現直接暴露了。譬如門面服務層,繞過領域服務層,直接調用DAO層進行數據讀寫操作,一旦需要重構修改原有的DAO層接口,就發現升級改造成本巨大,我不知道有多少個團隊也面臨過這種痛苦。

單一職責原則

該原則由羅伯特·C·馬丁(Robert C. Martin)於《敏捷軟件開發:原則、模式和實踐》一書中提出的。這裏的職責是指類變化的原因,單一職責原則規定一個類應該有且僅有一個引起它變化的原因,否則類應該被拆分(There should never be more than one reason for a class to change)。

這個原則雖然提出時是解決類的職責定義問題,但實際上在對模塊的劃分上也有指導意義。該原則雖然很簡單,但是往往也容易被忽視。

在最近的項目中,我充分體會到這個原則的作用,我們部門的金融網絡系統主要解決機構標準化對接問題,我們將系統分爲了上下兩層,下層通過標準化的接口對接機構,提升機構跨產品的複用能力;上層是產品擴展層,通過提供標準接口給到上游的業務產品層,支持同一個產品接入多家機構,屏蔽機構差異。我們判斷一個功能到底屬於機構對接層,還是產品擴展層的一個簡單的原則是:如果新增一家機構,能否做到隻影響機構對接層,而保持產品擴展層代碼不改;反過來,如果新增一個產品,是否能做到只修改產品擴展層,機構層能否不改代碼。同時,爲了避免這個原則被突破,我們甚至在機構對接層的代碼中,去除了所有和產品有關的參數,這樣,根據產品定製的邏輯天然無法放到這一層。

數據冗餘

架構設計應該使得系統中數據的冗餘最小。

譬如我們在實踐過程中,接口設計時,在Javadoc上強制指定接口的必傳參數,儘量做到最小集,減少上游系統使用接口的成本。另外要求在接口實現時,提前進行參數校驗,不讓不滿足要求的數據冗餘到系統中。

爲了提高系統性能,備份節點和子系統/模塊必要時需要對數據進行緩存,當發生變化時,必須有相應的機制保證緩存數據的一致性和有效性。

2 質量屬性相關原則

數據安全

這塊在我們金融業務部門中尤其突出,金融由於其特殊性,往往需要收集大量的客戶真實和隱私數據,數據安全是設計中需要重點考慮的問題,通常我們會主要關注以下三個方面的問題:

  • 數據存儲安全:敏感數據加密、日誌輸出脫敏。
  • 數據傳輸安全:包括加密、傳輸通道規範,最少字段傳輸(夠用原則),尤其是我們金融部門,需要將數據輸出給到外部第三方機構情況比較多,這塊上面會控制比較嚴格。
  • 數據輸出展示:前端展示需要防止水平越權,另外,前端的展示可以埋點和方便數據採集。

3 資損防控

  • 可覈對和可監控:上下游系統的數據模型覈對關聯關係簡單、穩定(具備通用性,和產品無關)。
  • 可熔斷:對關鍵資損鏈路需要做到可熔斷。

對金融技術部門而言,資損防控是第一位,而我們在實際過程中發現,由於前期的一些系統在設計之初沒有考慮資損的防控,導致覈對或者監控的成本很高,因此,在後來的系統數據模型設計時重點會去review是否具備可覈對。

4 併發控制

  • 悲觀鎖:代碼編碼規範——一鎖二查三更新。
  • 樂觀鎖:必須在事務內更新。

5 熱點問題

避免流量傾斜,導致單臺機器/單個數據表/數據庫集中讀寫。

這個需要在設計時充分提前預判業務的發展規模和系統的容量問題。在實際實施過程中,我們會提前按照3~5年左右的業務規模來設計。

6 數據傾斜

分表分庫規則在設計時需要考慮數據分佈均勻,避免單庫或者單表數據傾斜。

數據傾斜這個在之前踩過比較大的坑,在系統設計之初沒有結合業務場景去考慮系統的數據存儲層設計,導致數據出現嚴重傾斜,數據庫操作出現瓶頸,現在是我們在設計存儲層方案時必須要考慮的一個原則。

7 性能原則

可壓測:對性能要求高的鏈路,需要做到可以壓測。

這個主要是由於每到大促就需要重新梳理和改造壓測鏈路,耗時費力,苦不堪言。

8 事務控制相關原則

  • 優先使用編程式事務:爲了更好的控制事務,一般要求使用編程式事務,避免潛在的跨事務問題。
  • 事務更新需要保證順序一致性:強一致要求還是最終一致,強一致是否會涉及到跨庫,事務操作時需要相同記錄的更新順序保證一致。
  • 事務中不進行遠程調用。

9 一致性相關原則

  • 區分系統調用錯誤和業務失敗:遠程調用失敗,不代表下游系統沒有接收請求,更不能做爲業務失敗依據,需要嚴格區分系統調用錯誤和業務失敗。
  • 可重試:任何一行代碼執行時都有可能因系統重啓而中斷,所以需要支持可重試。
  • 異步處理必須增加覈對:最終一致性離不開恢復重試策略,也需要有系統間數據覈對用於及時發現數據不一致,同時在覈對時需要增加處理時效的監控,及時發現長時間未處理成功的數據。

二 API設計相關設計原則

1 水平越權控制

API設計時需要考慮防範水平越權。

目前我們的做法是,從前端到後端,每層都需要進行越權校驗。通過從接口設計層面防控,避免某層出現疏忽導致越權的事件發生。

2 接口冪等控制

調用方必須提供用於冪等控制的參數,爲了控制冪等,同一個請求的冪等參數不變。

在血淚史上,由於接口不冪等導致的問題太多了,這個目前基本上已經成了部門在接口設計上的共識。

3 兼容性原則

API升級和調整,需要兼容老的版本。

爲了保證接口可以升級,我們對接口的設計就會存在比較高的要求,譬如接口參數中不能使用枚舉,不能使用Java基礎類型等,同時也要求接口設計需要具備一定前瞻性和通用性,尤其對於面向業務領域的接口設計,更要求對該領域的業務知識有比較多的瞭解。

當然還有一些原則在《Java開發手冊》中已有敘述,這裏就不在贅述。

三 總結

本文介紹了我們在系統設計和開發實際場景中總結出的一些原則,通過這些原則的總結和沉澱,可以在後續出現同類問題時做出相對正確的選擇,避免重蹈覆轍。另外,通過在大的原則下進行具體化和明確化,能夠讓大家容易達成一致,讓架構方案更容易落地,不走偏。

另外,無論是在生活上還是工作上,建議多從成功的經驗或者失敗的教訓中去總結,形成自己的原則,豐富自己的決策系統。這是《原則》這本書給我帶來的一個比較大的啓發。

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

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