软件开发架构师

现代化Java之技术栈与自动化Clojure

架构 121 2019-04-30 02:14

历经二十多年的发展,Java 已经成为最成熟和发达的软件开发体系,有着丰富的技术资源和活跃的社区。但是时间也带给了 Java 的沧桑感,那么如何有效提高 Java 项目的开发效率,改善 Java 语言过于朴素所带来的笨拙感。

关于 Java 的未来发展趋势,刘鑫有着自己的思考,在新时代下,如何保证 Java 不落伍,如何让已经“上了岁数的”Java 在工程实践中焕发活力,刘鑫在此篇文章中详细介绍了现代化的 Java 应该是怎样的。以下为正文内容:

现代化Java之技术栈与自动化Clojure-1

语言、框架与技术栈

关于软件开发,特别是服务端软件开发的技术栈的探索,几乎贯穿了我整个的职业生涯。几年前我就想写这样一个专题,但是开了个头就又很快搁笔。反复思考,感觉自己仍然太过浅薄。

近几年随着技术领域的整体进步,和自己的学习积累,感觉现在是个比较合适的时机,建立一个围绕 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 等。

所以一般来说我不会用默认的项目模板,如果需要搭建一个新项目,我的项目目录大概是下图的结构:

现代化Java之技术栈与自动化Clojure-2

第一步,先定个小目标,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!"))

运行结果没有什么关子可卖:
现代化Java之技术栈与自动化Clojure-3
需要说明的是依赖里那个 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 会得到一条错误:
现代化Java之技术栈与自动化Clojure-4

原因很简单,因为我们要编译:
现代化Java之技术栈与自动化Clojure-5

得到的 jar 是可直接运行的:
现代化Java之技术栈与自动化Clojure-6

运行 lein jar 可以得到项目 jar ,而 lein uberjar 可以同时构建 standalone 版本的 jar 。说实话,在发现 maven 和 gradle 里需要 shadow 插件之前,我并没意识到 Leiningen 内置的功能还挺可爱的……

一个比较有意思的事情是,如果我们在不包含 project.clj 的目录下执行 lein repl ,它会用自己的 Clojure 库启动一个 repl 环境

现代化Java之技术栈与自动化Clojure-7

但是如果我们在上一个纯 java 版的 hello world 项目目录里执行 lain repl ,它会按照项目配置加载依赖,所以会给出一个错误提示:

现代化Java之技术栈与自动化Clojure-8

当然在项目里加入一个仅供开发时测试运行 repl 同时并不会引入到发布版中的 Clojure 并不难,在特定 profile 里加入依赖就好了,只要看一下官方示例代码,相信熟悉 Maven 的 Java 工程师都能搞定。这里不跑题太远。现在我们有了一个比 Maven 更友好,更容易书写的构建工具,有了 repl 环境和基本的项目结构,就可以做更丰富的测试案例了。

作者介绍

本篇作者刘鑫,2000 年毕业于兰州大学数学系数理统计专业,至今从事软件开发工作已近 20 年。早年参加过中直机关统发工资、交通部公路司年报、星空极速等项目,2012 年开始参与了矮人工匠和云游道两次创业。前两年曾在火币网的钱包组和撮合引擎组担任技术专家。是 Python 中文社区的早期成员,维护了 2.x 时代的 Python tutorial 中文版翻译。

今年在 QCon 全球软件开发大会广州站,刘鑫老师会从当前 Java 语言在工程实践中的痛点入手,现场解读如何【用 Clojure 改善 Java 的项目开发体验】,与到场的开发者现场交流,共探 Java 的未来趋势。

文章评论