ServiceFramework作为Java Web框架都有哪些不错的设计

前言

最近需要开发一个纯API的项目,mlsql-cluster,从无到有,到最后完整的proxy功能开发完毕,只花了四个小时不到,自己不尽小感叹了一把 ServiceFramework的高效。

关于ServiceFramework的诞生

ServiceFramework算是一个古老的,基于Java的web框架了。我印象中应该是我11年的作品,那个时候应该是RubyOnRails正火的时候。我做了一段时间Rails程序员,后面转型做搜索,期间觉得没啥好用的Web框架,于是就开发了ServiceFramework。

极致简约的要求

早年Java语言的笨拙一直是广受诟病的,业务还没两行,代码和配置就已经几百上千行了。首先我们不可能改变这门语言,那么如何做到极致简约呢?自动生成源码的套路肯定不行,用户就天天通过各种命令生成源码去了,而且通常生成的源码又丑又难看,还不敢改,所以我们需要无声无息的为用户生成必要的代码, 并且还不能让用户看见,还需要兼顾IDE的代码提示。那么,应该怎么玩呢? 核心在于两个点,我们后续会展开讲:

运行时代码生成(codegen,功能增强)+ 父类方法签名(代码提示)

极致简约体现在哪

应用包含容器

早年几乎清一色的,代码都是跑在容器里的(weblogic,tomcat等)。在11年的时候,SF做出了一个重要的设计,就是http只是代码对外暴露的一个交互方式,和RPC一样,Web容器只是你运行代码里的一个组件而已。所以SF的启动是这样的(演示代码都是用Scala写的哈):

image.png

就是一个普通的Main方法。大家有没有发现现在大部分Web框架已经都这么干了。

配置文件精简

早年Java领域出现了一个潮流,就是能配置的坚决不写代码,配置可以更灵活,但是它们忘了配置本身也是一种代码(语法受限的语言),反倒增加了成本,所以后面引入了Annotation以及约定俗成。SF设计之初,就只有两个配置文件,一个application.yml,一个logging.yml文件。基本需要配置的很少。核心就是一个数据库配置信息,然后一个http端口。

自动读取数据库配置ORM

个人感觉对数据库的操作很难比SF更简化了(吹牛)。在SF中ORM是无任何配置文件的,唯一的信息就是在application.yml里的链接信息:

image.png

接着你按传统的方式在数据库里建好表,比如

CREATE TABLE backend
(
  id                   int(11) NOT NULL AUTO_INCREMENT,
  name                 varchar(255) DEFAULT NULL,
  url                  text,
  tag                  text,
  ecs_resource_pool_id int(11),
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建了一张backend表。然后我要在代码中怎么操作呢?一行代码就是一个Model。

public class Backend extends Model {
}

什么都没有啊? 就这么个类,我们看看怎么操作数据库,首先是新建一条记录:

Map<String, Object> newParams = new HashMap<>();
newParams.put(......)
Backend backend = Backend.create(newParams);
backend.save();

找到并且删除一条记录:

Backend backend = Backend.where(map("name","jack")).fetchOne()
backend.delete()

那没有申明属性,怎么访问属性呢?

String name = backend.attr("name",String.class)

当然,因为我经常会用name属性,我们就申明一下,方便代码提示,不申明可以通过attr去定向拿:

public class Backend extends Model {
        private String name;
        public String getName() {
              return name;
         }
}

记住,这里的代码纯粹是为了做代码提示,不是必须的。

你可能会问,ORM里的关联咋办?

public class Backend extends Model {
       @OneToMany
       private List< Info>  infos;
       public Association infos(){
           throw new AutoGeneration();
        }
}
   

这就是所有,接着你就可以通过类似backend.infos.where(....).fetch()来操作。你不用写任何逻辑代码,ORM会根据你的数据库读取到的元数据自动帮你做关联,自动填充属性,自动提供查询语法(代码提示通过Model类已经写好的方法完成)

Web Contorller,一切只为便捷。

写一个controller 以及一个action,你只要这么做:

class BackendController extends ApplicationController {
   @At(path = Array("/backend/add"), types = Array(GET, POST))
  def backendAdd = {
    List("url", "tag", "name").foreach(item => require(hasParam(item), s"${item} is required"))
    Backend.newOne(params(), true)
    BackendService.refreshCache
    render(map("msg", "success"))
  }
}

继承ApplicationController,就是一个controller,@就定义了请求的endpoint。http参数怎么获取?使用param方法:

val name = param("name") 
paramAsInt("times",0) // 获取int类型参数,并且默认值设置为0
hasParam("name")//判断有没有
params() //拿到所有参数

比如前面的例子,我们鼓励直接在controller里使用模型类操作数据库,免去了service的麻烦,因为model已经具有足够的表达能力,很多业务逻辑也可以放在model里。

这不比通过定义方法的参数强很多?定义方法的参数你会说便于测试,我们看SF怎么做接口测试的:

@Test
  public void search() throws Exception {
      RestResponse response = get("/doc/blog/search", map(
              "tagNames", "_10,_9"
      ));
      Assert.assertTrue(response.status() == 200);
      Page page = (Page) response.originContent();
      Assert.assertTrue(page.getResult().size() > 0);
  }

So Easy.

基于HTTP协议的伪RPC协议

越来越多的人喜欢HTTP协议而非PPC, PRC无论测试还是复杂度其实都大于HTTP,但是每次调用HTTP接口还是很麻烦的,SF提供了一个对HTTP自动包装的接口(动态生成代理类的方式),你只要提供HTTP接口代码,就可以直接使用。比如:

trait BackendService {
  @At(path = Array("/run/script"), types = Array(GET, POST))
  def runScript(@Param("sql") sql: String): HttpTransportService.SResponse

  @At(path = Array("/run/script"), types = Array(GET, POST))
  def runScript(params: Map[String, String]): HttpTransportService.SResponse

  @At(path = Array("/run/sql"), types = Array(GET, POST))
  def runSQL(params: Map[String, String]): HttpTransportService.SResponse

  @At(path = Array("/instance/resource"), types = Array(GET, POST))
  def instanceResource(params: Map[String, String]): HttpTransportService.SResponse
}

这个接口是没有任何实现的,他对应的是后端一个服务的http接口。接着我们在SF里就可以这么调用了:

val instance = ClientProxy.get[BackendService]()
instance.runScript(params().asScala.toMap)

是不是很easy,很PRC?

后话

使用SF,你只需要几分钟就能搭建一个可以运行,具备部分业务逻辑功能的API服务。去掉尽量多的层,尽量让使用者可以用最简单的办法去完成对应的功能而不是去考虑一些设计的优雅性来完成一些功能特点。 大家可以查看mlsql-cluster 获得更多使用范例,感受其魅力。

另外,我个人认为比较完美的一个组合是: Reactjs + ServiceFramework 😁 . React是一个我很让我有愿意去写代码的的前端框架。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355