首页 文章

功能编程 Domain-Driven 设计

提问于
浏览
16

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

Domain-driven design 由 Value Object(不可变)和 Entities(可变)组成。

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

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

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

2 回答

  • 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 通信,并按接收顺序处理这些消息。因此,如果使用此模型,您将永远不会遇到并发缺陷。

  • 10

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

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

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

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

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

    同样在 FP 中,人们通过创建“语言”或“对话”(即 DSL)来实现可变到不可变的桥梁(建设者,Monads,Pipes,Arrows,STM etc...),使您能够变异,然后转换回不可变域。上面提到的使用 DSL 进行更改。这比您想象的更自然(e.g. SQL 是一个示例“对话”)。另一方面,OOP 更喜欢具有可变域并利用该语言的现有过程部分。

相关问题