简体   繁体   English

DDD功能方式:为什么在将DDD应用于函数语言时将状态与行为解耦更好?

[英]DDD functional way: Why is it better to decouple state from the behavior when applying DDD with functional language?

I've read several articles (also the book Functional domain modeling ) where they propose to decouple state of the domain object from the behavior, but I cannot understand advantage of such approach over reach domain model. 我已经阅读了几篇文章(也是Functional domain modeling书),他们建议将域对象的状态与行为分离,但我无法理解这种方法优于覆盖域模型的优势。

Here is an example of reach domain model: 以下是覆盖域模型的示例:

case class Account(id: AccountId, balance: Money) {
  def activate: Account = {
   // check if it is already active, eg, enforce invariant 
   ...
  }
  def freeze: Account = ???
} 

I can chain operations with this account in following way: 我可以通过以下方式将此帐户与操作链接起来:

account.activate.freeze

Here is example of "anemic" approach which they suggest: 以下是他们建议的“贫血”方法的例子:

case class Account(id: AccountId, balance: Money)

object AccountService {
  def activate =  (account: Account) => {
   // check if it is already active, eg, enforce invariant 
    ...
  }

  def freeze =  (account: Account) =>   {
    ...     
  }
}

And here I can chain operations like this 在这里,我可以链接这样的操作

activate andThen freeze apply account

What is the advantage of the second approach except of "elegant" syntax? 除了“优雅”语法之外,第二种方法的优点是什么?

Also, in case of reach domain model, I will enforce invariants in single class, but in case of "anemic" model, logic/invariants can spread across services 此外,在达到域模型的情况下,我将在单个类中强制执行不变量,但在“贫血”模型的情况下,逻辑/不变量可以跨服务传播

One advantage might be being able to add another link to the chain without having to modify and recompile domain model. 一个优点可能是能够向链添加另一个链接,而无需修改和重新编译域模型。 For example, say we wanted to add another validation step to check for fraud 例如,假设我们想要添加另一个验证步骤来检查欺诈行为

object AccountService {
  def fraud = (account: Account) => ...
}

then we could compose this step like so 然后我们可以像这样组成这一步

(fraud andThen activate andThen freeze)(account)

Conceptually, adding fraud validation step did not mutate the structure of the domain model case class Account , so why bother re-compiling it? 从概念上讲,添加fraud验证步骤并没有改变域模型case class Account的结构,那么为什么还要重新编译呢? It is a form of separation of concerns, where we want to narrow down the changes to the codebase to the minimal relevant part. 这是一种关注点分离的形式,我们希望将对代码库的更改缩小到最小的相关部分。

I offer two thought processes, that can help explain this puzzle: 我提供了两个思考过程,可以帮助解释这个难题:


The concept of state in your example and the book differ. 你的例子和书中的state概念有所不同。 (I do hope we both are referring to Functional and Reactive Domain Modeling ). (我希望我们都指的是功能和反应域建模 )。

Your example states of activate , and freeze are probably domain concepts, while the book talks about states that only serve as markers . 你的激活冻结的示例状态可能是域概念,而本书则讨论了仅用作标记的状态 They do not necessarily have a role in the domain logic and exist only to disambiguate states of the workflow. 它们不一定在域逻辑中起作用,并且仅存在消除工作流状态的歧义。 Ex. 防爆。 applied , approved and enriched . 应用批准丰富


Functional programming is all about implementing behaviors, that are independent of the data passed into them. 函数式编程就是实现行为,它们与传递给它们的数据无关。

There are two aspects of note while implementing such behaviors. 实现此类行为时需要注意两个方面。

A behavior can be reusable across contexts. 行为可以跨上下文重用。 It can be an abstract trait, a monoid if you will, that takes any type T , and performs the same operation on it. 它可以是一个抽象特征,如果你愿意,它可以是任意类型的T ,并对其执行相同的操作。 In your example, freeze could be such a behavior, applicable to Account , Loan , Balance , etc. 在您的示例中, freeze可能是这样的行为,适用于AccountLoanBalance等。

The behavior has no side effect whatsoever. 这种行为没有任何副作用。 One should be able to call the behavior again and again with the same data set and receive the same expected response without the system getting affected or throwing an error. 应该能够使用相同的数据集一次又一次地调用该行为,并在不受系统影响或抛出错误的情况下接收相同的预期响应。 Referencing your example, calling freeze repeatedly on an account should not throw an error. 引用您的示例,在帐户上重复调用冻结不应该抛出错误。

Combining the two points, one could say it makes sense to implement a behavior as a reusable piece of code across different contexts (as a Service ) while ensuring that the input is validated (ie, validate the state of the object provided as input before processing). 结合这两点,可以说在不同的上下文(作为Service )中将行为实现为可重用的代码段是有意义的,同时确保输入被验证(即,在处理之前验证作为输入提供的对象的状态) )。

By representing the acceptable state of the object as a separate type and parameterizing the model/object with this explicit type, we could enforce a static check of input, during compile time. 通过将对象的可接受状态表示为单独的类型并使用此显式类型参数化模型/对象,我们可以在编译期间强制执行输入的静态检查。 Referring to the example provided in the book, you can only approve andThen enrich . 参考书中提供的示例,您只能approve andThen enrich Any other incorrect sequence will raise a compile-time error, which is far more preferable to using defensive guards to check input during runtime. 任何其他不正确的序列都会引发编译时错误,这比使用防御性防护在运行时检查输入更为可取。

Thus, the second approach is not just elegant syntax at the end of the day. 因此,第二种方法不仅仅是在一天结束时优雅的语法。 It is a mechanism to build compile-time checks, based on the state of an object. 它是一种基于对象状态构建编译时检查的机制。


So, while the output has the appearance of an anemic model, the second approach is taking advantage of some beautiful patterns bought forth by functional programming. 因此,虽然输出具有贫血模型的外观,但第二种方法是利用函数式编程所获得的一些漂亮模式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM