基於RxJava的函式式Reactive Web框架:datamill

banq發表於2016-06-16
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);
}
<p class="indent">

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<Response> 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()));
}
...
}
<p class="indent">

你能看到我們使用@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())))
<p class="indent">


你能充分控制JSON到內部細節,也不需要使用Jackson進行JSON自定義,或者使用Spring Data REST試圖定製自己的響應,這些煩惱和複雜都沒有了。

下面再看看你一個倉儲查詢程式碼:

public class UserRepository extends Repository<User> {
...
public Observable<User> 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()));
    }
...
}
<p class="indent">

注意,這裡精確SQL查詢可自由組合,對於那些使用元註釋產生查詢語句的人來說,你會再次體會到清晰。
在一個簡單應用中,查詢中有很小比例需要超過JPA實現以外進行定製,幾乎所有應用都有這種情況,這時你開始鑽研框架內部程式碼時會變得非常消沉。

請注意這裡資料是如何從查詢結果中取出來放入實體的,最後注意程式碼還能保持怎樣的精確,這些都是使用lambda和RxJava可觀察操作的原因。




GitHub - rchodava/datamill: A Java framework for w

相關文章