Dust:當Truffle遇上資料庫開發
以太坊開發中,Truffle幾乎是事實的標準,用它可以方便地開發、測試和部署智慧合約。雖然它一直對外宣稱Dapp開發框架(隱含前後端一體化,結合node),但我認為它最大的功績就是將合約的整個生命週期有效地管理起來。因為,考慮到不同專案團隊偏好的技術棧不一樣,前後端完全可能是另外一套完全不同的選擇。但合約開發,你沒得選。
因此在我看來,稍微複雜的區塊鏈專案基本組成如下:
- backend,後端服務
- frontend,前端UI
- contract,合約工程
這裡的合約工程即是Truffle大顯身手的地方,雖然你也可以在一個使用web3j的後端工程中包含和管理合約,但我認為這種方式遠比不上用Truffle來得方便。
從這個模式中是否看到傳統專案中類似的場景,比如專案中資料庫製品的管控,即專案中資料庫部分的開發。稍微複雜一點的專案都會涉及到儲存過程、觸發器等資料庫製品的開發。並且,對於創業專案,其早期應該儘可能採用簡單架構,避免一上來就是微服務、事件驅動。用一些資料庫的方式解決問題,看似老舊和擴充套件性存在瑕疵,但好處是實現快速,節約了人力和時間。待概念驗證成功,開始盈利之後在做遷移也不會有太多問題。況且,如果對這部分有良好的管控,對於後期遷移也是好處多多。
本著這樣的想法,我開始思考是否可以將Truffle的模式引入到傳統專案的開發中,用類似的工具管理專案的資料庫製品,於是一個Demo性質的工具誕生了: dust 。這樣,一個專案的結構就變成:
- backend,後端服務
- frontend,前端UI
- database,dust工程
明眼人會提到,目前已經有類似flyway這樣的db migration工具可以幫助我們來管理專案中資料庫部分,那為啥還要有dust呢?這是個好問題!也正是因為有這樣的疑問,dust也僅止於demo程度,只是用來滿足我看到Truffle之後想要實現類似工具管理專案中資料庫製品的“心癮”。
不過,相比起flyway,dust還是有一些不同之處:它集成了一組成熟的工具集,並提供了一套解決方案來管理資料庫製品的整個生命週期,即:“建立 -> 測試 -> 部署 -> 升級”。甚至它也將flyway也納入其中,作為其底層支撐。
dust集成了:
- cli, picocli
- 自動化測試, spock
- 本地資料庫測試環境, testcontainers
- 輕量級的DB Migration工具, flyway
它的基本使用非常簡單自然:
- 建立Dust工程。
- 開發資料庫製品,如表、儲存過程、函式、任務、觸發器等。
- 完成相應的測試程式碼。
- 本地執行測試。
- 部署資料庫製品到不同的環境。
關於其詳細使用,可以訪問它的 github倉庫 。在後續的內容中,我將簡要說明一下dust的實現機制,滿足有興趣進一步瞭解其細節的讀者的胃口。
CLI
dust是一個cli應用,因此選擇好的cli框架會讓整個事情事半功倍。在Java工具集中,picocli算得上是顏值擔當,不僅支援常見cli系統的命令語法,而且還支援色彩。其實現效果並不比其他平臺中流行的cli框架差。
dust本身的命令系統並不複雜,標準的:Command + SubCommand的實現,這一架構可以用picocli輕易做到。而且,還自動生成幫助系統,如下圖。

image.png
具體實現請參見picocli文件和dust的實現。
dust工程
dust工程(使用create子命令建立)本質上就是一個gradle工程。建立它的過程很簡單,就是從dust執行檔案中資源的工程模板複製到指定目錄即可,參考下面的程式碼:
private void copyFolder(Path dest) throws IOException, URISyntaxException { Path src = FileSystems.newFileSystem(URI.create("jar:" + getClass() .getProtectionDomain() .getCodeSource() .getLocation()) , new HashMap<>()).getPath("/template"); Files.walk(src).forEach(source -> copy(source, dest.resolve(src.relativize(source).toString()))); } private void copy(Path source, Path dest) { try { Files.copy(source, dest, REPLACE_EXISTING); } catch (Exception e) { e.printStackTrace(); } }
有了模板工程,又因為是gradle工程,有經驗的開發者幾乎可以不再需要使用“dust”命令了。但是,我想更進一步,就如同Truffle本身也是NPM工程,但也一樣能用“Truffle deploy”這樣的方式執行。
實現這一步有一個投機取巧的方法,事先在模板工程中寫好相應的gradle task,接下來用picocli的command封裝,直接在程式中觸發gradle task呼叫即可。這裡就用到了gradle-tooling-api。但這裡需要留意向task傳入引數的方法,程式碼如下:
@Override public void run() { Path projectPath = Paths.get(".").toAbsolutePath().normalize(); try (ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(projectPath.toFile()).connect()) { connection.newBuild().forTasks("deploy").setJvmArguments("-Dexec.args=" + datasource) .setStandardOutput(System.out) .setStandardError(System.err) .run(); } }
留意其中的setJvmArguments。
這樣,你就可以用類似“dust deploy”的方式來觸發build.gradle中deploy任務的執行了。這同樣是grails這類框架的實現思路。
測試程式碼
這部分很簡單,因為模板工程中引入了Spock依賴。在使用時,開發者只需在test目錄下建立spock specification,然後執行“dust test”就能觸發。當然,其背後仍然是觸發“gradle test”這個任務啦。
本地測試環境
一般來講,涉及到資料庫的測試環境都不那麼友好,然而testcontainers的出現給大家帶來了一絲曙光。其原理跟上面說的一樣,引入相應依賴,直接在測試程式碼中使用即可。最後由“dust test”觸發。
部署指令碼
部署採用的是flyway的Java-based migrations,同時也採用其命名規範。我個人推薦“Versioned Migrations”,不建議採用“Repeatable Migrations”。因為在實際情況中,後者使得製品歷史消失了,本身已經違背了引入Migration的目的。而且,DML類的變動總涉及到資料的遷移,因此堅持“一直前向”是最適合的策略。而且,為了避免頻繁修改指令碼,我建議不要在專案早期引入dbmigration,而是待資料庫製品都相對穩定時,再開始不遲。
為了達到類似Truffle部署指令碼的效果,專案模板工程預置了三個檔案:
- DustBaseMigration.java,部署指令碼基類
- DustConfiguration.groovy,部署的環境配置載入類
- MigrationApp.java,flyway部署執行類
- 注:為什麼不直接在工程模板中引入gradle flyway外掛?原因是我想盡可能給使用者展示成為“dust”而非“flyway”。
一般來講,這三個檔案不需要dust使用者關心,故請不要去刪除或修改它!!!關於部署指令碼,很簡單,繼承DustBaseMigration就好了:
public class V1__CreateUser extends DustBaseMigration { @Override protected String[] files() { return new String[]{ "./artifacts/myuser.sql"// 資料製品檔名 }; } }
遷移指令碼中要求實現的 files() ,其指定了資料庫製品的執行順序,甚至你也可以在 artifacts 目錄下新增資料初始化檔案,然後通過部署指令碼完成部署。如:
@Override protected String[] files() { return new String[]{ "./artifacts/myuser.sql",// 建立 myuser 表 "./artifacts/insert_myuser.sql",// 插入資料 }; }
是不是有了點Truffle Migration指令碼的感覺了?
由於遷移指令碼採用的是 flyway 的 Java-based migrations ,若發現當前提供的 DustBaseMigration 類無法滿足你的要求,請直接使用 flyway 提供的相應工具類。
哦,忘說了,dust在遷移時會自動baseline,同時設定遷移版本為0,因此建立遷移指令碼時V後的數字務必大於0。
環境
這個簡單,在專案模板中引入dust-conf.json,形式如下:
{ "development": { "url": "jdbc:postgresql://127.0.0.1:5432/earth_test?useUnicode=true&characterEncoding=utf8", "user": "earth_admin", "password": "admin" }, "production": { "url": "jdbc:postgresql://127.0.0.1:5432/jupiter_test?useUnicode=true&characterEncoding=utf8", "user": "jupiter_admin", "password": "admin" } }
是不是越來越像Truffle了,哈哈!
安裝指令碼
要將dust釋出成命令列格式,還需完成最後一步:dust執行指令碼的編寫,這裡我們可以求助於gradle的application外掛。它提供了幾個任務自動完成相關工作:
- installdist,它將在工程的 build/install/dust/ 下安裝,bin目錄下為可執行檔案
- distTar/distZip,將生成tar或zip的安裝包
模板工程的build.gradle
最後說說模板工程的build.gradle,因為它需要進行一些定製才能實現我們需要的目錄結構:

image.png
雖然是gradle工程(嚴格的說是gradle groovy專案工程,java工程的超集),但是不是跟常見的目錄結構不一樣?這其中奧祕就在於下面兩行:
sourceSets.main.groovy.srcDirs += ["migrations"] sourceSets.test.groovy.srcDirs += ["test/groovy"]
總結
至此,整個Truffle的面向資料庫開發的克隆版本就這樣搭建完成了,挺好玩的吧,希望大家能喜歡。