datamill是一个基于RxJava开发函数响应式风格的Java Web框架,可看成是SpringBoot竞争的框架,使用Java8和lambda,它不同于其他Java框架,使得通过整个应用的数据流和行为变为高度可见的,这样你不需要使用魔术效果元注释,使得很多效果隐藏在复杂的框架和文档后面,相反,你只要显式明确指定数据是如何在你的应用中流动,如何修改这些数据即可。只需要使用简单的RxJava风格即可。
当你使用Spring框架时,虽然只需要很少代码就可以跑起来,但是当项目复杂以后,你会发现很多功能和行为被使用元注解隐藏在了框架和文档后面,变得难以捉摸和不确定性。但是你如果不想使用元注释,你会遇到阻碍,你需要调试数百行框架自身的代码以找出背后的原理机制,以及如何搞定框架以让它做你想做的事情。
看看datamill的简单代码:
public static void main(String[] args) {
OutlineBuilder outlineBuilder = new OutlineBuilder();
Server server = new Server(
rb -> rb.ifMethodAndUriMatch(Method.GET, "/status", r -> r.respond(b -> b.ok()))
.elseIfMatchesBeanMethod(outlineBuilder.wrap(new TokenController()))
.elseIfMatchesBeanMethod(outlineBuilder.wrap(new UserController()))
.orElse(r -> r.respond(b -> b.notFound())),
(request, throwable) -> handleException(throwable));
server.listen(8081);
}
datamill应用总是以标准的Java应用开始,可以明确地创建HTTP服务器,指定请求如何被处理,以及服务器监听端口,不像传统的JEE部署,你得配置servlet容器或应用服务器的配置,你只要启动这段服务器代码你就控制了一切,这也使得为这个服务器创建Docker容器变得很简单,只要使用Maven打包一个可执行的JAR包即可,放入标准的Java Dcoker容器即可。
当Http到达你的服务器,它是如何流过你的应用变得显著可见:
rb.ifMethodAndUriMatch(Method.GET, "/status", r -> r.respond(b -> b.ok()))
这一行代码说服务器首先检查请求应该是一个从URI为/status的GET请求,如果是,返回HTTP是ok的响应。
下面两行显示你能组织你的请求处理器,同时能够保持对请求之后如何处理也就是发生了什么情况保持可读性和可维护性。
.elseIfMatchesBeanMethod(outlineBuilder.wrap(new UserController()))
这段代码是说看看请求是否匹配UserController实例的一个处理方法,为了理解这个匹配工作原理,我们看看UserController的类,下面是处理请求的方法代码:
@Path("/users")
public class UserController {
...
@GET
@Path("/{userName}")
public Observable getUser(ServerRequest request) {
return userRepository.getByUserName(request.uriParameter("userName").asString())
.map(u -> new JsonObject()
.put(userOutlineCamelCased.member(m -> m.getId()), u.getId())
.put(userOutlineCamelCased.member(m -> m.getEmail()), u.getEmail())
.put(userOutlineCamelCased.member(m -> m.getUserName()), u.getUserName()))
.flatMap(json -> request.respond(b -> b.ok(json.asString())))
.switchIfEmpty(request.respond(b -> b.notFound()));
}
...
}
你能看到我们使用@Path和@Get元注释标记为请求处理器方法。这里你就不需要像Spring框架需要挖掘框架数百行代码以搞清楚框架是如何路由你的请求到你的代码。
最后,请注意在UserController中响应是如何被创建的,是如何显式进行JSON组合的:
.map(u -> new JsonObject()
.put(userOutlineCamelCased.member(m -> m.getId()), u.getId())
.put(userOutlineCamelCased.member(m -> m.getEmail()), u.getEmail())
.put(userOutlineCamelCased.member(m -> m.getUserName()), u.getUserName()))
.flatMap(json -> request.respond(b -> b.ok(json.asString())))
你能充分控制JSON到内部细节,也不需要使用Jackson进行JSON自定义,或者使用Spring Data REST试图定制自己的响应,这些烦恼和复杂都没有了。
下面再看看你一个仓储查询代码:
public class UserRepository extends Repository {
...
public Observable getByUserName(String userName) {
return executeQuery(
(client, outline) ->
client.selectAllIn(outline)
.from(outline)
.where().eq(outline.member(m -> m.getUserName()), userName)
.execute()
.map(r -> outline.wrap(new User())
.set(m -> m.getId(), r.column(outline.member(m -> m.getId())))
.set(m -> m.getUserName(), r.column(outline.member(m -> m.getUserName())))
.set(m -> m.getEmail(), r.column(outline.member(m -> m.getEmail())))
.set(m -> m.getPassword(), r.column(outline.member(m -> m.getPassword())))
.unwrap()));
}
...
}
注意,这里精确SQL查询可自由组合,对于那些使用元注释产生查询语句的人来说,你会再次体会到清晰。
在一个简单应用中,查询中有很小比例需要超过JPA实现以外进行定制,几乎所有应用都有这种情况,这时你开始钻研框架内部代码时会变得非常消沉。
请注意这里数据是如何从查询结果中取出来放入实体的,最后注意代码还能保持怎样的精确,这些都是使用lambda和RxJava可观察操作的原因。