【发布时间】:2021-11-18 07:32:20
【问题描述】:
我想从功能范式的角度来理解依赖管理的概念。我试图应用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 和记录器通过依赖注入注入。这样做有两个原因:
- 我真的不知道如何以除 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# 中完成的“标准”依赖注入没有任何不同。我能看到的唯一区别是,这是对函数而不是抽象-实现对进行操作,并且是“手动”完成的。
我在互联网上搜索了一些大型项目中依赖项管理的示例,但我发现的只是简单的示例,最多传递了一个或两个函数。虽然这些都是学习目的的好例子,但我真的看不到它在实际项目中被使用,在这种项目中,这种“手动”依赖项管理可能会迅速失控。关于外部数据源(例如数据库)的其他示例提供了预期接收连接字符串的方法,但是必须从某处获得此输入[通常通过 C# 中的IConfiguration],并将其硬编码在组合根中的某处以将其传递给组合函数是显然远非理想。
我发现的另一种方法是combination of multiple dependencies into single structure。这种方法更类似于带有“接口”的标准 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 中,还是应该创建特定于给定工作流/案例的单独组合根?这些方法中的任何一种都需要我从某个地方获取必要的依赖项[如存储库],以便创建组合根。
【问题讨论】:
标签: database functional-programming f# dependencies