過去的幾個月的業餘時間裡,一直在設計一個名為 Fklang ( https://github。com/feakin/fklang )的架構 DSL,以 DDD(領域驅動設計)為指導思想構建,除了完成 MVP 原型的編譯器與程式碼生成,還可以使用 Jetbrains IDE 開發(搜尋 Feakin)。
首先,架構描述語言或者設計語言並不是一個新的東西,Fklang 也是
舊瓶新裝
。我們只是按自己的理解去實現了一遍,只是在實現的過程中,我們發現:
基於標準化的方法論,可以實現規模化的軟體開發
。
為此,在開發 Fklang 的過程中,便嘗試結合了 “大魔頭” 的型別流(Typeflow)思想,便也以
軟體開發工業化
作為 Fklang 的目標之一。只是呢,對於 Fklang 而言,要實現開發工業化,還需要對於基礎設施做一系列抽象(後面詳細展開)。這也就是為什麼文章的標題是探索。
TL;DR 版本:立即開始你的吐槽之旅途:https://book。feakin。com/quick-start 。
引子 1:Fklang 緣由:ArchGuard 架構治理前移
我們開發 Fklang 的初衷是,為了實踐在 ArchGuard 中定義的三態模型中的設計態。對應三態如下:
設計態:目標架構。透過 DSL(領域特定語言) + 架構工作臺來構建 。
開發態:實現架構。關注於:視覺化 + 自定義分析 + 架構治理。
執行態:執行架構。結合 APM 工具,構建完整的分析鏈。
在 ArchGuard 中,我們關注於對開發態的治理,而其中的手段之一是:
規範工具化
。規範本身是應該內建的,諸如於我們應該制定好分層架構,諸如於 DDD 分層模式。並將這個分層架構與程式碼實現相繫結,再結合到開發工具中。諸如於 Fklang 的
layer
分層語法便是基於這個理念設計的:
layered DDD
{
dependency
{
interface
->
application
application
->
domain
interface
->
domain
application
->
infrastructure
interface
->
infrastructure
}
layer interface
{
package
:
“com。example。book”
;
}
。。。
}
在與 IDE 結合的情況下,我們就能在開發的過程中,避免開發人員破壞分層架構。這便是 Fklang 的第一個設計理念:
顯性化意圖設計
。
引子 2:領域驅動設計的標準化方法
在設計 Fklang 的過程中,我們也探索了一系列的架構描述語言,它們都有自己的標準方法論。與此不同的是,我們覺得采用現行的標準化方法,才能讓架構語言更容易落地。考慮到,現在更流行的架構設計方法論是 DDD 模式,在進行 DDD 建模工作坊 時,採用事件風暴或者其它方法,都是透過協作設計的方式進行的,而最後需要一個規範化的輸出。
Fklang 便是承載了規範化輸出部分,將圖形設計程式碼化,將與實現程式碼相結合(如程式碼生成等)。在設計 Fklang 的 DDD 部分語法,我們參考了 ContextMapper 部分(主要也是設計不出差異),示例如下所示:
ContextMap
TicketBooking
{
Reservation
->
Cinema
;
Reservation
->
Movie
;
Reservation
->
User
;
}
Context
Reservation
{
Aggregate
Reservation
;
}
Context
Cinema
{
Aggregate
Cinema
;
}
透過 Feakin 線上工具(https://online。feakin。com/),可以將上述的 DSL 顯性化出來,用於與架構師和開發人員進行交流:
PS:因為 Fklang 還沒有實現完整的型別系統,所以在現在的實現是與 DDD 相繫結。
引子 3:實現細節與基礎設施抽象
五年前,在編寫《Serverless 應用開發指南》時,我便覺得 Serverless 對於規模型團隊來說,並不是一個很好的解決方案。但是呢,它提供了一個非常好的架構思考方式:
對實現細節的抽象化
。所以,再回到 Bob 大叔對於《架構整潔之道》的【第 6 部分:實現細節】:
資料庫是實現細節
Web 是實現細節
應用框架是實現細節
然後呢,然後呢,我們需要一個漸進式的 Darklang(https://darklang。com/),它與框架、Web、資料庫無關。我們在寫程式碼的時候,往往只會配置過一次資料庫,剩下的資料庫操作可能是在
刪表與重建
。也因此,在描述資料庫時,我們要配置的應該是 env,配置怎樣的資料庫,怎麼的 http server 等等。Fklang 示例如下:
env
Local
{
datasource
{
driver
:
postgresql
host
:
“localhost”
port
:
5432
database
:
“test”
}
server
{
port
:
9090
;
}
}
既然,我們在設計階段已經定義好了 Context、Aggregate、Entity 等等,那麼這個時候它是不是就可以作為一個 Web Server 執行起來呢?如下圖所示:
這便引發了我們對於軟體開發工業化的思考。
軟體開發工業化:定義下一代架構
雖然,現在我們並沒有在 Fklang 中實現真正的軟體開發工業化。但是呢,我想在這裡分享一下探索過程中的一些理解:
軟體開發工業化是一種批次式加工的軟體模式,充分利用機器的學習能力與人工設計的智慧,以在部分工序上由機器取代人,進而實現軟體開發的快速規模化。
簡單來說,對於每個功能而言,開發人員接收到需求之後,只需要編寫
對應函式
中的功能,剩下的交由 AI 自行去生成與判斷。諸如於,我們要新新增一個建立待辦事項的 API,那麼就自動生成 Controller、Repository 的程式碼,開發人員只需要編寫 Service 中的那個對應
createTodo
方法即可。至於重構嘛,我覺得也不需要,發現重複程式碼之後,AI 應該自動幫你重構。
基於這樣的考慮,我們覺得實現工業化應該達到三個核心點:
設計與實現細節分離
。只編寫核心業務邏輯,無需關注於所有的輸入和輸出,如資料庫、Web API 等。
全生命週期半智慧化
。對於每一個環節進行量化,便可以實現架構進行自調節。
模式內建於工具
。工業化意味著標準化,也意味著知識的固化到系統中,模式是軟體開發的核心知識,應該由工具來繼承。
而一旦實現了架構治理的前置,我們便不再需要關注於如何治理。
設計與實現細節分離:基礎設施抽象
在這一點上,我們與傳統的 “甩手架構師” 是保持一樣的考慮,設計與一部分的實現細節是相分離的。只是核心的差異之處在於,我們作為架構師,應該構建出基礎設施抽象,並顯示化出設計意圖,諸如於我們前面強調的《實現細節與基礎設施抽象》。
我們可以拿 Serverless 架構或者 Faas(函式即服務)作為一個參照物。在採用 FaaS 架構的模式之下,我們並不關注於基礎設施,雲廠商或者基礎設施部門會提供彈性的架構支援。但是呢,FaaS 粒度過細,過多的程序使得我們無法接受這個成本。所以,在考慮工業化時,我們需要實現:
基於微服務之上的函式即服務(FaaS on Microservices)
。從模式上類似於 OSGI 模型,只是實現機制是不一樣的。我們理解的類似
FaaS on Microservices
的架構,可以每個聚合(aggregate )在開發時獨立執行,又可以與其他聚合一起工作。
而作為開發人員,他們不需要關注於 Web 介面與資料庫介面,只需要編寫核心業務邏輯即可,Controller 和 JPA 介面可以由設計生成,以達到框架、資料庫與設計無關。簡單來說,開發人員只需要編寫
處理函式
就足夠了,應對於 MVC 的 service 中的一個方法,處理輸入並返回輸出結果。
全生命週期半智慧化:量化與架構自調節
我們堅信軟體開發工業化另外一個點在於:
基於程式碼模型的 AI 程式碼生成
。
設計態。透過在 DSL 中寫入基本的設計,來生成程式碼與空函式,讓開發人員選擇與填空。
開發態。結合 AI 與現有的程式碼庫能力,來判斷邏輯是否正確,並進行調整。
執行態。透過監測 API 的執行情況,來自動調整 DDD 中的聚合、限界上下文。
在設計 Fklang 的過程中,我們構建了一個 flow 語法,它是用來生成註釋給 AI 看的:
impl
UserCreated
{
endpoint
{
POST
“/user/{id}”
;
}
flow
{
via
UserRepository
::
getUserById receive user
:
User
via
UserRepository
::
save
(
user
:
User
)
receive user
:
User
;
via
Kafak
send
User
to
“user。create”
;
}
}
其設計思想來源於,我們日常溝通中的,你這個 API 需要先查詢哪個表,再 xxx,最後再 xxx。雖然,設計得還比較粗糙,重點還在於輸入和輸出,在配置了分層之後,會在對應的 Controller (UserController)中插入對應的程式碼:
@PostMapping
(
“/user/{id}”
)
public
User
createUser
()
{
// 1。 get user:User from UserRepository。getUserById with ()
// 2。 get user:User from UserRepository。save with (user:User)
// 3。 send User from Kafak to “user。create”
}
在引入 GitHub Copilot 之後,便可以自動生成靠譜,還有不靠譜的程式碼。
除此,既然 AI 訓練可以實現由 AI 自行調參,併發方面是不是應該自行有機器來學習和設計?而要實現自調節要做的第一件事就是量化,定義成功,以反饋驅動設計。
模式內建於工具
工業化與手工作坊的區別在於,手工作坊做出的東西品質差異比較大,好的非常好,差的非常差。而工業化,雖然比不上好的,但是至少品質如一。他們所做的事情便是,將模式化的套路內建於機器的流水線之中。對於軟體開發來說,也是相似的:
採用諸如於 DDD 的 “標準化” 設計方法。
採用統一的開發語言。
……
這其實也是每個公司想去做提效的部分。從工業化的角度來說,這裡我們認為:複用、效率與規模化是類似於 CAP 的不可能三角。在設計 Uncode 我們一直想做的事情是:將軟體開發過程程式碼化,以實現自動化。相似的,對於工業化來說,我們也需要在現有的工具中,實現對於這些成熟模式的整合。
從另外一個角度來說,我覺得現有的 IDE 依舊有很大的改進空間。諸如於,既然大部分開發人員不用 TDD,那麼 IDE 在接受了 debug 之後,是不是可以記下引數,自動生成測試?
Fklang:DDD 驅動的架構 DSL
最後,讓我們簡單再介紹一下 Fklang,一個由 DDD 思想驅動的架構設計語言。
核心設計理念
Fklang 的目標是:透過宣告式 DSL 來繫結程式碼實現與架構設計,保證架構設計與實現的一致性。為此,我們有三個核心設計理念:
架構孿生:雙態繫結。提供架構設計態與實現態的雙向繫結,保證架構設計與實現的一致性。
顯性化設計意圖。將軟體設計的意圖化,藉助於 DSL 語言的特性,將意圖轉換化程式碼。
型別與事件驅動。透過事件驅動的方式,將資料型別與領域事件進行繫結。
PS:詳細介紹見:https://book。feakin。com/design-principles (還沒寫完)
大部分的內容已經在上面的內容裡介紹了。回到 Fklang 中,我們面臨的第一個挑戰是:如何在不影響開發效率的前提下,保證架構設計與實現的一致性?對於一個架構語言來說,要讓開發人員採用的一個關鍵點是:
如何真正地提升開發效率
?所以,這也就是我們依賴在探索的地方。
Fklang 的其他效能探索
基於 DDD 產物的 Mock Server
。既然 Fklang 能作為 DDD 設計結果的承載物,那麼考慮到 API 設計也是其中的一部分,自然而然地 Mock Server 也是可以跑起來的 —— 讀取 Aggregate、Entity 等生成 API。所以,一個 Mock Server 日誌示例:
fkl run
——
main
/
Volumes
/
source
/
feakin
/
fklang
/
docs
/
samples
/
impl
。
fkl
——
func mock
-
server
Running
at http
:
//localhost:9090 !
Routes
:
http
:
//localhost:9090/api/cinema/cinema
http
:
//localhost:9090/api/cinema/cinema/1
http
:
//localhost:9090/api/cinema/screeningroom
http
:
//localhost:9090/api/cinema/screeningroom/1
http
:
//localhost:9090/api/cinema/seat
http
:
//localhost:9090/api/cinema/seat/1
http
:
//localhost:9090/api/movie/movie
基於 API 的契約測試。
相似的,我們也將 API 契約作為測試的一部分,可用於測試 API 的實現是否是正確的,如下所示:
impl
UserUpdated
{
endpoint
{
PUT
“/user/{id}”
;
request
:
UpdateUser
;
response
:
User
;
}
}
不過,現在支援最好的是 GET 請求:
[
2022
-
11
-
20T08
:
58
:
39Z
INFO fkl
]
runOpt
:
RunOpt
{
main
:
“/Volumes/source/feakin/fklang/docs/samples/impl。fkl”
,
path
:
None
,
impl_name
:
Some
(
“PackageJsonGet”
),
env
:
None
,
func_name
:
HttpRequest
,
custom_func
:
None
}
[
2022
-
11
-
20T08
:
58
:
39Z
INFO fkl
::
builtin
::
funcs
::
http_request
]
headers
:
{
“user-agent”
:
“Mozilla/5。0 (Windows; U; Windows NT 5。1) AppleWebKit/533。2。1 (KHTML, like Gecko) Chrome/24。0。811。0 Safari/533。2。1”
}
[
2022
-
11
-
20T08
:
58
:
39Z
INFO fkl
::
builtin
::
funcs
::
http_request
]
Content
-
Type
:
text
/
plain
;
charset
=
utf
-
8
在未來,它也可以作為自動化測試的核心部分。
小結
儘管,在當前的版本里,我們使用的是 Rust + Pest 的方式開發。但是呢,在設計上我們已經趨近於 Kotlin DSL 的風格,方便於未來進行擴充套件。最後,再 Show 一下程式碼
文件地址:https://book。feakin。com/
IDEA 外掛下載:https://plugins。jetbrains。com/plugin/20026-feakin/versions/stable/229113
Fklang 專案地址:https://github。com/feakin/fklang
IDEA 外掛原始碼:https://github。com/feakin/intellij-feakin