Scala不是更好的Java

17-03-05 banq
                   

Scala不是更好的Java,而是一个具有自己的生态系统,最佳实践和方法的非常独特的语言。

当人们谈论Scala的使用经验时,经常说可以将Scala看作是更好的Java。许多公司特别是在2008-2009年间采用Scala的公司,并不想放弃Java等熟悉的工具,而只是将Scala集成到基于Maven的现有工作流程中。然而现在已经不再这样了。在大多数情况下,当代Scala技术栈再也不使用Maven作为构建工具,不使用Spring作为DI容器,很少使用传统的设计模式。那么他们使用什么?

依赖注入

可以说最流行的Scala Web框架是Play,当它刚刚出来时,它没有提供任何基础设施以实现DI。2.4版本提供了将Google Guice一起作为一个折中的DI解决方案。不是在Scala世界的每个人都非常渴望重回Java注释的,幸运的是,这其实也没有必要。使用Play框架,总是可以用另一个运行时解决方案替换Guice,或者使用一个完全不同的路由并使用所谓的编译时(compile-time)依赖注入。

使用编译时(compile-time)DI,能让服务类接受较低级别的依赖作为构造函数的参数。在Scala中,构造函数参数可以以常规方法访问,所以大多数时候,这些服务可能是完全无状态的。这种方法也被几个Java(和.NET)专家提倡,但它绝不是主流。有趣的是,Scala开发人员比Java / C#用户有优势,因为他们可以通过将基于宏的库与延迟定义组合来实现简化:

// trait AppComponents
lazy val userDao = wire[UserDao]
lazy val sessionDao = wire[SessionDao]
lazy val userService = wire[UserService]
lazy val authService = wire[AuthService]
<p>

上面的例子使用了一个来自MacWire库的函数wire。在编译阶段,此函数将分析每个类并调用其构造函数,同时将已初始化的服务作为参数传递。此外,使这些值延迟确保编译器而不是开发人员在将来负责初始化顺序。

毫不奇怪,这种方法在近年来变得非常流行,并且使得Play小组考虑将其作为在即将到来的版本的框架中的默认做法。

数据库访问

开发的另一个典型方面是与数据库交互。当Java第一次出来,很高兴看到一个查询数据库语言能够开箱即用。然而,使用纯JDBC会编写很多代码样板,因此主流Java方法是使用ORM。

当使用ORM时,通常创建领域类并注释它们以描述类如何映射到数据库模式。作为回报,ORM为您提供简单的方法来持久化和检索域对象,同时自动执行对象关系映射。这种方法在理论上肯定看起来很高大上,但在实践中,它导致了许多问题。来自Java社区的一些人指出,手动编写纯SQL实际上要比依靠ORM生成必要的语句更好。最突出的例子是名为jOOQ的库,它允许开发人员使用Java方法编写类型安全的SQL代码:

create.selectFrom(BOOK)
      .where(BOOK.PUBLISHED_IN.eq(2011))
      .orderBy(BOOK.TITLE)
<p>

然而,这种方法在Java中并不常见,而这个特定的库质量不是很高。所以大多数人继续使用ORM。

有趣的是,ORM从未真正在Scala中大幅度使用。似乎没有人需要它。用于处理数据库的最流行的Scala库是ScalikeJDBC,Slick,Quill。所有这些都更类似于jOOQ而不是Hibernate。同样,Scala用户比他们的Java同事更有优势,因为大多数这些库严重依赖于Java中不存在的Scala特性(宏,隐式参数,字符串插值)。

考虑以下使用ScalikeJDBC的示例:

// class UserDao
def getUser(userCode: String): Try[Option[User]] = Try {
  NamedDB('auth).readOnly { implicit session =>
    sql"select * from users where user_code = $userCode".
      map(User.fromRS).headOption().apply()
  }
}
<p>

代码可能看起来好像它容易遭受SQL注入,但实际上它不是:SQL字符串在后台转换为类型安全PreparedStatement。该getUser方法还使用一些Scala特性 - 即Try和Option- 更好地预期的结果类型。我已经解释了如何使用表达式组合monadic结构使您的代码更清晰,但重要的是,Java中没有人这样做,因为语言不支持。

JSON序列化

在Java中序列化和解析JSON的最流行的库是Jackson。Jackson被包括为许多(可能是大多数)与JSON相关库中的低级依赖,几乎每个人都广泛使用。Jackson使用反射API映射字段,但默认行为可以通过注释进行调整:

public class Name {
  @JsonProperty("firstName")
  public String _first_name;
}
<p>

因为Java世界中的一切都围绕着JavaBeans的概念,默认情况下,Jackson假定你的类包含私有的可变字段和getter / setter等方法来访问/改变内部字段。有趣的是,Jackson还包含一个子项目jackson-module-scala。它绝对也有用户,但我的经验表明,大多数Scala开发人员喜欢使用别的东西。

Scala有几个开发JSON库,大多数不是基于反射。让我们来看看最流行的一个 - play-json。

在Scala中,通常不会让您的数据类可变。因此,你创建一个case class并使其不可变:

case class Tag(id: UUID, text: String)
<p>

如何将它序列化为JSON?这很简单!只需添加一个隐式的类型值Writes:

object Tag {

implicit val writes = Json.writes[Tag]

}

就是这样!你不需要实现特殊的标记接口或在任何地方注册你的类。这基本上是type classes在起作用,令人惊讶的是,你真的不需要知道背后这个原理,直接简单使用它们。另一个有趣的事情是,Json.writes辅助方法是基于宏的,而不是基于反射的。再次,Java开发人员不能使用这个库,因为Java不支持类型类和宏。

结论

Scala开发人员往往不需要使用Spring进行依赖注入、不需要使用Hibernate访问数据库以及不需要使用基于反射的库用于JSON序列化。这些目标实现方式是基于Java中不存在的具有特定Scala特性的解决方案。因此,Scala不是一个更好的Java,而是一个具有自己的生态系统,最佳实践和方法的非常独特的语言。如果你遇到一个仍然相信“Scala是更好的Java”神话的人,请发送他们这篇文章。

The myth of using Scala as a better Java - Applied