Dependency injection 在使用f时,如何避免Api控制器中的每个新方法都接触到我的DI容器#
当我将WebApi与Ninject之类的东西一起使用时,我正试图了解如何在F#中处理DI 例如,在C#中,当我连接容器时,我会简单地告诉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
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没有,幻灯片也帮不了你,但我会看看是否能写下要点。。。