非同步架構思維:使用Akka實現領域建模

banq發表於2010-03-22
去年我們在Jdon.com狠狠地討論了非同步架構,這個非同步不是一般意義上的非同步,不那種無需實時返回結果的非同步,而是與是否需要返回結果無關的非同步,應該屬於一種併發策略,是在多核時代的平行計算和分散式計算思維的體現。

今天,Thinking Asynchronous - Domain Modeling using Akka Transactors - Part 1一文介紹了使用基於Scala語言的Akka框架如何實現非同步思維,非同步思維已經滲透到語言中,成為一種天然的程式設計習慣,顛覆了順序程式設計的傳統。

Akka框架之前介紹過,是和DCI架構結合的,我們已經發現軟體不同領域的發展最終都已經匯聚集合到一起,這本身也是一種Map/reduce哦。

閒話少說,讓我們看看這篇文章是如何寫的:

Evans DDD提出倉儲repository概念,將物件儲存持久和物件本身進行了分離,再加上NoSQL發展,文章引入了key-value儲存資料庫Redis作為repository的實現。

假設有一個Account物件,scala程式碼如下:

case class Account(no: String, 
  name: String, 
  dateOfOpening: Date, 
  dateOfClose: Option[Date],
  balance: Float)
<p class="indent">

有幾個欄位no 號碼,name名稱等等,設想場景:開啟一個新的Opening a New Account, 然後查詢這個賬戶的餘額, 在這個帳號加入一筆金額數字。

實現這個功能,一般是透過scala的訊息機制來實現,實現命令如下:

sealed trait AccountEvent
case class Open(from: String, no: String, name: String) extends AccountEvent
case class New(account: Account) extends AccountEvent
case class Balance(from: String, no: String) extends AccountEvent
case class Post(from: String, no: String, amount: Float) extends AccountEvent
<p class="indent">


其中trait是類似Qi4j中實現DCI架構的場景混合器,這裡是為事件AccountEvent,Open為開啟賬戶事件;New是新賬戶,Balance是查詢餘額事件,Post是加入新紀錄事件。

這些事件被客戶端發出,由Domain service領域服務攔截,最終發往倉儲Repsitory,Akka transactor可以提供基於事件的容錯性,如果事件發生衝突崩潰如何進行處理等都可以由程式碼指定。

trait AccountRepository extends Actor

class RedisAccountRepository extends AccountRepository {
  //如果事件衝突崩潰,再來一次
  lifeCycle = Some(LifeCycle(Permanent))    
  val STORAGE_ID = "account.storage"

  // actual persistent store Account例項集合 同時使用NoSQL資料庫持久化
  private var accounts = atomic { RedisStorage.getMap(STORAGE_ID) }

  def receive = {
    //接受New事件
    case New(a) => 
      atomic {
        //新載入賬戶,key為no,Value(BLOB)是Account物件的序列化陣列。
        accounts.+=(a.no.getBytes, toByteArray[Account](a))  
      }
     //接受查詢餘額事件處理
    case Balance(from, no) =>
      val b = atomic { accounts.get(no.getBytes) }
      b match {
        case None => reply(None)
        case Some(a) => 
          val acc = fromByteArray[Account](a).asInstanceOf[Account]
          reply(Some(acc.balance))
      }

      //.. other message handlers
  }
  //崩潰失敗重新如何處理?
  override def postRestart(reason: Throwable) = {
    accounts = RedisStorage.getMap(STORAGE_ID)  
  }
}
<p class="indent">

從以上看出:
1。Akka是一個基於let-it-crash 任其崩潰的方針. 透過LifeCycle(Permanent)定義崩潰後處理方式,Permanent是重新啟動 Temporary則是不啟動而直接關閉。

2.重新啟動的Hook鉤子方法你可以直接定義,如postRestart 就是重新啟動載入accounts集合。

3.Akka使用使用一種基於Java的事務機制 multiverse來實現事務,使用atomic {}就可以。

非同步訊息能夠實現端對端的解耦,不會堵塞,更易於管理,典型基於領域模型的的actor實現是輕量的,可以有數百萬個actors,它們之間透過訊息來協作互動,非常符合物件職責和互動的領域模型

說句題外話,jdon推出的開源框架Jdonframework透過domain events來實現領域模型與倉儲repository等技術架構實現互動,雖然比不上scala+Akka這麼徹底,但在設計理念上很相近,尤其還是使用Java平臺。見Domain Events非同步應用, JF的PPT中也有更多使用非同步訊息實現資料持久載入說明:http://www.jdon.com/jdonframework/

相關文章