2013年3月25日

摘譯:Grails 2.3 開始提供非同步(Async)支援

本文摘譯自:Road to Grails 2.3: Async Support

Servlet 3.0 開始提供非同步 API(Async, or Asynchronous APIs)支援,雖然在 Grails 2.2 就已經可以存取 Servlet 3.0 的非同步 API 功能,但這並不符合 Grails 開發者的期待,因為:
  • Servlet 3.0 APIs 是針對 Servlet 提供的規範
  • 直接使用 Servlet API 太過於低階且複雜
  • 簡單說就是用 Servlet 3.0 API 並不像 Groovy 的風格

從 Grails 2.3 開始提供 Async 的支援,一個 waitAll() 的簡單範例如下:
import static grails.async.Promises.*
def p1 = task { 2 * 2 }
def p2 = task { 4 * 4 }
def p3 = task { 8 * 8 }
assert [4,16,64] == waitAll(p1, p2, p3)

這三個任務(task)會以非同步方式執行,因此 p2、p3 不會等待 p1 處理完,而是在背景同時間進行處理。如此一來要處理非同步變得更容易,不必再使用複雜的 API 來實作。
從 Grails 的觀點來看,這個 Async API 要如何適用於 Controller?如何和 GORM 一起運作?關於交易(Transactions)又要如何處理呢?
很讚的是這些問題都有良好的處理方式,例如 GORM 的 get() 取得一筆 Domain Object(row)時,可以指定 async 來存取。
import static grails.async.Promises.*
def p1 = Person.async.get(1L)
def p2 = Person.async.get(2L)
def p3 = Person.async.get(3L)
def results = waitAll(p1, p2, p3)

Grails 會自動在背景的執行續開啟 Hibernate session,並且在處理後就將它關閉。你甚至可以透過 async.task 來批次處理多個查詢:
 
def promise = Person.async.task {
    withTransaction {
       def person = findByFirstName("Homer")
       person.firstName = "Bart"
       person.save(flush:true)    
    }
}
Person updatedPerson = promise.get()

對 Controller 來說,使用 Servlet 3.0 Async 機制的好處,就是可以透過現代化的容器(例如 Tomcat 7.0),來打破舊有的請求(request)/回覆(response)與執行續的限制。對於實作非同步的 Controller 來說,可以非同步處理一個請求(request)的回傳結果,讓回覆(response)透過非阻斷式(non-blocking)的方式。這對於現代化的容器來說更有設計的彈性,可以用於處理長時間的請求(long running requests)。
下面的範例使用 Yahoo 財務 API 來示範,Grails 2.3 利用 task 方法,在任何動作方法(action),都能回傳 Promise 的資料。

def stock(String ticker) {
   task {
       ticker = ticker ?: 'GOOG'
       def url = new URL("http://download.finance.yahoo.com/d/quotes.csv?s=${ticker}&f=nsl1op&e=.csv")
       Double price = url.text.split(',')[-1] as Double
       render "ticker: $ticker, price: $price"
   }
}

你也可以傳回一個非同步的資料模型(Async model),這些取得資料的處理將會以非同步方式執行,並保證將結果正確放在一個 Map 結構中,然後交給 View 生成頁面資料。
 
import static grails.async.Promises.*

def index() {
   tasks books: Book.async.list(),
         totalBooks: Book.async.count(),
         otherValue: {
           // do hard work
         }
}

對於 Grails 的服務(Service)程式,同時實作同步與非同步兩種 API 存在很平常,但不幸的是這樣對維護來說很無趣而且常造成錯誤。為了解決這樣的問題,Grails 2.3 帶來 DelegateAsync 的轉換方法讓一般的 Grails 服務也能產生相應的非同步版本。

例如原本的 Service :

class BookService {    
    List findBooks(String title) {
      // implementation
    }
}

只要加上 @DelegateAsync 標記。

import grails.async.*
class AsyncBookService {
   @DelegateAsync BookService bookService    
}

加上標記後,服務中每個方法都會以非同步方式執行並回傳 Promise 物件。

AsyncBookService asyncBookService
def findBooks(String title) {
    asyncBookService.findBooks(title)
       .onComplete { List results ->
          println "Books = ${results}"       
       }
}

如果服務使用交易模式,Promise 的執行將會在一個交易階段中進行。如果你使用 @Transactional 標記,被標記的屬性(attributes)將會在交易時被建立。

這是 Grails 2.3 版本才開始提供的功能,目前 Grails 最新的穩定發行版本為 2.2.1,敬請期待更多新版本帶來的特性。

*本文為部分摘要翻譯,並不保證內容正確性,譯者為 lyhcode 現職為從事自由軟體開發的背包客與部落客。

沒有留言:

張貼留言

lyhcode by lyhcode
歡迎轉載,請務必註明出處!