架構(gòu)即代碼,是一種架構(gòu)設(shè)計和治理的思想,它圍繞于架構(gòu)的一系列模式,將架構(gòu)元素、特征進行組合與呈現(xiàn),并將架構(gòu)決策與設(shè)計原則等緊密的與系統(tǒng)相結(jié)合。
如我的上一篇文章《為“架構(gòu)”再建個模:如何用代碼描述軟件架構(gòu)?》中所說,要準確描述軟件的架構(gòu)是一件頗具難度的事情。僅就實現(xiàn)的層面來說,也已經(jīng)很難通過一個標準模型來讓所有人達成一致,“哦,這就是架構(gòu)”。也因此,在無法定義架構(gòu)的情況下,也很難無法給出一個讓所有人信服的架構(gòu)治理模型。畢竟:模型只有合適的,永遠沒有對的。
但是呢,我們(ArchGuard Team)依舊會在 ArchGuard 構(gòu)建出一個架構(gòu)模型,以及架構(gòu)治理模型,作為推薦的 “最佳實踐”。除此,我們還應(yīng)該提供一種自定義企業(yè)應(yīng)用架構(gòu)的可能性,這就是架構(gòu)即代碼。面向初級架構(gòu)師來說,他們只需要按照 ArchGuard 的最佳實踐來實施即可;面向中高級架師,他們可以基于 ArchGuard 提供的插件化能力 + DSL 構(gòu)建自己的架構(gòu)體系。
所以,如你在其它系統(tǒng)中所看到的那樣,要提供這樣的能力,需要一定的編碼、配置等。所以,我們就需要構(gòu)建一個架構(gòu)即代碼的系統(tǒng)。那么,問題來了,即代碼又是什么鬼。
架構(gòu)即代碼是什么?
在先前的一系列的代碼化(https://ascode.ink/)文章中,描述了如何將軟件開發(fā)完全代碼化,包含了將文檔、需求、設(shè)計、代碼、構(gòu)建、部署、運營等變成代碼化。設(shè)計和實現(xiàn)一個領(lǐng)域特定語言并不難,如《領(lǐng)域特定語言設(shè)計技巧》一文中所描述的過程,在這個上下文之下就是:
- 定義呈現(xiàn)模式。尋找適合于呈現(xiàn)架構(gòu)的方式,如 UML 圖、依賴圖、時序圖等。
- 提煉領(lǐng)域特定名詞。一系列的架構(gòu)相關(guān)元素,如架構(gòu)風(fēng)格:微內(nèi)核等、架構(gòu)分層:MVC 等。
- 設(shè)計關(guān)聯(lián)關(guān)系與語法。如何以自然的方式來關(guān)聯(lián)這些架構(gòu)元素,如關(guān)鍵詞、解析占位符等。
- 實現(xiàn)語法解析。除了實現(xiàn)之后,另外一種還要考慮的是:如何提供更靈活的擴展能力?
- 演進語言的設(shè)計。版本迭代
也因此,我們將架構(gòu)即代碼定義為:
架構(gòu)即代碼,是一種架構(gòu)設(shè)計和治理的思想,它圍繞于架構(gòu)的一系列模式,將架構(gòu)元素、特征進行組合與呈現(xiàn),并將架構(gòu)決策與設(shè)計原則等緊密的與系統(tǒng)相結(jié)合。
接下來的問題就是,如何將這個理念有機的與系統(tǒng)結(jié)合在一起?并友好地提供這樣的 API 接口(DSL)?
于是放到當(dāng)前 ArchGuard 的 PoC,架構(gòu)即代碼的呈現(xiàn)方式是 “ArchDoc”,一種基于 Markdown 的交互式代碼分析和治理方式。即所有的 “代碼” 都通過 markdown 來管理,優(yōu)點有一大堆:
- 使用內(nèi)嵌 DSL (用語法塊管理)表述架構(gòu)
- 可以記錄系統(tǒng)的架構(gòu)文檔,如架構(gòu)決策、業(yè)務(wù)架構(gòu)等
- 擁有廣泛的解析庫,能提供更靈活的定制靈感(Ctrl + C, Ctrl + V)。
- 自定義 Render
- 廣泛的編輯工具支持
唯一的缺點就是實現(xiàn)這樣一個工具并不簡單。
架構(gòu)即代碼的特點
不過,我們已經(jīng)實現(xiàn)了一個簡單的 PoC(概念證明)版本,在這個版本里,它的特點是:
- 顯式地描述與呈現(xiàn)架構(gòu)。
- 架構(gòu)文檔即是規(guī)則
- 設(shè)計、文檔與實現(xiàn)一致
當(dāng)然了,還有各種的可擴展能力(這是一個再普通不過的特點了)。
顯式地描述與呈現(xiàn)架構(gòu)
回到日常里,我們經(jīng)常聽架構(gòu)師說,“我們的服務(wù)采用的是標準的 DDD 的分層架構(gòu)”。但是,這個分層是不是諸如于 “Interface 層依賴于 application、domain、infrastructure 層” 等一系列的依賴關(guān)系?開發(fā)人員是否知道這些規(guī)則?這些都是問題。所以,一個架構(gòu)即代碼的系統(tǒng),它應(yīng)該能顯式地呈現(xiàn)出系統(tǒng)中的那些隱性知識。
諸如于,我們應(yīng)該將分層中的依賴關(guān)系,顯式地聲明寫出來:
layered {
prefixId("org.archguard")
component("interface") dependentOn component("application")
組件("interface") 依賴于 組件("domain")
component("interface") dependentOn component("infrastructure")
組件("application") 依賴于 組件("domain")
組件("application") 依賴于 組件("infrastructure")
組件("domain") 依賴于 組件("infrastructure")
}
PS:請忽視上面 Kotlin 代碼中的中文元素,它只是用來說明使用中文描述的可能性。畢竟,開心的話,也可以使用文言文。
結(jié)合 ArchGuard 中的 DSL 與可視化工具(這里采用的是 Mermaid.js),就能呈現(xiàn)我們所設(shè)計的分層架構(gòu):
再再結(jié)合一下設(shè)計的分層 Linter 工具(正在實現(xiàn)中):
linter('Backend').layer()
一旦分層中的依賴關(guān)系錯了,就可以在持續(xù)集成中阻斷這些代碼的提交 —— 類似于 ArchUnit 這樣的機制。稍有區(qū)別的是,你不需要將測試和代碼放在代碼庫中,而是可以統(tǒng)一的去管理它們。
而對于其它一系列的更復(fù)雜的規(guī)則來說,我們可以自定義它們,并將他們與文檔結(jié)合在一起。
架構(gòu)文檔即是規(guī)則
在這種模式之下,我們還可以將文檔與代碼相結(jié)合 —— 前提是:我們已經(jīng)編寫了一系列的規(guī)則。如我們在 ArchGuard 中,針對于不同的場景編寫了一系列的規(guī)則:
- SQL,如不允許
select *
等 - Test Code,用于檢測代碼中的壞味道
- Web API ,分析 API 的設(shè)計是否 RESTful
- Layer (待實現(xiàn)),分析代碼中的分層實現(xiàn)
- Arch (待實現(xiàn)),類似于 ArchUnit 或者 Guarding 制定更細的依賴規(guī)則
- Change(待實現(xiàn)),編寫自定義的變更影響范圍規(guī)則,如某個類不應(yīng)該被其它的變更影響到
有了基本架構(gòu)文檔規(guī)范之后,我們可以規(guī)則化它們,并結(jié)合到一起。如下是一個結(jié)合 Checklist 和規(guī)則的列表示例:
- [x] 不應(yīng)該存在被忽略(Ignore、Disabled)的測試用例 (#no-ignore-test)
- [ ] 允許存在重復(fù)的 assertion (#redundant-assertion)
#no-ignore-test
對應(yīng)于正在實現(xiàn)的 ArchGuard 中的規(guī)則,而 GFM 的 Checklist 中,如果 check 了,則可以表示為開啟規(guī)則;如果沒有 check,則為不開啟。前面的文字部分,則是對應(yīng)的規(guī)則描述,與傳統(tǒng)的 linter 相比較,略顯靈活。
而不論是編寫文檔還是閱讀文檔的人,他們可以很輕松地構(gòu)建起對應(yīng)的上下文。
設(shè)計、文檔與代碼一致
有了設(shè)計和文檔之后,就需要結(jié)合到已有的代碼中,讓三者保持一致和準確。在我們的場景之下,就是 ArchGuard 已有的 API,它包含了:
- 創(chuàng)建對于代碼倉庫的分析
- 分析代碼的語法和構(gòu)建工具、變更歷史等
- 分析代碼是否滿足規(guī)則等
如下是 ArchGuard 中對于 repo 設(shè)計的 DSL(基于 Kotlin),用于創(chuàng)建代碼倉庫的分析:
repos {
repo(name = "Backend", language = "Kotlin", scmUrl = "https://github.com/archguard/archguard")
repo(name = "Frontend", language = "TypeScript", scmUrl = "https://github.com/archguard/archguard-frontend")
repo(name = "Scanner", language = "Kotlin", scmUrl = "https://github.com/archguard/scanner")
}
只有三者保持了一致,我們才能確保架構(gòu)的設(shè)計與實現(xiàn)是一致的。
架構(gòu)即代碼是個什么系統(tǒng)?
從實現(xiàn)的層面來說,一個架構(gòu)即代碼系統(tǒng)是一個支持編排的數(shù)據(jù)系統(tǒng)。原因在于,我們并不想關(guān)心數(shù)據(jù)處理的過程,但是想獲取數(shù)據(jù)的結(jié)果,從結(jié)果中獲取洞見。正如,我們所見到的一個個大數(shù)據(jù)系統(tǒng),構(gòu)建了一個個的可視化能力,以祈禱從中得到洞見。
不過,和祈禱稍有不同的是,我們是帶著 N% 可能性的猜想,所以叫做探索。
一種探索式的架構(gòu)治理
傳統(tǒng)的軟件開發(fā)模型是:編輯-編譯-運行(edit-compile-run),這種開發(fā)模型的前提是,我們擁有足夠的業(yè)務(wù)洞見。對于一個帶著豐富領(lǐng)域知識的業(yè)務(wù)系統(tǒng)來說,構(gòu)建這樣一個系統(tǒng)并不是一件困難。但是,當(dāng)我們?nèi)狈ψ銐虻念I(lǐng)域?qū)<遥覀儜?yīng)該如何往下走呢?復(fù)雜問題,你只能探索 (Probe) -> 感知 (Sense) -> 響應(yīng) (Respond)。
而既然我們本身和很多新生代的架構(gòu)師一樣,也需要探索,也需要分析,然后才是得到結(jié)論。那么,我們不妨再嘗試切換一下模式。如同,我們構(gòu)建 ArchGuard 的軟件開發(fā)模型,也是執(zhí)行-探索(execute-explore),先從分析一下系統(tǒng)(發(fā)布一個分析功能),再配合已有的模式,最后得到 “結(jié)論” 或者規(guī)則(再發(fā)布一個 linter 功能)。
在數(shù)據(jù)領(lǐng)域,這種方式相當(dāng)?shù)牧餍校^去人們用 IPython,現(xiàn)在都改用 Jupyter;另外一個類型則是類似于 RMarkdown 提供的報表式的思路。
- IPython。 is a command shell for interactive computing in multiple programming languages.
- Jupyter Notebook. is a web-based interactive computing platform.
- R Markdown。Turn your analyses into high quality documents, reports, presentations and dashboards with R Markdown.
- D3.js 社區(qū)的 Observable。用于 Explore, analyze, and explain data. As a team.
從模式上來說,ArchGuard 更偏向于 RStudio 的模式,只是從社區(qū)的資源上來說,Jupyter 相關(guān)的實現(xiàn)比較多。
一個經(jīng)常 OOM 的 “大數(shù)據(jù)系統(tǒng)”
在我們(ArchGuard core team)的 “數(shù)次討論” 中,最終認為 ArchGuard 是一個大數(shù)據(jù)分析,而不是簡單的數(shù)據(jù)分析。原因是系統(tǒng)中存在大量的 bug 和大數(shù)據(jù)相關(guān)的(狗頭):
- 存在一定數(shù)量的 Out of Memory。
- 大數(shù)據(jù)量情況下的可視化優(yōu)化。
也就是所謂的 ”bug 驅(qū)動的架構(gòu)設(shè)計“。
除此,之后另外一個頗有意思的點是,對于更大型的系統(tǒng)來說,它存在大量的新的提交,又或者是新的分支。我們即需要考慮:應(yīng)對持續(xù)提交的代碼,構(gòu)建增量分析的功能。
當(dāng)我們嘗試使用大數(shù)據(jù)的思路,如 MapReduce、Streaming Analysis 相關(guān)的模式來解決相關(guān)的問題時,發(fā)現(xiàn)它是可以 work 的不錯的 —— 畢竟都是數(shù)據(jù)分析。
在 ArchGuard 是如何實現(xiàn)的?
ArchGuard 圍繞于 DSL + Kotlin REPL + 數(shù)據(jù)可視化,構(gòu)建了一個可交互的架構(gòu)分析與治理平臺。因為還在實現(xiàn)中,所以叫下一代。
1. 提煉架構(gòu)元素
上文中的(https://ascode.ink/)系列中,也包含了兩個架構(gòu)相關(guān)的工具,一個是代碼生成 DSL:Forming、另外一個則是架構(gòu)守護 DSL:Guarding。兩個 DSL 所做的事情是,圍繞特定的規(guī)則將架構(gòu)元素組合到一起,這里的架構(gòu)元素。
如果沒有做過,這一個過程看上去是挺麻煩的,實現(xiàn)上有一些頗為簡單的東西可以參考(復(fù)制):
- 架構(gòu)描述語言論文(ADL)。ADL 已經(jīng)是一個很成熟的領(lǐng)域了,在設(shè)計模式火的那個年代,架構(gòu)模式(《面向模式的軟件架構(gòu)》)也特別的火。
- 架構(gòu)相關(guān)書籍的目錄。一本好的架構(gòu)書,只需要看目錄就能有個索引,所以也就有了基本的架構(gòu)元素。
- 架構(gòu)的模式語言。模式語言所呈現(xiàn)的是模式之間的關(guān)系
- ……
僅僅是復(fù)制那多沒意思,要是能自己做做抽象,也是一種非常好玩的事情。
2. 構(gòu)建插件化與規(guī)則分析
如上所述,在 ArchGuard 中,我們嘗試以一系列的規(guī)則,構(gòu)建系統(tǒng)的規(guī)則,而這些規(guī)則是以插件化的形式暴露的。
這就意味著,這樣一個系統(tǒng)應(yīng)該是支持自定義的插件化能力,它即可以讓你:
- 接入一個新的語言
- 編寫新的規(guī)則
- 構(gòu)建新數(shù)據(jù)的 pipeline
在 ArchGuard 中還需要改進的是,提供一種元數(shù)據(jù)的能力。
3. 抽象 DSL 作為膠水
從實現(xiàn)層面來說,為了支撐粘合的能力,我們目前計劃設(shè)計了三種能力的 DSL:后端架構(gòu)查詢 DSL、架構(gòu) DSL、特征 DSL。
后端架構(gòu)查詢 DSL
類似于 LINQ (Language Integrated Query,語言集成查詢)封裝 CRUD 接口,以提供編譯時類型檢查或智能感知支持,在 Kotlin 中有諸如于:KtOrm 的形式。如:
database
.from(Employees)
.select(Employees.name)
.where { (Employees.departmentId eq 1) and (Employees.name like "%vince%") }
.forEach { row ->
println(row[Employees.name])
}
像一個編程語言編寫,可以提供更友好的語法性支持。
架構(gòu) DSL
即架構(gòu)描述語言(Architecture Description Language),以提供一種有效的方式來描述軟件架構(gòu)。
特征 DSL:分析、掃描與 Linter
即封裝 ArchGuard Scanner、Analyser、Linter 等,用于構(gòu)建系統(tǒng)所需要的基礎(chǔ)性架構(gòu)特征。
4. 構(gòu)建可交互的環(huán)境
兩年前,在與眾多的 Thoughtworker 一起構(gòu)建 Ledge 的時候,我們就一直在強調(diào)文檔代碼化,并提供可交互的文檔環(huán)境。在 Ledge 里,你可以使用 Markdown 來繪制各類的圖表,只需要借助聲明圖表類型,示例見:https://devops.phodal.com/helper 。
從模式上來說,ArchGuard 更像是一個 RStudio + Jupyter 的結(jié)合版,即提供了大量自定義圖形 + 組件能力的 REPL。
在 REPL 上,由于我們計劃使用 Kotlin 構(gòu)建 DSL,所以需要尋找的是 Kotlin 的 REPL。Kotlin 官方創(chuàng)建的 kotlin-jupyter 便成為了一個很好的參考,可惜還沒有用得上。與此同時,Kotlin 在設(shè)計初期就有了 Kotlin Scripting 的場景,所以其實 kotlin-scripting-compiler-embeddable
就能滿足需求。于是,在 PoC 里,我們參考了 Apache Zeppelin 引入了 Kotlin REPL,并創(chuàng)建了一個 WebSocket 作為服務(wù)。
在可視化上,稍微復(fù)雜一些,需要構(gòu)建一個 Markdown 解析器、Block 編輯器等。我們暫時采用了 Mermaid.js 作為可視化的圖形庫之一,另外的還有 D3.js、Echarts 也是其中之一。剩下的問題,便是如何通過 DSL 來整合它們?構(gòu)建前后端的數(shù)據(jù)模型是一個臨時的方案?
PoC 示例見截圖:
5. 依舊是一個 PoC
在這里,ArchGuard 的交互性分析,依然只是一個 PoC(概念證明),但是在不遠的將來,你就可以在 ArchGuard 中使用它了。
其它
構(gòu)建一個這樣復(fù)雜的工具,并不是一件容易的事。歡迎加入 ArchGuard,一起學(xué)習(xí)架構(gòu)和架構(gòu)治理,還有開發(fā)一個純技術(shù)驅(qū)動的開源軟件。
如果你想實踐以下的技術(shù),手把手教你學(xué)會:
- 編譯器前端。對設(shè)計和實現(xiàn) DSL 有興趣
- 編譯器周邊。Kotlin 的編譯器使用
- ……
當(dāng)然,如果你也感興趣于:
- 改進一個遺留系統(tǒng)。重構(gòu)和設(shè)計 ArchGuard 的前端、后端。
雖然,我們無法向你提供 code,但是說不定未來可以呢 —— 作為一個開源的自研項目,萬一能構(gòu)建開放式的商業(yè)模式?除此,你可以 show your code in public,認更多的行業(yè)上的架構(gòu)師認識你。