功能编程+领域驱动设计

Mik*_*378 16 entities domain-driven-design functional-programming scala

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

域驱动设计由Value Object(不可变)和Entities(可变)组成.

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

让我们假设,项目使用Scala作为主要语言,如果我们处理并发,我们如何编写实体作为案例类(不可变),而不会有过时状态的风险?

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

Edm*_*984 14

您可以在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
   }
}
Run Code Online (Sandbox Code Playgroud)

这很好,但天哪,你仍然坚持管理并发.那么,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)

        }

    }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案在Akka文档中有详细描述:http://doc.akka.io/docs/akka/2.1.0/scala/actors.html.我们的想法是通过向其邮箱发送消息与Actor通信,并按接收顺序处理这些消息.因此,如果使用此模型,您将永远不会遇到并发缺陷.

  • 用例?银行账户.后台工作可能是报告,ATM处理程序,利率计算器...... (2认同)

Ada*_*ent 10

这是一个意见问题,与您认为的scala具体相比较少.

如果你真的想拥抱FP我会为你所有的域对象都采用不可变的路由,并且永远不会放任何行为.

这就是有些人将上述服务模式称为行为和状态之间始终存在分离.这在OOP中避开但在FP中是自然的.

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

除了经常提到的并发之外,我喜欢两个非常好的不可变对象的事情是,它们对于缓存更加可靠,并且它们对于分布式消息传递也是很好的(例如,amqp上的protobuf)因为意图非常明确.

同样在FP中,通过创建"语言"或"对话"(即DSL(构建器,Monads,管道,箭头,STM等))来对抗可变到不可变的桥,使您能够变异然后转换回不可变的域.上述服务使用DSL进行更改.这比你想象的更自然(例如SQL是一个例子"对话").另一方面,OOP更喜欢具有可变域并利用该语言的现有过程部分.