3

设计大型应用程序时的一个主要架构目标是减少耦合和依赖性。依赖关系是指源代码依赖关系,当一个函数或数据类型使用另一个函数或另一种类型时。高级架构指南似乎是Ports & Adapters架构,略有不同也称为洋葱架构六边形架构干净架构:建模应用程序的类型和功能位于中心,然后使用在领域的基础上提供有用服务的案例,在最外圈是技术方面,如持久性、网络和 UI。

依赖规则说依赖必须只指向内部。例如; 持久性可能取决于用例中的函数和类型,而用例可能取决于域中的函数和类型。但不允许域依赖于外环。我应该如何在 Haskell 中实现这种架构?具体来说:我如何实现一个不依赖(=导入)持久性模块中的函数和类型的用例模块,即使它需要检索和存储数据?

假设我想通过一个函数实现一个用例订单放置U.placeOrder :: D.Customer -> [D.LineItem] -> IO U.OrderPlacementResult,该函数从订单项创建一个订单并尝试保留该订单。这里,U表示用例模块和D域模块。该函数返回一个 IO 操作,因为它需要以某种方式保持订单。然而,持久化本身是在最外层的架构环中——在某些模块中实现P;因此,上述函数不能依赖于从P.

我可以想象两种通用解决方案:

  1. 高阶函数:该函数U.placeOrder接受一个额外的函数参数,例如 U.OrderDto -> U.PersistenceResult。这个函数是在persistence( P)模块中实现的,但是它依赖于模块的类型U,而U模块不需要声明依赖P
  2. 类型类:U模块定义了一个Persistence声明上述函数的类型类。该P模块依赖于该类型类并为其提供一个实例。

变体 1 非常明确,但不是很通用。它可能会导致具有许多参数的函数。变体 2 不那么冗长(例如,请参见此处)。然而,变体 2 导致了许多无原则的类型类,这在大多数现代 Haskell 教科书和教程中被认为是不好的做法。

所以,我有两个问题:

  • 我错过了其他选择吗?
  • 通常推荐哪种方法(如果有)?
4

1 回答 1

2

事实上,还有其他选择(见下文)。

虽然您可以部分应用程序用作依赖注入,但我不认为它是一个合适的功能架构,因为它使一切变得不纯。

对于您当前的示例,这似乎并不重要,因为U.placeOrder它已经不纯了,但总的来说,您希望您的 Haskell 代码包含尽可能多的引用透明代码。

您有时会看到一个涉及Readermonad 的建议,其中“依赖项”作为阅读器上下文而不是直接的函数参数传递给函数,但据我所知,这些只是(同构?)相同想法的变体,同样的问题。

更好的替代方案是功能核心、命令式外壳自由单子。可能还有其他选择,但这些是我所知道的。

功能核心,命令式外壳

您通常可以分解您的代码,以便将您的域模型定义为一组纯函数。这在 Haskell 和 F# 等语言中通常更容易做到,因为您可以使用 sum 类型来传达决策。U.placeOrder例如,该函数可能如下所示:

U.placeOrder :: D.Customer -> [D.LineItem] -> U.OrderPlacementDecision

请注意,这是一个纯函数,其中U.OrderPlacementDecision可能是枚举用例所有可能结果的 sum 类型。

那是你的功能核心。然后,您将在一个不纯的三明治中编写您的命令式外壳(例如您的main函数):

main :: IO ()
main = do
  stuffFromDb <- -- call the persistence module code here
  customer -- initialised from persistence module, or some other place
  lineItems -- ditto
  let decision = U.placeOrder customer lineItems
  _ <- persist decision
  return ()

(我显然没有尝试对该代码进行类型检查,但我希望它足够正确,可以理解这一点。)

免费的单子

到目前为止,功能核心、命令式外壳是实现所需架构结果的最简单方法,而且很明显通常可以侥幸逃脱。不过,在某些情况下这是不可能的。在这些情况下,您可以改用免费的 monad。

使用自由单子,您可以定义大致相当于面向对象接口的数据结构。就像在函数式核心、命令式 shell案例中一样,这些数据结构是 sum 类型,这意味着您可以保持函数纯净。然后,您可以在生成的表达式树上运行不纯的解释器。

我写了一篇关于如何思考 F# 和 Haskell 中的依赖注入的文章系列。我最近还发表了一篇文章(除其他外)展示了这种技术。我的大部分文章都附有 GitHub 存储库。

于 2020-05-14T14:05:20.323 回答