Dependency injection 在使用f时,如何避免Api控制器中的每个新方法都接触到我的DI容器#

Dependency injection 在使用f时,如何避免Api控制器中的每个新方法都接触到我的DI容器#,dependency-injection,f#,Dependency Injection,F#,当我将WebApi与Ninject之类的东西一起使用时,我正试图了解如何在F#中处理DI 例如,在C#中,当我连接容器时,我会简单地告诉DI类型解析为什么,例如: kernel.Bind<ISomeInterface>().To<SomeClass>(); 现在在我的容器中,当我绑定它时,我会执行如下操作: kernel.Bind<CustomerController>().To<CustomerController>() .WithConstr

当我将WebApi与Ninject之类的东西一起使用时,我正试图了解如何在F#中处理DI

例如,在C#中,当我连接容器时,我会简单地告诉DI类型解析为什么,例如:

kernel.Bind<ISomeInterface>().To<SomeClass>();
现在在我的容器中,当我绑定它时,我会执行如下操作:

kernel.Bind<CustomerController>().To<CustomerController>()
.WithConstructorArgument(createCustomerFunc, getAllCustomerFunc, etc...) 
|> ignore
kernel.Bind()到()
.WithConstructorArgument(createCustomerFunc、getAllCustomerFunc等)
|>忽略
现在,如果我添加方法:GetActiveCustomers,那么我必须修改上面的代码才能传入新的部分应用程序


那感觉。。。错误-我只是在错误地处理这个问题吗?

基本上,您是在面向对象框架中使用功能代码样式。由于WebAPI要求您实例化一个控制器,因此您必须以某种方式将OO连接到函数方法

在DI容器中设置函数值是一种相当笨拙的方法,因为您必须手动绑定到构造函数参数。我建议使用基于适配器模式的方法,即创建一个类来包装(静态)函数调用

pub type CustomerFunctionAdapter() =
     member x.CreateCustomer = createCustomerFunc
     member x.GetAllCustomers = getAllCustomersFunc
     // ...
仍然与

kernel.Bind<ISomeInterface>().To<CustomerFunctionAdapter>();
kernel.Bind().To();

这样,您的更改和添加将在CustomerFunctionAdapter中进行,而不是在DI绑定中进行。

使用DI容器既有一些优点,也有一些缺点

主要的优点是,如果需要连接所有依赖项。使用约定优于配置,您还可以获得这样的好处:您的代码必须更加一致,因为它必须遵循约定

但也有一些缺点。最直接的是你失去了工作。您可以更轻松地对系统进行更改,当所有内容都编译时,系统会在运行时失败。有些人希望您可以要求DI容器进行自我诊断,但是

使用DI容器的另一个不太明显的缺点是,正如您所说,简单地向控制器添加更多成员变得太容易了。这实际上增加了耦合,或者降低了内聚性,但是DI容器提供的基于反射的自动化将此问题隐藏起来

因为我相信缺点大于优点,所以我建议您使用DI容器而不是DI容器

这适用于C#、Visual Basic.NET或Java中的面向对象编程,但同样适用于F#中的函数编程

在未混合的函数F#代码库中,我根本不会使用类或接口;相反,我只会将函数组合在一起

在ASP.NET Web API等混合代码库中,我会尽快跨越OOP和FP之间的桥梁。正如我在演讲()中所解释的,我将向控制器中注入一个函数,如下所示:

type ReservationsController(imp) =
    inherit ApiController()
    member this.Post(rendition : ReservationRendition) : IHttpActionResult =
        match imp rendition with
        | Failure(ValidationError msg) -> this.BadRequest msg :> _
        | Failure CapacityExceeded -> this.StatusCode HttpStatusCode.Forbidden :> _
        | Success () -> this.Ok () :> _
let imp =
    Validate.reservationValid
    >> Rop.bind (Capacity.check 10 SqlGateway.getReservedSeats)
    >> Rop.map SqlGateway.saveReservation
这就是控制器的全部代码库。所有行为都由
imp
实现

在应用程序的启动代码中,
imp
的组成如下:

type ReservationsController(imp) =
    inherit ApiController()
    member this.Post(rendition : ReservationRendition) : IHttpActionResult =
        match imp rendition with
        | Failure(ValidationError msg) -> this.BadRequest msg :> _
        | Failure CapacityExceeded -> this.StatusCode HttpStatusCode.Forbidden :> _
        | Success () -> this.Ok () :> _
let imp =
    Validate.reservationValid
    >> Rop.bind (Capacity.check 10 SqlGateway.getReservedSeats)
    >> Rop.map SqlGateway.saveReservation
您可能会争辩说,上面的
ReservationsController
只定义了一个
Post
方法。如果控制器必须公开更多的方法怎么办

在这种情况下,为每个方法注入一个实现函数。在RESTAPI中,任何控制器无论如何都应该只有2-3个方法,因此实际上,这意味着2-3个依赖项。在我看来,这是一个完全可以接受的依赖数量


最大2-3方法的原因是,在适当的RESTful设计中,资源往往遵循以下几种交互模式:

  • GET
  • POST
  • POST
    GET
  • PUT
    GET
  • 删除
    获取
  • 放入
    删除
    获取

完整的组合(
POST
GET
PUT
DELETE
)是REST设计的味道,但所有这些都是一个完全不同的讨论。

关于DI容器后期绑定问题的一个好观点。不管是哪种方式,一开始它感觉很尴尬,但现在在完成V1之后就不那么尴尬了-没有接口,纯功能性的-而且最后也少了很多麻烦:-)在那次谈话中(“看起来没有模仿”一词),您从未测试过
imp
函数本身。如果你这样做了,这个函数将有另外3或4个参数(取决于你想如何连接它),这只是一个非常简单的玩具示例。最终,这样的“根”函数最终会有无数个参数(树下所有依赖项的传递闭包),这就是最初的问题的实质所在。@FyodorSoikin如果我在C#中做了类似的事情,并且涉及到DI容器,我同样不会觉得有必要对集成进行单元测试,但如果我有,我可以。在我的Pluralsight课程中,有很多(C#)这样的例子,它并没有改变设计。这一结论也适用于F#。在伦敦举行的Progressive F#Tutorials 2014上,我展示了用F#编写的上述
imp
函数的相同类型的集成测试,它没有改变任何东西。有Progressive F#Tutorials talk的视频吗?或者至少是幻灯片组?@FyodorSoikin没有,幻灯片也帮不了你,但我会看看是否能写下要点。。。