軟體開發工業化:架構語言 Fklang 對下一代架構設計的探索

過去的幾個月的業餘時間裡,一直在設計一個名為 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 顯性化出來,用於與架構師和開發人員進行交流:

軟體開發工業化:架構語言 Fklang 對下一代架構設計的探索

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 對下一代架構設計的探索

這便引發了我們對於軟體開發工業化的思考。

軟體開發工業化:定義下一代架構

雖然,現在我們並沒有在 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