文章首發於公衆號:松花皮蛋的黑板報
做者就任於京東,在穩定性保障、敏捷開發、高級JAVA、微服務架構有深刻的理解數據庫
文章來源:www.liangsonghua.me
做者介紹:京東資深工程師-梁鬆華,長期關注穩定性保障、敏捷開發、JAVA高級、微服務架構架構
1、複雜性和規模增加的解決之道微服務
解決複雜和大規模軟件的武器能夠被粗略地歸爲三類:抽象、分治和知識工具
一、分治
把問題空間分割爲規模更小且易於處理的若干子問題。分割後的問題須要足夠小,以便一我的單槍匹馬就可以解決他們;其次,必須考慮如何將分割後的各個部分裝配爲總體。分割得越合理越易於理解,在裝配成總體時,所需跟蹤的細節也就越少。即更容易設計各部分的協做方式。評判什麼是分治得好,即高內聚低耦合。以火車爲例,每一個車箱都要符合承重要求,行李車箱承重能力要高於其餘車箱,車箱間的鏈接要牢固且易於拆解,有足夠的靈活性方便轉彎。另外大件行李應該單獨存放,避免佔用普通車箱的空間,餐車應該在整個列車的中間,方便用餐測試
二、抽象
使用抽象可以精簡問題空間,並且問題越小越容易理解。舉個例子,從北京到上海出差,能夠先理解爲使用交通工具前往,但不須要一開始就想清楚究竟是高鐵仍是飛機,以及乘坐他們須要注意什麼this
三、知識
經過知識手段抽象出限界上下文以及如何去分治編碼
2、DDD概覽
DDD全稱爲「Domain Driven Design」,意思是領域驅動設計,基於業務知識設計系統,代碼反映業務,它將真實業務概念和業務規則轉換爲軟件系統中的概念和規則,在一個個有界上下文中發揮其做用,完成用戶要求的功能,從而下降或隱藏業務複雜性,使系統有更好的擴展性,以擁抱變化。說白了你那套領域邏輯到底有沒有效?邏輯上自洽爲真,沒有邏輯矛盾,是否是現實中也是爲真?須要在有界上下文中驗證spa
舉個例子:假若有父親和兒子這兩個表,生成的 POJO 應該是翻譯
public class Father{…} public class Son{ //son 表裏有 fatherId 做爲 Father 表 id 外鍵 private String fatherId; public String getFatherId(){ return fatherId; } …… }
這時候兒子犯了點什麼錯,老爸很是不爽地扇了兒子一個耳光,老爸手疼,兒子臉疼。Manager一般這麼作設計
public class SomeManager{ public void fatherSlapSon(Father father, Son son){ // 假設 painOnHand, painOnFace 都是數據庫字段 father.setPainOnHand(); son.setPainOnFace(); } }
這裏,manager充當了上帝的角色,扇個耳光都得他老人家幫忙。可是現實世界應該是教訓兒子是本身的事情,並不須要別人幫忙,上帝也不行
public class Father{ public void slapSon(Son son){ this.setPainOnHand(); son.setPainOnFace(); } }
3、關注點
一、微服務關注點
1)、運行時進程間通訊,可以容錯和故障隔離
2)、去中心化管理數據和冶理
3)、服務能夠獨立的開發、測試、構建、部署
4)、高內聚低耦合,職責單一
二、DDD關注點
1)、關注業務領域,創建邊界並構建通用語言,高效溝通
2)、對業務進行抽象,和業務專家一塊兒建模
3)、儘量維持代碼和業務的低表示差別
一個供電系統中,合同部門關心的是電價,用電跟蹤部門關心的是電量,到告終算部門則是關心購電量,他們只關心本身所轄邊界內的重心,用不一樣方言在講話,沒有造成統一語言。實際上購電量=電價+電量+用戶時間段+用電規則,購電量纔是業務模型中的核心方法,它應該是模型中的通用語言。有了統一語言後,能夠減小將業務架構映射到系統架構時的層層翻譯,避免組件劃分過程當中的邊界錯位,加強系統對業務的響應速度
從上面你能夠發現DDD強調的是邏輯劃分,微服務強調的是隔離部署,系統架構的邏輯劃分能夠細於部署單元的物理隔離,較好的架構應該是演進式,避免過早微服務化帶來的麻煩
4、DDD設計實踐
一、按業務劃分限界上下文
從業務能力的角度識別核心域、支撐域、通用域並去除二義性,好比電商業務中訂單就是核心域,訂單服務產生的其餘業務則是支撐域,而通知中心、短信服務則是通用域
二、消除隱式數據依賴
通常狀況下訂單表會使用user_id做爲用戶標識,但若是系統未來要對接企業,咱們能夠新增一個訂單服務可是開發維護成本太高,能夠新增一個企業員工記錄可是員工離職交接維護成本太高,能夠新增一個虛擬員工帳號可是無心間耦合了,正確作法是新增一個企業用戶上下文,而後使用」用戶ID加用戶類型」標識購買者,消除隱式數據依賴
《架構整潔之道》這本書中說到,任何形式的共享數據行爲都會致使強耦合,若是給服務之間傳遞的數據記錄中增長一個新字段,那麼每一個須要操做這個字段的服務都必需要作出相應的變動,服務間必須對這條數據的解讀達成一致,那麼他們是間接彼此耦合的
三、明肯定義依賴方向
這裏不得不提到整潔架構(又名洋蔥架構),整潔架構最主要原則是依賴原則,它定義了各層的依賴關係,越往裏,依賴越低,代碼級別越高。外圓代碼依賴只能指向內圓,內圓不知道外圓的任何事情。通常來講,外圓的聲明(包括方法、類、變量)不能被內圓引用。一樣的,外圓使用的數據格式也不能被內圓使用
四、下游的自我保護
對於下流來講,須要根據本身的領域模型建立一個單獨的防腐層,該層做爲上游系統的代理向你的系統提供功能,它在你本身的模型和他方模型間進行概念對象和其行爲進行翻譯轉換,好比當數據實體格式不符合系統要求時,只須要在防腐層中添加對應的轉換器便可,領域模型可保持不變
6、DDD編碼的意義
讓代碼體現業務,保持兩者的低表示差別,難點在於對聚合根的實現
在DDD模式中將對象分爲值對象和實體。實體對象是有生命週期的,能夠惟一標識的(不是數據庫中的ID),此對象只能屬於某個業務。而值對象是沒有生命週期的,好比訂單領域上下文中,訂單是實體、訂單項是實體、訂單狀態是值對象。原來咱們系統劃分單位一般是模塊,可是粒度不夠細,因此須要對實體和值對象等進行關聯設計後,進行聚合的劃分和聚合根的肯定,好比訂單和訂單項、訂單和訂單狀態有關聯,他們總體做爲一個聚合,一般聚合中其餘實體須要依賴聚合根,很顯然訂單應該是聚合根(生命週期最長,其餘聚合項離開它沒有任何意義)。DDD模式中對一個聚合中實體的訪問或操做,必須經過這個聚合的聚合根開始,主要的目的是數據的最終一致性。另外聚合根之間不能創建關係,只能經過ID引用
好比
class Book { private @Id Long id; private Set<AutherRef> authors = new HashSet<>(); public void addAuther(Author author) { authers.add(createAuthorRef(author)); } private AuthorRef createAuthorRef(Author author) { AuthorRef authorRef = new AuthorRef(); authorRef.author = author.id; return authorRef; } }
7、總結
傳統開發模式是肯定需求後定義表結構,以數據庫表做爲系統導向,可是DDD建議應該先着眼於業務自己,減小對數據庫表結構的依賴,避免業務發生變化,把咱們打得措手不及,它把你的思考起點從技術的角度拉到了業務上。不過在進行DDD設計時須要注意劃分邊界,注意定義邊界間的關係,注意概念不要穿透邊界
最後你會發現通篇都在談論的「邊界」劃分,咱們知道微服務落地的難點之一就是如何正確折分,若是拆分後的服務出現互相調用或者須要高成本解決各個服務間的數據一致性,將得不償失,因此正確的打開方式是在進行微服務拆分前應該先充分了解領域驅動設計
文章來源:www.liangsonghua.me做者介紹:京東資深工程師-梁鬆華,長期關注穩定性保障、敏捷開發、JAVA高級、微服務架構