前言 最近需要開發一個純API的項目,mlsql-cluster,從無到有,到最後完整的proxy功能開發完畢,只花了四個小時不到,自己不盡小感嘆了一把 ServiceFramework的高效。 關於ServiceFramework的誕生 ServiceFramework算是一個古老的,基於Java ...
前言
最近需要開發一個純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寫的哈):
就是一個普通的Main方法。大家有沒有發現現在大部分Web框架已經都這麼幹了。
配置文件精簡
早年Java領域出現了一個潮流,就是能配置的堅決不寫代碼,配置可以更靈活,但是它們忘了配置本身也是一種代碼(語法受限的語言),反倒增加了成本,所以後面引入了Annotation以及約定俗成。SF設計之初,就只有兩個配置文件,一個application.yml,一個logging.yml文件。基本需要配置的很少。核心就是一個資料庫配置信息,然後一個http埠。
自動讀取資料庫配置ORM
個人感覺對資料庫的操作很難比SF更簡化了(吹牛)。在SF中ORM是無任何配置文件的,唯一的信息就是在application.yml里的鏈接信息:
接著你按傳統的方式在資料庫里建好表,比如
1 CREATE TABLE backend 2 ( 3 id int(11) NOT NULL AUTO_INCREMENT, 4 name varchar(255) DEFAULT NULL, 5 url text, 6 tag text, 7 ecs_resource_pool_id int(11), 8 PRIMARY KEY (id) 9 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
創建了一張backend表。然後我要在代碼中怎麼操作呢?一行代碼就是一個Model。
1 public class Backend extends Model { 2 }
什麼都沒有啊? 就這麼個類,我們看看怎麼操作資料庫,首先是新建一條記錄:
1 Map<String, Object> newParams = new HashMap<>(); 2 newParams.put(......) 3 Backend backend = Backend.create(newParams); 4 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,你只要這麼做:
1 class BackendController extends ApplicationController { 2 @At(path = Array("/backend/add"), types = Array(GET, POST)) 3 def backendAdd = { 4 List("url", "tag", "name").foreach(item => require(hasParam(item), s"${item} is required")) 5 Backend.newOne(params(), true) 6 BackendService.refreshCache 7 render(map("msg", "success")) 8 } 9 }
繼承ApplicationController,就是一個controller,@就定義了請求的endpoint。http參數怎麼獲取?使用param方法:
val name = param("name")
paramAsInt("times",0) // 獲取int類型參數,並且預設值設置為0
hasParam("name")//判斷有沒有
params() //拿到所有參數
比如前面的例子,我們鼓勵直接在controller里使用模型類操作資料庫,免去了service的麻煩,因為model已經具有足夠的表達能力,很多業務邏輯也可以放在model里。
這不比通過定義方法的參數強很多?定義方法的參數你會說便於測試,我們看SF怎麼做介面測試的:
1 @Test 2 public void search() throws Exception { 3 RestResponse response = get("/doc/blog/search", map( 4 "tagNames", "_10,_9" 5 )); 6 Assert.assertTrue(response.status() == 200); 7 Page page = (Page) response.originContent(); 8 Assert.assertTrue(page.getResult().size() > 0); 9 }
So Easy.
基於HTTP協議的偽RPC協議
越來越多的人喜歡HTTP協議而非PPC, PRC無論測試還是複雜度其實都大於HTTP,但是每次調用HTTP介面還是很麻煩的,SF提供了一個對HTTP自動包裝的介面(動態生成代理類的方式),你只要提供HTTP介面代碼,就可以直接使用。比如:
1 trait BackendService { 2 @At(path = Array("/run/script"), types = Array(GET, POST)) 3 def runScript(@Param("sql") sql: String): HttpTransportService.SResponse 4 5 @At(path = Array("/run/script"), types = Array(GET, POST)) 6 def runScript(params: Map[String, String]): HttpTransportService.SResponse 7 8 @At(path = Array("/run/sql"), types = Array(GET, POST)) 9 def runSQL(params: Map[String, String]): HttpTransportService.SResponse 10 11 @At(path = Array("/instance/resource"), types = Array(GET, POST)) 12 def instanceResource(params: Map[String, String]): HttpTransportService.SResponse 13 }
這個介面是沒有任何實現的,他對應的是後端一個服務的http介面。接著我們在SF里就可以這麼調用了:
val instance = ClientProxy.get[BackendService]()
instance.runScript(params().asScala.toMap)
是不是很easy,很PRC?
後話
使用SF,你只需要幾分鐘就能搭建一個可以運行,具備部分業務邏輯功能的API服務。去掉儘量多的層,儘量讓使用者可以用最簡單的辦法去完成對應的功能而不是去考慮一些設計的優雅性來完成一些功能特點。 大家可以查看mlsql-cluster 獲得更多使用範例,感受其魅力。
另外,我個人認為比較完美的一個組合是: Reactjs + ServiceFramework