6
The Honeymoon Is Over: Reflections after 1.5 years of Akka (proseand.co.nz)
IngramChen 積分 7

作者試了一年半的 Akka actor model,結果他的團隊寫了一整團亂七八糟的 code (尤其是新人寫的)。他講述 actor model 的缺點 - actor 在互動時,都是靠傳遞訊息 (loosely couple),如果這一連串的訊息有因果關系 (就是一串對話),在 Actor 內就得用個 Map 紀錄這段歷程,直到對話結束。

本來如果直接呼叫 function 的話,歷程就是在參數與回傳值間交換而已,用了 Actor Model,就得暫存在 Map,花精神額外管理。而他們的程式碼就大部份都在維護這類髒東西,測試也都花很多精力維持 Actor 對話的正確流程。

作者採用 functional programming 後,團隊的開發品質的確有助益,但是採用 Actor Model 後,卻沒有得到類似的結果,只留下一堆垃圾。

我自己的經驗是,能夠直接呼叫 function,同步的得到結果,就這樣寫程式吧。程式界有很多奇特的偏門,Actor Model 就是其中之一,這些偏門實在不是每天寫的程式該遇到的,尤其是一個十幾二十人的專案,還要跑個三、五年以上。Actor Model 的存在可以說是為了解決特殊的問題 -- 它適合 high concurrency、以及分散式的系統。這些系統上套用 Actor 的模型就很適合。如果是每天寫的 CRUD,或是一些演算,用 Actor 只是在綁自己而已。

timwu 積分 0

不熟 Akka, 想問一下GO 是否也會有類似的問題? 不太能體會他說的 Map 指得是什麼?

另外他說的function, 其實是 Future, GO要做 Future, 其實也是得 goroutine + channel .

IngramChen 積分 5

Akka 提供一個 Actor 模型,如果你的程式架構都依賴它,那麼整個軟體寫起來會整個偏向 Actor。但是 golang 的比較像 library,想用的地方就用,沒需要的地方還是可以照正常的寫法。

它說的 function 是 async 的,自然是回傳 Future。如果直接串的話,就是在 future callback 裡寫接下來的邏輯:

class FooActor {
   void onMessage(msg) {
       //foo 送訊息給 bar,而且要求回傳
     Future future = barActor.ask("hello")
     future.onSuccess(result-> {
          // 對方回給你它的名字,處理一些邏輯
          String name = result;

          //現在這個 callback 裡的 thread 是誰??
     });
   }
}

上面的範例,兩個 actor 交換資料,fooActor 靠 future 得到了對方的名字,但其實這個已經違反 Actor model 的精神,因為兩個 actor tightly couple,而且 callback 的 thread 是誰的也是很困擾的。Actor 就是想要遠離 thread 的操作,才特別設計的模型。

一個正統的 Actor 會這樣改寫:

class FooActor {
   void onMessage(msg) {
     if (msg instanceOf StartHello) {
         //tell(), 射後不理
         barActor.tell("hello")
     } else if (msg instanceOf HelloName) {
         String name = ((HelloName)msg).name;
         //do something about name
     }
   }
}

這樣設計的話,FooActor 就只會對 HelloName 這樣的訊息做反應,它不理會這個訊息是不是來自 barActor,而且 thread 這次也是對的,Actor 模型會保證 thread safety。

好啦,如果現在有另一個 bazActor ,它可以提供 email address,讓 fooActor 最終完成寄信的需求 (收集到 name 和 email 後,就寄信)

class FooActor {
   Map storage = new HashMap();
   void onMessage(msg) {
     if (msg instanceOf StartHello) {
         barActor.tell("hello")
     } else if (msg instanceOf HelloName) {
         String name = ((HelloName)msg).name;
         storage.put("name", name);
     } else if (msg instanceOf StartEmail) {
         bazActor.tell("email")
     } else if (msg instanceOf EmailAddress) {
         String address = ((EmailAddress)msg).address;
         String name = storage.get("name");
         sendEmail(name, address, "subject...");
     }
   }
}

好啦,上面就是 FooActor 和 Bar/BazActor 的一連串對話,等三方對話完後,FooActor 才有辦法寄信,中間過程中只能用 Map 暫存 nameemail。看到這就知道這程式夠難維護吧!這可是照著 Actor 模型的精神去寫的,actor 間完全的 decouple。

如果用 Future 一直串就沒這問題了,但這就是一般的傳統的 async 操作。如果大部份的邏輯都能這樣做,那導入 Actor 架構的意義何在?這就是作者導入一年半後,對於 Actor 模型的存疑。

我沒有 golang 的經驗,不過如果有人硬把所有的 function 呼叫通通改用 channel 互丟,那其實也會造成類似的結果吧。

結論就是架構軟體前要思考一下,慎重考慮導入全新的模型是否值得 ,不要因為技術紅 (比方說 ReactJs),或者是想玩新玩意 (比方說 microservice) 就隨便引入。當然很多事做了才知道啦,在新舊技術間不斷地試誤,就是我們開發者的日常...

snowmantw 積分 1

對一個做前端的人而言像 ReactJS 並不是因為紅才會被拿來用。當然我不否認可能有人是這樣,可是它的確解決了以前工具不足的地方。

IngramChen 積分 1

就舉例啊,有些人懂的利弊,有些人只學新的。另一個有名的例子是 MongoDB