首先要说明,Kotlin支持你所知道的所有Java框架和库,包括但不限于Spring全家桶、Guice、Hibernate、MyBatis、Jackson等,甚至有人在用Kotlin写Spark大数据程序,因此Kotlin不需要专门的框架。因此,为Kotlin开发框架的人,都是怀着满满的爱!
Kotlin现在主要流行于Android开发,我是搞后端开发的,不熟悉Android,就不妄言了。这篇文章主要介绍后端框架,包括Web、SQL、依赖注入、测试这些方面。
Web框架
Wasabi
- An HTTP Framework https://github.com/wasabifx/wasabi
极简的Web框架,基于Netty构建,编程风格效仿了Ruby的Sinatra和Node.js的Express。
Java也有个效仿Sinatra风格的Web框架,叫Spark(是的,与某大数据框架重名了)。
使用很简单:
var server = AppServer()
server.get("/", { response.send("Hello World!") })
server.start()
也可以这么写:
server.get(“/“) { response.send("Hello World!") }
加一个前置拦截器(next()
表示进入下一步处理):
server.get("/",
{
val log = Log()
log.info("URI requested is ${request.uri}")
next()
},
{
response.send("Hello World!", "application/json")
}
)
获取参数:
server.get("/customer/:id", { val customerId = request.routeParams["id"] } )
server.get("/customer", { val customerName = request.queryParams["name"] } )
为了提供可维护性,可以在别处定义一个方法,在程序入口引用它:
appServer.get("/customer", ::getCustomer)
这种微框架很适合快速为一个后端服务添加REST接口。
Kara
https://github.com/TinyMissio...
JetBrains官方支持的Web框架,特色是类型安全的HTML DSL和CSS DSL (风格类似Haml/Slim和SASS/LESS)
因为Kotlin是支持动态执行代码的,所以DSL理论上是可以热修改的,但是不知道Kara框架有没有内置这个特性。
DSL示例
HTML View:
class Index() : HtmlView() {
override fun render(context: ActionContext) {
h2("Welcome to Kara")
p("Your app is up and running, now it's time to make something!")
p("Start by editing this file here: src/com/karaexample/views/home/Index.kt")
}
}
HTML Layout:
class DefaultLayout() : HtmlLayout() {
override fun render(context: ActionContext, mainView: HtmlView) {
head {
title("Kara Demo Title")
stylesheet(DefaultStyles())
}
body {
h1("Kara Demo Site")
div(id="main") {
renderView(context, mainView)
}
a(text="Kara is developed by Tiny Mission", href="http://tinymission.com")
}
}
}
Forms:
class BookForm(val book : Book) : HtmlView() {
override fun render(context: ActionContext) {
h2("Book Form")
formFor(book, "/updatebook", FormMethod.Post) {
p {
labelFor("title")
textFieldFor("title")
}
p {
labelFor("isPublished", "Is Published?")
checkBoxFor("isPublished")
}
}
}
}
CSS:
class DefaultStyles() : Stylesheet() {
override fun render() {
s("body") {
backgroundColor = c("#f0f0f0")
}
s("#main") {
width = 85.percent
backgroundColor = c("#fff")
margin = box(0.px, auto)
padding = box(1.em)
border = "1px solid #ccc"
borderRadius = 5.px
}
s("input[type=text], textarea") {
padding = box(4.px)
width = 300.px
}
s("textarea") {
height = 80.px
}
s("table.fields") {
s("td") {
padding = box(6.px, 3.px)
}
s("td.label") {
textAlign = TextAlign.right
}
s("td.label.top") {
verticalAlign = VerticalAlign.top
}
}
}
}
其实就是在写Kotlin代码,显然你可以自行扩展出更多的DSL,还可以用面向对象或函数式的方式来复用。
Controllers 像Spring MVC和Django的风格
不需要用到反射,性能更高:
object Home {
val layout = DefaultLayout()
Get("/")
class Index() : Request({
karademo.views.home.Index()
})
Get("/test")
class Test() : Request({
TextResult("This is a test action, yo")
})
Post("/updatebook")
class Update() : Request({
redirect("/forms")
})
Get("/complex/*/list/:id")
Complex(id : Int) : Request({
TextResult("complex: ${params[0]} id = ${params["id"]}")
})
}
当然也有拦截器,在这里叫Middleware
实现这个接口并绑定到路由路径就可以了:
/**
* Base class for Kara middleware.
* Middleware is code that is injected inside the request pipeline,
* either before or after a request is handled by the application.
*/
abstract class Middleware() {
/**
* Gets called before the application is allowed to handle the request.
* Return false to stop the request pipeline from executing anything else.
*/
abstract fun beforeRequest(context : ActionContext) : Boolean
/**
* Gets called after the application is allowed to handle the request.
* Return false to stop the request pipeline from executing anything else.
*/
abstract fun afterRequest(context : ActionContext) : Boolean
}
appConfig.middleware.add(MyMiddleware(), "/books")
综合来说,Kara确实是比较优秀的web框架。
但有一点不好,默认使用3000端口,与Rails重复了。
SQL框架
Exposed
https://github.com/jetbrains/...
JetBrains官方支持的SQL/ORM框架,风格颇为类似Django ORM,并且充分发挥了Kotlin的强类型优势。
项目主页有很长一段示例代码,全程都是强类型,非常流畅,令人赏心悦目:
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.SchemaUtils.drop
object Users : Table() {
val id = varchar("id", 10).primaryKey() // Column
val name = varchar("name", length = 50) // Column
val cityId = (integer("city_id") references Cities.id).nullable() // Column
}
object Cities : Table() {
val id = integer("id").autoIncrement().primaryKey() // Column
val name = varchar("name", 50) // Column
}
fun main(args: Array) {
Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
transaction {
create (Cities, Users)
val saintPetersburgId = Cities.insert {
it[name] = "St. Petersburg"
} get Cities.id
val munichId = Cities.insert {
it[name] = "Munich"
} get Cities.id
Cities.insert {
it[name] = "Prague"
}
Users.insert {
it[id] = "andrey"
it[name] = "Andrey"
it[cityId] = saintPetersburgId
}
Users.insert {
it[id] = "sergey"
it[name] = "Sergey"
it[cityId] = munichId
}
Users.insert {
it[id] = "eugene"
it[name] = "Eugene"
it[cityId] = munichId
}
Users.insert {
it[id] = "alex"
it[name] = "Alex"
it[cityId] = null
}
Users.insert {
it[id] = "smth"
it[name] = "Something"
it[cityId] = null
}
Users.update({Users.id eq "alex"}) {
it[name] = "Alexey"
}
Users.deleteWhere{Users.name like "%thing"}
println("All cities:")
for (city in Cities.selectAll()) {
println("${city[Cities.id]}: ${city[Cities.name]}")
}
println("Manual join:")
(Users innerJoin Cities).slice(Users.name, Cities.name).
select {(Users.id.eq("andrey") or Users.name.eq("Sergey")) and
Users.id.eq("sergey") and Users.cityId.eq(Cities.id)}.forEach {
println("${it[Users.name]} lives in ${it[Cities.name]}")
}
println("Join with foreign key:")
(Users innerJoin Cities).slice(Users.name, Users.cityId, Cities.name).
select {Cities.name.eq("St. Petersburg") or Users.cityId.isNull()}.forEach {
if (it[Users.cityId] != null) {
println("${it[Users.name]} lives in ${it[Cities.name]}")
}
else {
println("${it[Users.name]} lives nowhere")
}
}
println("Functions and group by:")
((Cities innerJoin Users).slice(Cities.name, Users.id.count()).selectAll().groupBy(Cities.name)).forEach {
val cityName = it[Cities.name]
val userCount = it[Users.id.count()]
if (userCount > 0) {
println("$userCount user(s) live(s) in $cityName")
} else {
println("Nobody lives in $cityName")
}
}
drop (Users, Cities)
}
}
CRUD和各种查询都很容易表达,也能自动建表,确实方便得很。
但是文档没提到schema migration,想必是没这个功能。如果你想修改表结构,还得手动用SQL去改?这方面需要提高。
Requery
https://github.com/requery/re...
这是一个主要面向Android的Java ORM框架,为Kotlin提供了一些额外特性。
你需要把实体声明为abstract class或interface,然后标上类似JPA的注解:
@Entity
abstract class AbstractPerson {
@Key @Generated
int id;
@Index("name_index") // table specification
String name;
@OneToMany // relationships 1:1, 1:many, many to many
Set phoneNumbers;
@Converter(EmailToStringConverter.class) // custom type conversion
Email email;
@PostLoad // lifecycle callbacks
void afterLoad() {
updatePeopleList();
}
// getter, setters, equals & hashCode automatically generated into Person.java
}
@Entity
public interface Person {
@Key @Generated
int getId();
String getName();
@OneToMany
Set getPhoneNumbers();
String getEmail();
}
它提供了SQL DSL,看起来似乎依赖代码生成:
Result query = data
.select(Person.class)
.where(Person.NAME.lower().like("b%")).and(Person.AGE.gt(20))
.orderBy(Person.AGE.desc())
.limit(5)
.get();
用Kotlin可以写得更简洁:
data {
val result = select(Person::class) where (Person::age gt 21) and (Person::name eq "Bob") limit 10
}
Kwery, Kuery, Kotliquery
https://github.com/andrewoma/...
https://github.com/x2bool/kuery
https://github.com/seratch/ko...
这三个实际上是SQL库,对JDBC做了一些封装,提供了简易的SQL DSL。我不想用篇幅来介绍,有兴趣的朋友可以去项目主页看一看。
还要特别推荐Ebean ORM框架 https://ebean-orm.github.io/ 融合了JPA和Active Record的风格,成熟度相对高一些,已有一定规模的用户群,虽然不是专为Kotlin设计,但作者也在使用Kotlin。
依赖注入框架
Kodein
https://github.com/SalomonBry...
用法简单,支持scopes, modules, lazy等特性。
val kodein = Kodein {
bind() with provider { RandomDice(0, 5) }
bind() with singleton { SqliteDS.open("path/to/file") }
}
class Controller(private val kodein: Kodein) {
private val ds: DataSource = kodein.instance()
}
个人认为Kodein没有什么亮点。
因为传统的Spring和Guice都是可以用的,功能和稳定性更有保证,所以建议继续用传统的吧。
测试框架
Spek
https://github.com/JetBrains/...
JetBrains官方支持的规格测试框架,效仿了Ruby的RSpec,代码风格很相似,可读性很好:
class SimpleTest : Spek({
describe("a calculator") {
val calculator = SampleCalculator()
it("should return the result of adding the first number to the second number") {
val sum = calculator.sum(2, 4)
assertEquals(6, sum)
}
it("should return the result of subtracting the second number from the first number") {
val subtract = calculator.subtract(4, 2)
assertEquals(2, subtract)
}
}
})
除此之外,Java的JUnit, TestNG, Mockito等框架在Kotlin中都是可以使用的。
结语
相比于Scala,Kotlin在实用性方面下了大功夫,无缝兼容Java,融入Java生态,自身也有简洁的语法和强大的DSL能力。(想一想Scala 2.10 2.11 2.12的互不兼容,以及build一个项目会下载标准库的n个版本,例如2.11.0、2.11.1、2.11.2,也不知道实际运行的是哪个。)
这些新兴的Kotlin框架延续了Kotlin的简洁和强大,相信它们很快就会展现出光明的前途!