gpt4 book ai didi

database - F# - 依赖管理

转载 作者:行者123 更新时间:2023-12-04 12:30:39 25 4
gpt4 key购买 nike

我想从功能范式的角度理解依赖关系管理的概念。我试图应用 dependency rejection 的概念据我了解,这一切都归结为从不纯 [I/O] 和纯操作创建一个“三明治”,并在系统边缘执行任何 I/O 操作时仅将值传递给纯函数。问题是,我仍然必须以某种方式从外部来源获取结果,这就是我遇到的问题。

考虑下面的代码:

[<ApiController>]
[<Route("[controller]")>]
type UserController(logger: ILogger<UserController>, compositionRoot: CompositionRoot) =
inherit BaseController()

[<HttpPost("RegisterNewUser")>]
member this.RegisterNewUser([<FromBody>] unvalidatedUser: UnvalidatedUser) = // Receive input from external source: Impure layer
User.from unvalidatedUser // Vdalidate incoming user data from domain perspective: Pure layer
>>= compositionRoot.persistUser // Persist user [in this case in database]: Impure layer
|> this.handleWorkflowResult logger // Translate results to response: Impure layer

CompositionRoot 和 logger 是通过依赖注入(inject)注入(inject)的。这样做有两个原因:

  • 我真的不知道如何以 DI 以外的其他功能方式获取这些依赖项。
  • 在这种特殊情况下,CompositionRoot 需要基于 EntityFramework 的数据库存储库,这些存储库也是通过 DI 获得的。

这是组合根本身:

type CompositionRoot(userRepository: IUserRepository) = // C# implementation of repository based on EntityFramework
member _.persistUser = UserGateway.composablePersist userRepository.Save
member _.fetchUserByKey = UserGateway.composableFetchByKey userRepository.FetchBy

在我看来,上面的内容与在 C# 中完成的“标准”依赖项注入(inject)没有任何不同。我能看到的唯一区别是,这个是对函数而不是抽象实现对进行操作,并且它是“手动”完成的。

我在互联网上搜索了一些大型项目中依赖管理的例子,但我找到的是最多传递一两个函数的简单例子。虽然这些都是用于学习目的的好例子,但我真的看不到它在现实世界的项目中得到利用,在这种“手动”依赖关系管理可能很快就会失控的情况下。关于外部数据源(如数据库)的其他示例提供了预期接收连接字符串的方法,但此输入必须从某处获取[通常通过 C# 中的 IConfiguration] 并将其硬编码在组合根中的某处以传递它到组合函数显然远非理想。

我发现的另一种方法是 combination of multiple dependencies into single structure .这种方法更类似于具有“接口(interface)”的标准 DI,同样是手工编写的。

我还有最后一个顾虑:那些调用需要某些依赖项的其他函数的函数怎么办?我是否应该将这些依赖项向下传递给所有函数?

let function2 dependency2 function2Input =
// Some work here...

let function1 dependency1 dependency2 function1Input =
let function2Input = ...

function2 dependency2 function2Input

// Top-level function which receives all dependencies required by called functions
let function0 dependency0 dependency1 dependency2 function0Input =
let function1Input = ...

function1 dependency1 dependency2 function1Input

最后一个问题是关于组合根本身的:它应该位于何处?我应该以类似于在 C# Startup 中注册所有服务的方式构建它,还是应该创建特定于给定工作流/案例的单独组合根?这些方法中的任何一种都需要我从某个地方获取必要的依赖项 [如存储库] 以创建组合根。

最佳答案

这里的问题不止一个,我尽量按顺序回答。

首先,您需要权衡不同架构决策的优缺点。为什么在第一种情况下将依赖项注入(inject) Controller ?

如果您想让 Controller 接受某些类型的自动化测试,这可能是个好主意。我通常do this with state-based integration testing .然而,还有另一种观点坚持认为您不应该对 Controller 进行单元测试。这个想法是 Controller 应该如此耗尽逻辑单元测试不值得麻烦。在这种情况下,您不需要那个级别的依赖注入(inject) (DI) - 至少,不需要用于测试目的。相反,您可以将实际的数据库代码留在 Controller 中,而不必求助于任何 DI。那么,任何测试都必须涉及一个真实的数据库(尽管也可以自动化)。

这将是一个有效的架构选择,但为了争论起见,我们假设您至少想要将依赖项注入(inject) Controller ,以便您可以进行一些自动化测试。

在 C# 中,我会为此使用接口(interface),并且 I'd also use interfaces in F# .没有理由将同事与 free monads 混淆.不过,传递函数也可能是可行的。

I searched over the internet for some examples of dependencies management in larger project

是的,这是一个已知问题(在 OOD 中也是如此)。由于相当明显的原因,缺乏复杂的示例:真实世界的示例是专有的,通常不是开源的,很少有人会花费几个月的空闲时间来开发足够复杂的示例代码库。

为了弥补这一不足,我开发了这样一个代码库来配合 my book Code That Fits in Your Head .该代码库使用 C# 而不是 F#,但它确实遵循 Impureim Sandwich architecture (又名 功能核心,命令式 shell)。我希望您能够从该示例代码库中学习。它兼顾了一小部分不纯的依赖关系,但将它们全部限制在 Controller 中。

What about functions that call other functions that require some dependencies?

在 FP 中,您应该努力编写纯函数。虽然您可以从其他函数 ( higher order functions ) 组合函数,但它们 should all still be pure .因此,组合具有不纯依赖性的其他函数的函数并不是真正的惯用,因为 that makes the entire composition impure .

相反,keep all impure dependencies at the boundary of the system (e.g. Controllers) and compose everything else from pure functions .

composition root itself: Where should it be located?

如果您完全需要一个(关于可测试性的第一点),它将等同于 C#。将其关闭应用程序的入口点。

无论语言,我更喜欢pure DI为此。

关于database - F# - 依赖管理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69324771/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com