使用f#时,如何在Api控制器中为每个新方法触摸我的DI容器

时间:2015-12-10 00:44:20

标签: dependency-injection f#

当我使用像Ninject这样的WebApi时,我试图围绕如何处理F#中的DI。

例如,在C#中,当我连接我的容器时,我只是告诉DI类型解析的内容,例如:

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

我的Api控制器会在控制器构造函数需要时自动连接它。

很好,现在我可以在界面中添加方法&amp;整天上课,无需再次接触容器。

然而在F#中(除非我这样做完全错误)我创建了部分应用程序然后将它们传递给控制器​​,每次我添加一个方法我必须再次在容器中连接。也许这是正确的,我不确定,但似乎更多的布线。

为了澄清我的意思,让我们来看一个典型的REST Api。对于每个具有CRUD的实体 - 例如:

客户(创建,阅读,更新,删除)。

我是否必须将每个功能注入控制器?

所以在这个例子中,让我说我有服务 - &gt;域名 - &gt;回购模型:

let createCustomerFunc = createCustomerDomainFunc createCustomerRepoFunc
let getAllCustomersFunc = getAllCustomerDomainFunc getAllCustomerRepoFunc
let updateCustomerFunc cust = [...]
let deleteCustomerFunc id = [...]
let getSingleCustomerFunc id = [...]

现在在我的容器中,当我绑定它时,我会做类似的事情:

kernel.Bind<CustomerController>().To<CustomerController>()
.WithConstructorArgument(createCustomerFunc, getAllCustomerFunc, etc...) 
|> ignore

现在,如果我添加方法:GetActiveCustomers我将不得不修改上面的代码以传递新的部分应用程序?

感觉......错了 - 我只是错误地接近这个?

2 个答案:

答案 0 :(得分:8)

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

主要优点是,如果you have a large code base, you can use convention over configuration连接所有依赖项。使用约定优于配置,您还可以获得代码必须更加一致的好处,因为它必须遵循约定。

但是有几个缺点。最直接的是你失去了rapid feedback from the compiler。您可以更轻松地对系统进行更改,并且在编译所有内容时,系统会在运行时失败。有些人希望你可以让DI容器进行自我诊断,但是you can't

使用DI容器的另一个不太明显的缺点是,如你所说,简单地向控制器等添加更多成员就太容易了。这实际上增加了耦合,或降低了内聚力,但基于反射的自动化DI Containers提供了这个问题。

由于我认为缺点超过优势,我建议您使用Pure DI代替DI容器。

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

在未混合的Functional F#代码库中,我根本不会使用类或接口;相反,我只是一起组成功能。

在混合代码库中,例如ASP.NET Web API,我尽可能快地跨越OOP和FP之间的桥梁。正如我在Test-Driven Development with F#谈话中所解释的那样(expanded material available on Pluralsight),我将一个函数注入到控制器中,如下所示:

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 () :> _

那个控制器的整个代码库。所有行为均由imp实施。

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

let imp =
    Validate.reservationValid
    >> Rop.bind (Capacity.check 10 SqlGateway.getReservedSeats)
    >> Rop.map SqlGateway.saveReservation

您可能会争辩说上述ReservationsController只定义了一种Post方法。如果Controller必须公开更多方法怎么办?

在这种情况下,请按方法注入实现函数。在REST API中,任何Controller都应该只有2-3个方法,这实际上意味着2-3个依赖项。在我看来,这是一个完全可以接受的依赖项。

最大2-3方法的原因在于,在适当的RESTful设计中,资源往往遵循一些交互模式:

  • GET
  • POST
  • POSTGET
  • PUTGET
  • DELETEGET
  • PUTDELETEGET

完整组合(POSTGETPUTDELETE)是一种REST设计气味,但所有这些都是完全不同的讨论。

答案 1 :(得分:0)

从根本上说,您在面向对象的框架中使用了功能代码样式。由于WebAPI要求您实例化控制器,因此必须以某种方式将OO桥接到功能方法。

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

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

仍然与

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

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