天天看點

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

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

作者 | 劍癡

來源 |

阿裡技術公衆号

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

——瑞·達利歐(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開發手冊》中已有叙述,這裡就不在贅述。

三 總結

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

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