ServiceFramework作為Java Web框架都有哪些不錯的設計
前言
最近需要開發一個純API的專案, ofollow,noindex">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 ( idint(11) NOT NULL AUTO_INCREMENT, namevarchar(255) DEFAULT NULL, urltext, tagtext, 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 :grin: . React是一個我很讓我有願意去寫程式碼的的前端框架。