[Lift Web Framework] 超簡單的 REST API 範例

打個廣告先

Lift 是一個不怎麼紅但很有趣的 Scala 網頁應用程式框架,對我自己而言,我覺得它最令人驚豔的地方在於他真的可以實現「程式碼與外觀分離」這件事,。

在大部份的 MVC 框架中,雖然我們可以把大部份的程式邏輯放在 Controller 和 Model 裡面,但多數的時候控制外觀的 HTML 模版裡仍然會有像是為了要顯示資料而出現的迴圈程式碼。

然而在 Lift 裡面,模版就是純脆的 xHTML / HTML5 網頁,不能含有任何的程式碼。

在下個星期五(2013 / 04 / 19)舉辦的 OSDC.tw 上,下午兩點我會有一個 Talk 的題目是「我如何停止憂慮並愛上 Non-MVC Web Framework」,內容就是介紹 Lift 這個相當有趣的模版系統,來看為什麼 Lift 可以實現完全沒有程式碼但又可以注入動態內容的模版,歡迎有興趣的朋友來聽。

不過因為是很淺的介紹,所以如果已經有用過 Lift 的話,可能會覺得內容很虛就是了。XDD

RestHelper

RestHelper 是 Lift 提供的一個 Trait,可以讓我們使用很簡單的 DSL 來描述我們的 Rest API 以及相關的實作。

因為覺得目前介紹 Lift RestHelper 的範例都有點長,所以來寫一個超極短的 Blog 的 Json REST API,支援 GET(查詢) / POST(新增) / DELETE(刪除) 三個動作,

部落格 Model 物件

因為要支援 POST 和 DELETE,所以我們需要一個簡單的 Model 物件,來模擬文章的存放。

程式碼如下,如果熟悉 Scala 的話,應該相當容易理解:

case class Article(id: Int, title: String, content: String) {
  def toJSON = {
    import net.liftweb.json._
    import net.liftweb.json.JsonDSL._

    ("id" -> id) ~ ("title" -> title) ~ ("content" -> content)
  }
}

object Article {

  var store: List[Article] = Article(12, "qqq", "sss") :: Nil

  def addArticle(title: String, content: String): Article = {
    val nextID = store.map(_.id).max + 1
    val newArticle = new Article(nextID, title, content)
    store ::= newArticle
    newArticle
  }

  def getArticle(id: Int): Option[Article] = store.filter(_.id == id).headOption
  def deleteArticle(id: Int) { store = store.filterNot(_.id == id) }
}

Blog API REST Helper

接下來要實作 RestHelper 這個 Trait,其中最重要的部份是 serve 區塊,裡面使用了 Partial Function / Pattern Matching 的方法來判斷傳進來的 HTTP Request 是 GET / DELETE / POST 以及相關的 MIME-type,來判斷是否要接受這個 HTTP Reqeust 還有要呼叫哪個 sub-routine.

import net.liftweb.http.rest.RestHelper
import net.liftweb.http.OkResponse
import net.liftweb.util.Helpers.AsInt

object BlogAPI extends RestHelper {

  def getArticleJSON(postID: Int): Option[JValue] = {
    Article.getArticle(postID).map(_.toJSON)
  }

  def deleteArticle(postID: Int) = {
    Article.deleteArticle(postID)
    new OkResponse
  }

  def postArticle(jsonData: JValue): JValue = {
    Article.addArticle(
      title = (jsonData \ "title").extract[String],
      content = (jsonData \ "content").extract[String]
    ).toJSON
  }

  serve {
    case "api" :: "blog" :: AsInt(postID) :: Nil JsonGet req => getArticleJSON(postID)
    case "api" :: "blog" :: AsInt(postID) :: Nil JsonDelete req => deleteArticle(postID)
    case "api" :: "blog" :: Nil JsonPost ((jsonData, req)) => postArticle(jsonData)
  }
}

Boot.scala

最後,我們需要在 Boot.scala 裡面把 BlogAPI 放到 Lift 的 dispatch 表格中,讓進來的 HTTP Request 可以被 BlogAPI 處理:

class Boot
{
  def boot
  {
    LiftRules.dispatch.append(BlogAPI)
  }
}

回響