2013-02-11 180 views
14

函數式編程提倡不可變的類和參照透明。函數式編程+域驅動設計

域驅動設計由值對象(不可變)和實體(可變)組成。

我們應該創建不可變的實體而不是可變的實體嗎?

假設,項目使用的Scala爲主要語言,我們怎麼可能寫實體爲case類(不可改變的話),而不用擔心過時狀態,如果我們正在處理的併發?

什麼是好習慣?保持實體可變(var字段等),並避免案例類的偉大語法?

+1

你應該看看事件採購imo,這是一個很好的方法來實現與面向對象的DDD類似的方法。 – 2013-02-11 14:22:28

+1

查看https://www.manning.com/books/functional-and-reactive-domain-modeling,其中涵蓋了如何組合DDD和FP。 Debasish也有一些很好的演示,例如檢查:https://www.youtube.com/watch?v=U0Rk9Knq8Vk – Cal 2016-04-25 04:30:46

回答

12

你可以有效地在Scala中使用不可變的實體,並避免可變字段和從可變狀態派生出來的所有錯誤。使用不可變實體可以幫助您實現併發性,而不會讓事情變得更糟。你以前的可變狀態將成爲一組轉換,它將在每次更改時創建一個新的引用。

但是,在您的應用程序的某個級別,您將需要具有可變狀態,否則您的應用程序將無用。這個想法是儘可能地在你的程序邏輯中推動它。我們來舉一個銀行賬戶的例子,這個賬戶可能因爲利率和ATM取款或存款而改變。

你有兩個有效途徑:

  • 您公開可以修改內部特性的方法和你管理這些方法併發(很少,其實)

  • 你讓所有的類不可變的,你用一個可以改變賬戶的「經理」來包圍它。

既然第一個很簡單,我會詳細介紹第一個。

case class BankAccount(val balance:Double, val code:Int) 

class BankAccountRef(private var bankAccount:BankAccount){ 
    def withdraw(withdrawal) = { 
     bankAccount = bankAccount.copy(balance = bankAccount.balance - withdrawal) 
     bankAccount.balance 
    } 
} 

這很好,但天哪,你仍然堅持管理併發。那麼,斯卡拉爲您提供了一個解決方案。這裏的問題是,如果您將對BankAccountRef的引用分享給後臺作業,則必須同步該調用。問題在於你正在以不理想的方式進行併發。

做併發的最佳方式:消息傳遞

如果在另一邊,在不同的工作不能直接在顯示BankAccount或BankAccountRef調用方法,只是通知他們,有些操作必須執行?那麼,你有一個Actor,在Scala中進行併發的最喜歡的方式。

class BankAccountActor(private var bankAccount:BankAccount) extends Actor { 

    def receive { 
     case BalanceRequest => sender ! Balance(bankAccount.balance) 
     case Withdraw(amount) => { 
      this.bankAccount = bankAccount.copy(balance = bankAccount.balance - amount) 
     } 
     case Deposit(amount) => { 
      this.bankAccount = bankAccount.copy(balance = bankAccount.balance + amount) 

     } 

    } 
} 

此解決方案在Akka文檔中有詳細描述:http://doc.akka.io/docs/akka/2.1.0/scala/actors.html。這個想法是你通過發送消息到它的郵箱來與一個Actor進行通信,並且這些消息按照接收的順序進行處理。因此,如果使用此模型,您將永遠不會有併發缺陷。

+0

但目前在內存中是否存在一組轉換的風險?讓我們設想幾個後臺作業任務的不同線程維護同一個實體的不同版本。這會導致不一致,不是嗎? – Mik378 2013-02-11 14:38:41

+0

我明白你來自哪裏。維護狀態的後臺作業可能成爲問題的根源。我仍然想知道哪個用例是維持獨立後臺作業之間共享狀態的有效方式 – Edmondo1984 2013-02-11 14:44:09

+2

用例?銀行賬戶。後臺工作可能是報告,ATM處理程序,利率計算器...... – 2013-02-11 14:47:47

10

這是一種意見問題,是你認爲不太具體的scala。

如果你真的想擁抱FP,我會爲你所有的域對象進行不可變的路由,並且永遠不要放任何行爲。

這就是有些人稱之爲行爲與狀態之間總是存在分離的上述服務模式。這在OOP中避而遠之,在FP中是自然的。

這也取決於你的域名是什麼。用戶界面和視頻遊戲等有狀態的事物可以讓OOP更輕鬆一些。對於網站或REST等硬核後端服務,我認爲服務模式更好。

除了經常提到的併發之外,我喜歡關於不可變對象的兩件非常好的事情是它們對於緩存更加可靠,而且它們對於分佈式消息傳遞(例如,通過amqp的protobuf)也非常好,因爲意圖非常明確。

在FP人

還通過創建,使您能夠變異一種「語言」或「對話」又名DSL(Builders,單子,管道,箭頭,STM等)打擊可變,以不變的橋樑,然後改造回到不可變的領域。上述服務使用DSL進行更改。這比你想象的更自然(例如SQL是一個「對話」的例子)。另一方面,OOP更喜歡擁有一個可變域,並利用該語言現有的程序部分。

+0

這是我如何做到這一點。用於對域行爲和不可變數據對象(案例類作爲ADT)進行建模的服務。保持服務儘可能純粹(不可避免地存在IO,但您可以小心翼翼地將其推到邊緣)。 DDD中存在許多常見模式來解決在具有圖層的命令式OO系統中管理可變狀態的問題。我認爲FP方法可以避免大量的需求。 – 2013-02-11 19:00:55

+0

@Adam根特好的回答:) – Mik378 2013-02-11 19:48:20

+0

@BrianSmith是的。我認爲DDD以及其他一些Fowler/Evans模式被高估(這些人需要在FP陣營中花費一些時間)。現代後端Web應用程序更適合FP。 [我甚至是一個Java程序員,更喜歡它](https://github.com/agentgt/jirm)。另外DDD的定義是flakey。看起來DDD鄙視轉換又名DTOs,但以我的經驗,你總是最終需要傳輸對象(即不同的視圖)。 FP擁抱轉型。 – 2013-02-11 20:29:25