功能编程+领域驱动设计

时间:2013-02-11 13:53:38

标签: scala functional-programming domain-driven-design entities

函数式编程提升了不可变类和引用透明性。

域驱动设计由值对象(不可变)和实体(可变)组成。

我们应该创建不可变实体而不是可变实体吗?

让我们假设,项目使用Scala作为主要语言,如果我们处理并发问题,我们如何将实体编写为 case classes (immutable so)而不会有过时状态的风险?

什么是好习惯?保持实体可变(var字段等...)并避免案例类的强大语法

2 个答案:

答案 0 :(得分:13)

您可以在Scala中有效地使用不可变实体,并避免可变字段的恐怖以及源自可变状态的所有错误。使用Immutable实体可以帮助您实现并发性,不会让事情变得更糟。您之前的可变状态将成为一组转换,它将在每次更改时创建新的引用。

但是,在某个应用程序级别,您需要具有可变状态,否则您的应用程序将无用。我们的想法是在程序逻辑中尽可能地推动它。让我们举一个银行账户的例子,它可以因利率和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
   }
}

这很好,但天哪,你仍然坚持管理并发。那么,Scala为您提供了解决方案。这里的问题是,如果您将对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通信,并按接收顺序处理这些消息。因此,如果使用此模型,您将永远不会出现并发缺陷。

答案 1 :(得分:10)

这是一个意见问题,与你想的那样具有较少的scala特定性。

如果你真的想拥抱FP,那么我会为你所有的域对象选择不可变的路由,并且永远不会放任何行为。

有些人将上述服务模式称为行为和状态之间的分离。这在OOP中是避免的,但在FP中是自然的。

这也取决于您的域名。使用UI和视频游戏等有状态的东西,OOP有时会更容易。对于像网站或REST这样的核心后端服务,我认为服务模式更好。

除了经常提到的并发性之外,我喜欢不可变对象的两个非常好的事情是它们在缓存方面更可靠,并且它们也非常适合分布式消息传递(例如amobp上的protobuf),因为意图非常明确。 / p>

同样在FP中,人们通过创建“语言”或“对话”(即DSL(Builders,Monads,Pipes,Arrows,STM等)来对抗可变到不可变的桥梁。你要变异,然后转换回不可变域。上述服务使用DSL进行更改。这比你想象的更自然(例如,SQL是一个例子“对话”)。另一方面,OOP更喜欢具有可变域并利用该语言的现有过程部分。