How not to touch my DI container for every new method in Api Controller when using f #

I am trying to wrap my head around how to handle DI in F # when using WebApi with something like Ninject.

For example, in C #, when I connect my container, I just tell DI which type is allowed, for example:

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

My Api Controllers will automatically bind this when the controller builder is required.

Great, now I can add methods to the interface and class all day without touching the container again.

However, in F # (if I am not doing it completely wrong) I create partial applications and then pass them to the controller, every time I add a method, I have to connect to the container again. This may be right, I'm not sure, but it seems like a lot more wiring.

To clarify what I mean, let's take a typical REST Api. For each object with CRUD - for example, for example:

Client (create, read, update, delete).

Should I embed each function in the controller?

So, in this example, let's say I have a service β†’ domain β†’ repo model:

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

Now in my container, when I bind it, I would do the following:

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

Now, if I add a method: GetActiveCustomers, would I have to modify my code above to pass a new partial application?

It seems ... wrong - am I just getting it wrong?

+5
source share
2 answers

Using a DI container provides some advantages as well as some disadvantages.

The main advantage is that if you have a large code base, you can use the configuration convention to connect all the dependencies. If you agree with the configuration, you will also get the advantage that your code will be more consistent, because it must follow the conventions.

However, there are several drawbacks. The most immediate thing is that you lose fast feedback from the compiler . You can make it much easier to make changes to your system, and while everything compiles, the system crashes at runtime. Some people hope that you can ask the DI Container to diagnose it yourself, but you cannot .

Another, less obvious, drawback of using the DI container is that it becomes too easy, as you say, to simply add more members to the controllers, etc. This actually increases traction or decreases traction, but the reflection-based automation provided by DI Containers hides this problem from you.

Since I believe the disadvantages outweigh the benefits, I recommend that you use Pure DI instead of DI containers.

This applies to object-oriented programming in C #, Visual Basic.NET or Java, but equally applies to functional programming in F #.

In the unbiased functional base of F # code, I would not use classes or interfaces at all; instead, I only collected functions.

In a mixed codebase, such as the ASP.NET Web API, I would move the bridge between OOP and FP as quickly as possible. As I explain in the section Test-Driven Development with F # ( advanced material available on Pluralsight ), d will inject the function into the controller as follows:

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

This is the entire code base of this Controller. All behavior is implemented by imp .

In the application startup code, imp is like this:

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

You can argue that the ReservationsController above defines only one Post method. What if the controller should expose more methods?

In this case, add an implementation function for each method. In the REST API, any controller should have 2-3 methods in any case, which means, in fact, 2-3 dependencies. In my opinion, this is a perfectly acceptable number of dependencies.


The reason for the maximum of method 2-3 is that in a proper RESTful design, resources tend to follow several interaction patterns:

  • GET
  • Post
  • Post GET
  • PUT , GET
  • DELETE , GET
  • PUT , DELETE , GET

The full combination ( Post , GET , PUT , DELETE ) is the smell of REST design, but it's all a completely different discussion.

+8
source

Essentially, you are using a functional code style in an object-oriented structure. Since the WebAPI required that you create the controller instance, you must somehow associate OO with the functional approach.

The value of the function values ​​in the DI container is rather inconvenient, since you need to manually bind the constructor arguments. I would recommend an approach based on adapter templates, that is, create a class for transferring calls to (static) functions.

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

and still mess with

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

So your changes and additions are in the CustomerFunctionAdapter, and in your DI bindings.

0
source

Source: https://habr.com/ru/post/1237814/


All Articles