現代化Java之技術棧與自動化Clojure

歷經二十多年的發展,Java 已經成爲最成熟和發達的軟件開發體系,有着豐富的技術資源和活躍的社區。但是時間也帶給了 Java 的滄桑感,那麼如何有效提高 Java 項目的開發效率,改善 Java 語言過於樸素所帶來的笨拙感。

關於 Java 的未來發展趨勢,劉鑫有着自己的思考,在新時代下,如何保證 Java 不落伍,如何讓已經“上了歲數的”Java 在工程實踐中煥發活力,劉鑫在此篇文章中詳細介紹了現代化的 Java 應該是怎樣的。以下爲正文內容:

語言、框架與技術棧

關於軟件開發,特別是服務端軟件開發的技術棧的探索,幾乎貫穿了我整個的職業生涯。幾年前我就想寫這樣一個專題,但是開了個頭就又很快擱筆。反覆思考,感覺自己仍然太過淺薄。

近幾年隨着技術領域的整體進步,和自己的學習積累,感覺現在是個比較合適的時機,建立一個圍繞 Java 體系的工程技術棧,將它整理成文字,與同行分享。

一方面,Java8.0 之後,語言的進步使它在生產力上與舊版本已有很大提高。雖然 Java 與新時代的編程語言相比,仍顯得比較笨拙,但是做爲一個技術體系的基礎,已經足夠可用。另一方面,基於JVM 環境的編程語言的發展,使得 JVM 可以成爲一個通用的運行時環境,在其上利用若干互補的編程語言構建工程。

JVM 平臺上最著名的新貴莫過於 Scala,特別是 Spark 的興起爲 Scala 爭取了很多愛好者。但是就我親身體驗而言,Scala 也有一些問題:

首先是 Scala 本身是一門複雜語言,要用好這門語言,需要大量的領域知識。當然將其作爲一個“更好的 Java”來用,未嘗不是一件好事,這也是我向普通用戶推薦的用法。要發揮 Scala 的全部優勢,需要一個優秀的團隊,成員有對代碼風格和質量的追求和共識,願意爲駕馭工具,提高生產力付出精力。其次 Scala 的構建工具 sbt 實在不能說令人滿意另外就是,Scala 實在跟java“太像”了,它是一個自成一體的複雜體系,如果用 Scala,很難劃清一個邊界,找到其它語言的切入點。這倒不能說是 Scala 的缺點,更準確的說,其它技術需要 Scala 的多,Scala 需要其它技術的少。

我嘗試了 Java+Clojure 的體系,發現是一個非常好的組合。Java 是靜態編譯型語言,Clojure 是動態類型,雖然它本質上仍然是編譯型的,但是可以在 repl 中方便的交互,也可以以腳本形式運行。兩者可以在工程上建立非常清晰的功能邊界,各司其職。Clojure 的構建工具 leiningen 是 maven 的高級封裝,可以充分利用 Clojure 的語法和 maven 的資源。Clojure的語言風格和內置庫,都強調了與 Java 的互通,而表達式內在的數據抽象能力,以及大量依賴 Clojure 語法的功能支持,可以將 Java 項目變得更敏捷和乾淨。在 Java 項目中引入 Clojure ,可以有效提高生產力。

或許 Java 語言層面的笨拙,本身也促進了其工具庫和框架的蓬勃發展,現代 Java 生態中,負責組建項目架構的 Spring/Guice,提供並行/併發抽象的 Java Concurrent 和 Akka、Clojure.core.async 、提供數據庫訪問的 Hibernate 等,都是很好的作品。在具體的項目中,找到一個互補的工具集很有意義。

一個複雜工程,往往不是單一架構和技術棧能夠覆蓋的,技術組合能夠互補就很重要了。例如異步框架中 Vertx 曾經是我期待很高的一環,它爲多種語言提供了SDK,包括Java、Scala、NodeJs等。但是實踐中這個東西完全無法讓人滿意,在高性能壓力下,出現大量無法管理的錯誤。更重要的是它非常的排外,一旦在項目中使用 Vertx,就要整個在編程風格上遵循它的需要,大量的回調並沒有節省開發人員的思考時間,相反還要削足適履,不斷思考自己的代碼邏輯是否會阻塞框架。而它的異步安全,依賴全局的單一 Vertical 對象,在原生支持多核並行的JVM環境下,強制開發人員依賴GIL,是一個非常愚蠢的退步。

相反,我在嘗試 Akka 的過程中,體驗非常好,Akka 不會強制用戶在一個單一的 materializer 下運行邏輯,跨節點擴展非常容易,而進程內的 actor 運行負擔也非常小,接入 Akka 的過程很友善,並不會污染 actor 之外的代碼風格,在 Spring MVC 中使用 Akka 也不會有任何問題,我還嘗試在Google Cloud 的 App Engine 實例(war環境)中用 Akka 管理爬蟲邏輯,整個過程沒有任何問題。這也印證了前面所說的"其它Jvm技術需要Scala"的場景。感謝 Akka 提供了完整的 Java dsl,雖然它只能用在 JVM 項目中,但是對於服務端開發並不是很大問題。我編寫了很多在 Clojure/Java中使用 Akka 的代碼,組合使用了各種各樣的框架和庫,都沒有遇到風格上的衝突。後續會和大家分享一些 Java+Clojure+Leiningen+AKKA 的開發體驗。

源碼地址:https://link.zhihu.com/?target=https%3A//github.com/MarchLiu/market

Hello Leiningen

Maven 相信 絕大部分Java 程序員都不陌生,但是當前市面上關於 Maven 的書籍卻幾乎沒有,畢竟構建工具這件事太不直接了,也不像軟件工程領域的那些流行詞那麼吸引人。但是構建工具還是會影響項目的質量甚至企業的協作方式,可以說是實際生產中非常重要的一環,因此 Java程序員需要了解 Clojure ,也必須要從 Clojure 的構建工具開始。

主流的 Clojure 構建工具是 Leiningen 和 Boot,個人慣用第一個,前提是需要確認你已經安裝了JDK,接下來進入代碼時間。

接下來就默認大家開發環境中已經有了JDK 10+。Leiningen 的安裝很簡單。在官網 http://leiningen.org 可以找到 shell 腳本,保存後在自己的終端裏就可以執行下載安裝,特意提到這個東西是因爲,呃……它經常下載失敗。

有時候需要按照腳本里面給到的 URL(隨着版本升級會不一樣,所以建議用的時候去腳本里找)手工下載,然後放到 ~/.lein/self-installs 裏,然後就可以順利安裝了。可喜的是 Leiningen 並不依賴 Clojure 環境,Clojure 的 runtime 太小了(或者說 JVM 已經足夠完備)。得益於此,Leiningen 只需要一個不大的 standalone.jar,下面是平常慣用的一個 Leiningen 項目模板示例:

(defproject one-by-one "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.9.0"]
                 [org.clojure/core.specs.alpha "0.1.10" :exclusions [[org.clojure/clojure] [org.clojure/spec.alpha]]]
                 [org.clojure/spec.alpha "0.1.123" :exclusions [[org.clojure/clojure]]]
                 [com.typesafe.akka/akka-actor_2.12 "2.5.14"]
                 [org.scala-lang.modules/scala-java8-compat_2.12 "0.9.0"]
                 [io.aeron/aeron-driver "1.10.3"]
                 [com.typesafe.akka/akka-remote_2.12 "2.5.14"]
                 [com.fasterxml.jackson.core/jackson-core "2.9.6"]
                 [com.fasterxml.jackson.core/jackson-databind "2.9.6"]
                 [com.google.protobuf/protobuf-java "3.6.0"]
                 [com.taoensso/nippy "2.14.0"]
                 [cheshire "5.8.1"]
                 [com.github.romix.akka/akka-kryo-serialization_2.12 "0.5.2"]]
  :source-paths ["src/main/clojure"]
  :java-source-paths ["src/main/java"]
  :test-paths ["test/main/clojure" "test/main/java"]
  :aot :all
  :profiles {:server {:main liu.mars.Server
                      :jvm-opts ["-Dconfig.resource=/server.conf"]}
             :client {:main liu.mars.Client
              :jvm-opts ["-Dconfig.resource=/client.conf"]}}

  :resource-paths ["resources"])

完整展示 Leiningen 功能的演示腳本可以在官網上找到,這裏就不贅述了,與我自己的個人習慣有以下幾個方面的區別:

  • 將 Clojure 代碼放在 src/main/clojure 下,Java 代碼則是 src/main/java ,如果有其它語言,依次類推。
  • 測試代碼位於 test/main/clojure 和 test/main/java 。
  • main 同級的目錄會用來管理可能存在的不同 profile 的目錄,例如以前用 Clojure 寫 Spark 任務時我會需要 src/dev/clojure 和 src/release/clojure 這樣的目錄,以及 test/local/clojure 和 test/clusters/clojure 等。

所以一般來說我不會用默認的項目模板,如果需要搭建一個新項目,我的項目目錄大概是下圖的結構:

第一步,先定個小目標,Hello Leiningen !
類似於 maven 的 pom 文件,Leiningen 的代碼文件放在項目根目錄下,名爲 project.clj ,一個最簡單的 project.clj 大概是這麼一個樣子:

(defproject sample "0.1.0-SNAPSHOT"
  :description "A sample application for leingen"
  :dependencies [[org.clojure/clojure "1.9.0"]]
  :source-paths ["src/main/clojure"]
  :main liu.mars.app)

內容很容易理解,基本的項目信息,依賴庫、main 函數的入口類和代碼目錄。對應的 app.clj內容也很簡單:

(ns liu.mars.app)

(defn -main
  []
  (println "Hello, Leingen!"))

運行結果沒有什麼關子可賣:

需要說明的是依賴裏那個 Clojure 是用來運行這個 println 的,如果我們寫個 Java 版的 Hello World,連這個依賴都可以去掉,project.clj 如下:

(defproject sample "0.1.0-SNAPSHOT"
  :description "A sample application for leingen"
  :java-source-paths ["src/main/java"]
  :main liu.mars.App)

對應的App.java:

package liu.mars;

public class App{
    public static void main(String [] args){
	System.out.println("Hello, Leiningen");
    }
}

然而此時如果運行Lein run會得到一條錯誤:

原因很簡單,因爲我們要編譯:

得到的 jar 是可直接運行的:

運行 lein jar 可以得到項目 jar ,而 lein uberjar 可以同時構建 standalone 版本的 jar 。說實話,在發現 maven 和 gradle 裏需要 shadow 插件之前,我並沒意識到 Leiningen 內置的功能還挺可愛的……

一個比較有意思的事情是,如果我們在不包含 project.clj 的目錄下執行 lein repl ,它會用自己的 Clojure 庫啓動一個 repl 環境

但是如果我們在上一個純 java 版的 hello world 項目目錄裏執行 lain repl ,它會按照項目配置加載依賴,所以會給出一個錯誤提示:

當然在項目里加入一個僅供開發時測試運行 repl 同時並不會引入到發佈版中的 Clojure 並不難,在特定 profile 里加入依賴就好了,只要看一下官方示例代碼,相信熟悉 Maven的 Java 工程師都能搞定。這裏不跑題太遠。現在我們有了一個比 Maven 更友好,更容易書寫的構建工具,有了 repl 環境和基本的項目結構,就可以做更豐富的測試案例了。

作者介紹

本篇作者劉鑫,2000年畢業於蘭州大學數學系數理統計專業,至今從事軟件開發工作已近20年。早年參加過中直機關統發工資、交通部公路司年報、星空極速等項目,2012年開始參與了矮人工匠和雲遊道兩次創業。前兩年曾在火幣網的錢包組和撮合引擎組擔任技術專家。是 Python 中文社區的早期成員,維護了2.x時代的Python tutorial 中文版翻譯。

今年在QCon全球軟件開發大會廣州站,劉鑫老師會從當前 Java 語言在工程實踐中的痛點入手,現場解讀如何【用 Clojure 改善 Java 的項目開發體驗】,與到場的開發者現場交流,共探 Java 的未來趨勢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章