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 条评论
登录 后参与评论

相关文章

来自专栏编舟记

一步步编写SonarQube Plugin

插件确实不好写,因为插件是插入庞大的系统当中工作的,那也就意味着写插件需要具备一定的领域知识,包括系统架构、扩展点、业务共性及差异、API及其业务模型对应、安装...

7893
来自专栏吉浦迅科技

DAY71:阅读Device-side Launch from PTX

我们正带领大家开始阅读英文的《CUDA C Programming Guide》,今天是第71天,我们正在讲解CUDA 动态并行,希望在接下来的30天里,您可以...

1232
来自专栏架构师之旅

《Spring敲门砖之基础教程第一季》 第二章(1) Spring框架之IOC首例-HelloWorld

回顾 上一章我们主要学习了Spring的一些理论知识,对Spring框架有了一个总体的概括,大家应该在头脑里形成一个初步的印象,接下来我们就会针对Spring框...

20310
来自专栏JAVA高级架构

Netty原理浅析

Netty是JBoss出品的高效的Java NIO开发框架。本文将主要分析Netty实现方面的东西,由于精力有限,本人并没有对其源码做了极细致的研 究。如果下面...

1182
来自专栏技术博客

Ioc模式和MEF

  分离关注( Separation of Concerns : SOC)是Ioc模式和AOP产生最原始动力,通过功能分解可得到关注点,这些关注可以是 组件Co...

892
来自专栏coolblog.xyz技术专栏

Spring IOC 容器源码分析系列文章导读

Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本。经过十几年的迭代,现在的 Spring 框架已...

29410
来自专栏java达人

java 处理xml的三种技术

最初,XML 语言仅仅是意图用来作为 HTML 语言的替代品而出现的,但是随着该语言的不断发展和完善,人们越来越发现它所具有的优点:例如标记语言可扩...

2475
来自专栏枕边书

在Spring-Boot中实现通用Auth认证的几种方式

最近一直被无尽的业务需求淹没,没时间喘息,终于接到一个能让我突破代码舒适区的活儿,解决它的过程非常曲折,一度让我怀疑人生,不过收获也很大,代码方面不明显,但感觉...

950
来自专栏Kubernetes

深度解析Kubernetes Local Persistent Volume(二)

摘要:上一篇博客”深度解析Kubernetes Local Persistent Volume(一)“对local volume的基本原理和注意事项进行了分析,...

1.6K3
来自专栏平凡文摘

Netty 实现原理浅析

1593

扫码关注云+社区

领取腾讯云代金券