TodoBackend展示应用以及ActFramework的实现

1. 关于TodoBackend

TodoBackend是一个公认的服务器后端技术展示平台, 现在已有85个不同语言和框架的展示项目(Showcase)加入这个平台上, 供开发人员参考学习.

开发TodoBackend的展示应用需要满足以下需求:

  • 应用必须是RESTful的服务, 所有的服务端点必须通过平台指定的测试
  • 应用必须实现对CORS的支持

更多关于TodoBackend展示应用的信息可以参见贡献指南

2. 框架/技术比较

比较一下常见框架/技术实现TodoBackend展示应用的情况:

Language/Platform

Implementation

Data Persistent

Line of Code

Java/JVM

ActFramework

MongoDB

64

Java/JVM

Spring4 + Boot

Java Set

200

Java/JVM

vertx

MongoDB

241

Java/JVM

Dropwizard

Java Map

115

Java/JVM

Jooby

Java Map

231

Java/JVM

SparkFramework

PostgreSQL

348

Kotlin/JVM

Rapidoid

Java Map

81

Closure/JVM

Closure

PostgreSql

142

Scala/JVM

Scala/Play2.5

PostgreSql

136

Golang

Gin

Map in memory

128

Golang

stdlib

In memory data structure

238

JavaScript/NodeJs

express

PostgreSql

130

JavaScript/NodeJs

Koa

Redis

169

Python

webpy

Array in memory

32

Python

django

sqllite

164

Ruby

rails

PostgreSql

311

PHP

symfony2

sqlite

130 (only count files in src dir)

Haskell

Snap

Sqlite

98

C#/.Net

Asp.Net core

? (Entity Framework)

887

C#/.Net

ASP.NET WebAPI 2

In memory list

215

Swift

Kitura

MongoDB

473

3. ActFramework的实现

第一个ActFramework的实现基于MongoDB. 源代码同时发布在码云github.

2.1 代码分析

1. 域模型

在这个实现中我们使用了MongoDB作为数据存储. Act通过act-morphia插件提供了很好的MongoDB支持. 该插件依赖于官方的Morphia文档对象转换层

Act在Morphia之上提出了一个革新特性: AdaptiveRecord, 这个特性运行后端开发人员在域模型类中只声明参与后端计算逻辑的字段. 而只需呈现在前端不参与后端运算的字段可以不用申明. 下面就是我们基于AdaptiveRecordTodo类:

@Entity(value = "todo", noClassnameStored = true)
public class Todo extends MorphiaAdaptiveRecordWithLongId<Todo> {

    // needs to define this property to make it comply with todobackend spec
    // unless https://github.com/TodoBackend/todo-backend-js-spec/issues/6
    // is accepted
    public boolean completed;

    // url is required as per backend test spec. However it is not required
    // to put into the database. So we mark it as Transient property
    @Transient
    public String url;

    // We will generate the derived property `url` after
    // saving the model and loading the model
    @PostLoad
    @PostPersist
    private void updateUrl() {
        url = Act.app().router().fullUrl(S.concat("/todo/", getIdAsStr()));
    }
}

如上所示, 我们并在Todo类中没有声明前端应用用到的title, order甚至连completed都可以省却. 之所以定义了completed的原因在这个TodoBackend test spec的问题

注意类中声明的url属性并非需要存入数据库的数据, 这是一个派生字段, 由GET TODO Item的URL和当前Todo的id联合产生. 我们使用了Morphia的PostLoadPostPersist生命周期回调方法来填充url的值

2. 服务

在传统的Java Web应用中像这个实现中将服务(也称为控制器)嵌入域模型类的做法非常罕见:

@Entity(value = "todo", noClassnameStored = true)
public class Todo extends MorphiaAdaptiveRecordWithLongId<Todo> {

    // needs to define this property to make it comply with todobackend spec
    // unless https://github.com/TodoBackend/todo-backend-js-spec/issues/6
    // is accepted
    public boolean completed;

    ....

    @Controller("/todo")
    @Produces(H.MediaType.JSON)
    public static class Service extends MorphiaDaoWithLongId<Todo> {

        @PostAction
        public Todo create(Todo todo, Router router) {
            return save(todo);
        }

        @GetAction("{id}")
        public Todo show(long id) {
            return findById(id);
        }
        ...
    }
}

但我们认为在这个TODO应用中这样的安排是可以的, 因为该服务只针对Todo一个域模型. 另一方面我们其实鼓励使用这种方式来组织代码, 原因如下:

  1. 操作(服务)与数据(域模型)封装到一个模块是面向对象提倡的做法, 这样可以让应用的内聚性增强
  2. 同时也提高了代码可读性. 因为不需要在类文件(甚至在不同的包目录)之间来回切换, 就可以在阅读服务控制代码的时候查看被操作的数据细节.

顺便提一下, 代码中的@Produces(H.MediaType.JSON) 其实都可以省去. 前提是TodoBackend接受并修改了这个问题报告

3. CORS

TodoBackend要求展示用例必须支持CORS. 于是我们在其他的实现中就会找到各种各样跟CORS相关的代码, 比如:

来自Java 8 with Spring 4 Boot 实现

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    HttpServletResponse response = (HttpServletResponse) res;
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "x-requested-with, origin, content-type, accept");
    chain.doFilter(req, res);
}

和来自Java with Dropwizard implementation

private void addCorsHeader(Environment environment) {
    FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORS", CrossOriginFilter.class);
    filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
    filter.setInitParameter("allowedOrigins", "*");
    filter.setInitParameter("allowedMethods", "GET,PUT,POST,DELETE,OPTIONS,HEAD,PATCH");
}

在Act中我们不需要类似的代码. 在Act中只需在配置文件中加入一行 cors=true即可. 这是另一个Act很酷的地方, 框架已经集成了很多工具帮助处理和Web应用相关的需求, 比如CORS和CSRF等等

总结

ActFramework提供了一个强大而灵活的机制来帮助开发人员迅速而简洁地开发RESTful的服务应用. 使用ActFramework开发人员只需要专注与业务逻辑而不需要去架设各种通用工具.

参考链接

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java 成神之路

使用 NIO 实现 echo 服务器

4667
来自专栏C#

DotNet加密方式解析--非对称加密

    新年新气象,也希望新年可以挣大钱。不管今年年底会不会跟去年一样,满怀抱负却又壮志未酬。(不过没事,我已为各位卜上一卦,卦象显示各位都能挣钱...)...

4888
来自专栏张善友的专栏

LINQ via C# 系列文章

LINQ via C# Recently I am giving a series of talk on LINQ. the name “LINQ via C...

2645
来自专栏Golang语言社区

【Golang语言社区】GO1.9 map并发安全测试

var m sync.Map //全局 func maintest() { // 第一个 YongHuomap := make(map[st...

4728
来自专栏pangguoming

Spring Boot集成JasperReports生成PDF文档

由于工作需要,要实现后端根据模板动态填充数据生成PDF文档,通过技术选型,使用Ireport5.6来设计模板,结合JasperReports5.6工具库来调用渲...

1.2K7
来自专栏张善友的专栏

Silverlight + Model-View-ViewModel (MVVM)

     早在2005年,John Gossman写了一篇关于Model-View-ViewModel模式的博文,这种模式被他所在的微软的项目组用来创建Expr...

2968
来自专栏杨龙飞前端

scrollto 到指定位置

2514
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3145
来自专栏陈仁松博客

ASP.NET Core 'Microsoft.Win32.Registry' 错误修复

今天在发布Asp.net Core应用到Azure的时候出现错误InvalidOperationException: Cannot find compilati...

4868
来自专栏一个会写诗的程序员的博客

Spring Reactor 项目核心库Reactor Core

Non-Blocking Reactive Streams Foundation for the JVM both implementing a Reactiv...

2182

扫码关注云+社区