Scala具体解释---------高速入门Scala
我无可救药地成为了Scala的超级粉丝。在我使用Scala开发项目以及编写框架后,它就仿佛凝聚成为一个巨大的黑洞,吸引力使我不得不飞向它,以至于開始背离Java。
固然Java 8为Java阵营增添了一丝亮色,却是望眼欲穿,千呼万唤始出来。
而Scala程序猿,却早就在享受lambda、高阶函数、trait、隐式转换等带来的福利了。
Java像是一头史前巨兽,它在OO的方向上差点儿走到了极致。硬将它拉入FP阵营。确乎有些强人所难了。而Scala则不,由于它的诞生就是OO与FP的混血儿——完美的基因融合。
“Object-Oriented Meets Functional”,这是Scala语言官方站点上飘扬的旗帜。这也是Scala的野心,当然,也是Martin Odersky的雄心。
Scala社区的发展
然而,一门语言并不能孤立地存在,必须提供依附的平台,以及环绕它建立的生态圈。不如此。语言则不足以壮大。Ruby非常优秀,但假设没有Ruby On Rails的推动,也非常难发展到今天这个地步。Scala相同如此。
反过来。当我们在使用一门语言时,也要选择符合这门语言的技术栈,在整个生态圈中找到适合详细场景的框架或工具。
当然,我们在使用Scala进行软件开发时,亦能够寻求庞大的Java社区支持;但是。假设选择调用Java开发的库。就会牺牲掉Scala给我们带来的福利。幸运的是,在现在,多数情况你已不必如此。伴随着Scala语言逐渐形成的Scala社区,已经開始慢慢形成相对完整的Scala技术栈。不管是企业开发、自己主动化測试或者大数据领域。这些框架或工具已经很完整地呈现了Scala开发的生态系统。
高速了解Scala技术栈
若要了解Scala技术栈,并高速学习这些框架。一个好的方法是下载typesafe推出的Activator。
它提供了相对富足的基于Scala以及Scala主流框架的开发模板,这当中实则还隐含了typesafe为Scala开发提供的最佳实践与指导。下图是Activator模板的截图:
那么,是否有渠道能够总体地获知Scala技术栈究竟包含哪些框架或工具,以及它们的特性与使用场景呢?感谢Lauris Dzilums以及其它在Github的Contributors。在Lauris Dzilums的Github上。他建立了名为awesome-scala的Repository。搜罗了当下基本的基于Scala开发的框架与工具,涉及到的领域包含:
- Database
- Web Frameworks
- i18n
- Authentication
- Testing
- JSON Manipulation
- Serialization
- Science and Data Analysis
- Big Data
- Functional Reactive Programming
- Modularization and Dependency Injection
- Distributed Systems
- Extensions
- Android
- HTTP
- Semantic Web
- Metrics and Monitoring
- Sbt plugins
是否有“乱花渐欲迷人眼”的感觉?不是太少,而是太多!
那就让我删繁就简。就我的经验介绍一些框架或工具,从持久化、分布式系统、HTTP、Web框架、大数据、測试这六方面入手,作一次蜻蜓点水般的俯瞰。
持久化
归根结底,对数据的持久化主要还是通过JDBC訪问数据库。可是。我们须要更好的API接口,能更好地与Scala契合,又或者更自然的ORM。
假设希望运行SQL语句来操作数据库。那么运用相对广泛的是框架ScalikeJDBC,它提供了很easy的API接口,甚至提供了SQL的DSL语法。
比如:
val alice: Option[Member] = withSQL { select.from(Member as m).where.eq(m.name, name) }.map(rs => Member(rs)).single.apply()
假设希望使用ORM框架,Squeryl应该是非常好的选择。我的同事杨云在项目中使用过该框架,体验不错。该框架眼下的版本号为0.9.5,已经比較成熟了。
Squeryl支持按惯例映射对象与关系表,相当于定义一个POSO(Plain Old Scala Object)。从而降低框架的侵入。
若映射违背了惯例。则能够利用框架定义的annotation如@Column定义映射。
框架提供了org.squeryl.Table[T]来完毕这样的映射关系。
由于能够运用Scala的高阶函数、偏函数等特性。使得Squeryl的语法很自然,比如依据条件对表进行更新:
update(songs)(s => where(s.title === "Watermelon Man") set(s.title := "The Watermelon Man", s.year := s.year.~ + 1) )
分布式系统
我放弃介绍诸如模块化管理以及依赖注入。是由于它们在Scala社区的价值不如Java社区大。比如,我们能够灵活地运用trait结合cake pattern就能够实现依赖注入的特性。因此。我直接跳过这些内容。来介绍影响更大的支持分布式系统的框架。
Finagle的血统高贵,来自过去的寒门,如今的高门大族Twitter。
Twitter是较早使用Scala作为服务端开发的互联网公司。因而积累了很多的Scala经验,并基于这些经验推出了一些颇有影响力的框架。因为Twitter对可伸缩性、性能、并发的高要求,这些框架也极为关注这些质量属性。Finagle就是当中之中的一个。它是一个扩展的RPC系统,以支持高并发server的搭建。我并没有真正在项目中使用过Finagle,大家能够到它的官方站点获得很多其它消息。
对于分布式的支持,绝对绕不开的框架还是AKKA。
它产生的影响力如此之大,甚至使得Scala语言从2.10開始,就放弃了自己的Actor模型,转而将AKKA Actor收编为2.10版本号的语言特性。很多框架在分布式处理方面也选择了使用AKKA,比如Spark、Spray。AKKA的Actor模型參考了Erlang语言。为每一个Actor提供了一个专有的Mailbox,并将消息处理的实现细节做了良好的封装,使得并发编程变得更加easy。
AKKA非常好地统一了本地Actor与远程Actor,提供了差点儿一致的API接口。AKKA也可以非常好地支持消息的容错,除了提供一套完整的Monitoring机制外。还提供了对Dead Letter的处理。
AKKA天生支持EDA(Event-Driven Architecture)。当我们针对领域建模时。能够考虑针对事件进行建模。在AKKA中,这些事件模型能够被定义为Scala的case class,并作为消息传递给Actor。
借用Vaughn Vernon在《实现领域驱动设计》中的样例,针对例如以下的事件流:
我们能够利用Akka简单地实现:
case class AllPhoneNumberListed(phoneNumbers: List[Int]) case class PhoneNumberMatched(phoneNumbers: List[Int]) case class AllPhoneNumberRead(fileName: String) class PhoneNumbersPublisher(actor: ActorRef) extends ActorRef { def receive = { case ReadPhoneNumbers => //list phone numbers actor ! AllPhoneNumberListed(List(1110, )) } } class PhoneNumberFinder(actor: ActorRef) extends ActorRef { def receive = { case AllPhoneNumberListed(numbers) => //match actor ! PhoneNumberMatched() } } val finder = system.actorOf(Prop(new PhoneNumberFinder(...))) val publisher = system.actorOf(Prop(new PhoneNumbersPublisher(finder))) publisher ! ReadPhoneNumbers("callinfo.txt")
若须要处理的电话号码数据量大。我们能够非常easy地将诸如PhoneNumbersPublisher、PhoneNumberFinder等Actors部署为Remote Actor。此时。只须要更改client获得Actor的方式就可以。
Twitter实现的Finagle是针对RPC通信,Akka则提供了内部的消息队列(MailBox),而由LinkedIn主持开发的Kafka则提供了支持高吞吐量的分布式消息队列中间件。这个顶着文学家帽子的消息队列,可以支持高效的Publisher-Subscriber模式进行消息处理,并以高速、稳定、可伸缩的特性非常快引起了开发人员的关注。并在一些框架中被列入候选的消息队列而提供支持。比如。Spark Streaming就支持Kafka作为流数据的Input Source。
HTTP
严格意义上讲,Spray并不是单纯的HTTP框架。它还支持REST、JSON、Caching、Routing、IO等功能。Spray的模块及其之间的关系例如以下图所看到的:
我在项目中主要将Spray作为REST框架来使用。并结合AKKA来处理领域逻辑。Spray处理HTTP请求的架构例如以下图所看到的:
Spray提供了一套DSL风格的path语法。可以很easy地编写支持各种HTTP动词的请求,比如:
trait HttpServiceBase extends Directives with Json4sSupport { implicit val system: ActorSystem implicit def json4sFormats: Formats = DefaultFormats def route: Route } trait CustomerService extends HttpServiceBase { val route = path("customer" / "groups") { get { parameters('groupids.?) { (groupids) => complete { groupids match { case Some(groupIds) => ViewUserGroup.queryUserGroup(groupIds.split(",").toList) case None => ViewUserGroup.queryUserGroup() } } } } } ~ path("customers" / "vip" / "failureinfo") { post { entity(as[FailureVipCustomerRequest]) { request => complete { VipCustomer.failureInfo(request) } } } } }
我个人觉得,在进行Web开发时,全然可以放弃Web框架。直接选择AngularJS结合Spray和AKKA,相同可以非常好地满足Web开发须要。
Spray支持REST。且Spray自身提供了服务容器spray-can,因而同意Standalone的部署(当然也支持部署到Jetty和tomcat等应用server)。Spray对HTTP请求的内部处理机制实则是基于Akka-IO,通过IO这个Actor发出对HTTP的bind消息。比如:
IO(Http) ! Http.Bind(service, interface = "0.0.0.0", port = 8889)
我们可以编写不同的Boot对象去绑定不同的主机Host以及port。
这些特性都使得Spray可以非常好地支持当下较为流行的Micro Service架构风格。
Web框架
正如前面所说,当我们选择Spray作为REST框架时,全然可以选择诸如AngularJS或者Backbone之类的JavaScript框架开发Webclient。
client可以处理自己的逻辑。然后再以JSON格式发送请求给REST服务端。这时。我们将模型视为资源(Resource),视图全然在client。JS的控制器负责控制client的界面逻辑,服务端的控制器则负责处理业务逻辑。于是传统的MVC就变化为VC+R+C模式。
这里的R指的是Resource,而服务端与client则通过JSON格式的Resource进行通信。
若硬要使用专有的Web框架,在Scala技术栈下,最为流行的就是Play Framework。这是一个标准的MVC框架。
另外一个相对小众的Web框架是Lift。它与大多数Web框架如RoR、Struts、Django以及Spring MVC、Play不同,採用的并不是MVC模式,而是使用了所谓的View First。
它驱动开发人员对内容生成与内容展现(Markup)形成“关注点分离”。
Lift将关注点重点放在View上。这是由于在一些Web应用中,可能存在多个页面对同一种Model的Action。倘若採用MVC中的Controller,会使得控制变得很复杂。
Lift提出了一种所谓view-snippet-model(简称为VSM)的模式。
View主要为响应页面请求的HTML内容,分为template views和generated views。
Snippet的职责则用于生成动态内容,并在模型发生更改时。对Model和View进行协调。
大数据
大数据框架最耀眼的新星非Spark莫属。与很多专有的大数据处理平台不同。Spark建立在统一抽象的RDD之上。使得它能够以基本一致的方式应对不同的大数据处理场景,包含MapReduce,Streaming,SQL。Machine Learning以及Graph等。
这即Matei Zaharia所谓的“设计一个通用的编程抽象(Unified Programming Abstraction)。
因为Spark具有先进的DAG执行引擎,支持cyclic data flow和内存计算。因此相比較Hadoop而言,性能更优。
在内存中它的执行速度是Hadoop MapReduce的100倍。在磁盘中是10倍。
因为使用了Scala语言,通过高效利用Scala的语言特性。使得Spark的总代码量出奇地少。性能却在多数方面都具备一定的优势(仅仅有在Streaming方面。逊色于Storm)。下图是针对Spark 0.9版本号的BenchMark:
因为使用了Scala,使得语言的函数式特性得到了最优秀的利用。其实。函数式语言的诸多特性包含不变性、无副作用、组合子等。天生与数据处理匹配。于是,针对WordCount。我们能够如此简易地实现:
file = spark.textFile("hdfs://...") file.flatMap(line => line.split(" ")) .map(word => (word, 1)) .reduceByKey(_ + _)
要是使用Hadoop,就没有这么方便了。幸运的是,Twitter的一个开源框架scalding提供了对Hadoop MapReduce的抽象与包装。它使得我们能够依照Scala的方式运行MapReduce的Job:
class WordCountJob(args : Args) extends Job(args) { TextLine( args("input") ) .flatMap('line -> 'word) { line : String => tokenize(line) } .groupBy('word) { _.size } .write( Tsv( args("output") ) ) // Split a piece of text into individual words. def tokenize(text : String) : Array[String] = { // Lowercase each word and remove punctuation. text.toLowerCase.replaceAll("[^a-zA-Z0-9\\s]", "").split("\\s+") } }
測试
尽管我们能够使用诸如JUnit、TestNG为Scala项目开发编写单元測试。使用Cocumber之类的BDD框架编写验收測试。但在多数情况下,我们更倾向于选择使用ScalaTest或者Specs2。
在一些Java开发项目中,我们也開始尝试使用ScalaTest来编写验收測试,乃至于单元測试。
若要我选择ScalaTest或Specs2。我更倾向于ScalaTest。这是由于ScalaTest支持的风格更具备多样性,能够满足各种不同的需求。比如传统的JUnit风格、函数式风格以及Spec方式。我的一篇博客《ScalaTest的測试风格》具体介绍了各自的语法。
一个被广泛使用的測试工具是Gatling。它是基于Scala、AKKA以及Netty开发的性能測试与压力測试工具。我的同事刘冉在InfoQ发表的文章《新一代服务器性能測试工具Gatling》对Gatling进行了具体深入的介绍。
ScalaMeter也是一款非常不错的性能測试工具。我们可以像编写ScalaTest測试那样的风格来编写ScalaMeter性能測试用例,并可以快捷地生成性能測试数据。这些功能都非常有助于我们针对代码或软件产品进行BenchMark測试。我们以前用ScalaMeter来编写针对Scala集合的性能測试,比如比較Vector、ArrayBuffer、ListBuffer以及List等集合的相关操作。以便于我们更好地使用Scala集合。
下面代码展示了怎样使用ScalaMeter编写性能測试:
import org.scalameter.api._ object RangeBenchmark extends PerformanceTest.Microbenchmark { val ranges = for { size <- Gen.range("size")(300000, 1500000, 300000) } yield 0 until size measure method "map" in { using(ranges) curve("Range") in { _.map(_ + 1) } } }
依据场景选择框架或工具
比起Java庞大的社区,以及它提供的浩如烟海般的技术栈。Scala技术栈几乎相同能够说是沧海一粟。
然而,麻雀虽小却五脏俱全,何况Scala以及Scala技术栈仍然走在迈向成熟的道路上。
对于Scala程序猿而言,由于项目的不同,未必能涉猎全部技术栈,并且针对不同的方面。也有多个选择。
在选择这些框架或工具时,应依据实际的场景做出推断。为稳妥起见,最好能运用技术矩阵地方式对多个方案进行设计权衡与决策。
我们也不能固步自封。视Java社区而不顾。毕竟那些Java框架已经经历了千锤百炼,并有很多成功的案例作为佐证。
关注Scala技术栈,却又不局限自己的视野,量力而为。选择合适的技术方案。才是设计与开发的正道。
作者简单介绍
张逸。现为ThoughtWorks
Lead Consultant。作为一名咨询师。主要为客户提供组织的敏捷转型、过程改进、企业系统架构、领域驱动设计、大数据、代码质量提升、測试驱动开发等咨询与培训工作。
本文借鉴于http://www.infoq.com/cn/articles/scala-technology/