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.
- 演示站点: http://todobackend.actframework.org/
- 标准测试情况: http://www.todobackend.com/specs/index.html?http://todobackend.actframework.org/todo
2.1 代码分析
1. 域模型
在这个实现中我们使用了MongoDB作为数据存储. Act通过act-morphia插件提供了很好的MongoDB支持. 该插件依赖于官方的Morphia文档对象转换层
Act在Morphia之上提出了一个革新特性: AdaptiveRecord
, 这个特性运行后端开发人员在域模型类中只声明参与后端计算逻辑的字段. 而只需呈现在前端不参与后端运算的字段可以不用申明. 下面就是我们基于AdaptiveRecord
的Todo
类:
@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的PostLoad
和PostPersist
生命周期回调方法来填充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一个域模型. 另一方面我们其实鼓励使用这种方式来组织代码, 原因如下:
- 操作(服务)与数据(域模型)封装到一个模块是面向对象提倡的做法, 这样可以让应用的内聚性增强
- 同时也提高了代码可读性. 因为不需要在类文件(甚至在不同的包目录)之间来回切换, 就可以在阅读服务控制代码的时候查看被操作的数据细节.
顺便提一下, 代码中的@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开发人员只需要专注与业务逻辑而不需要去架设各种通用工具.