Zaid-Ajaj / Fable.Remoting

Type-safe communication layer (RPC-style) for F# featuring Fable and .NET Apps
https://zaid-ajaj.github.io/Fable.Remoting/
MIT License
273 stars 55 forks source link

Dependency injection explanation in the book #170

Closed kaashyapan closed 4 years ago

kaashyapan commented 4 years ago

I apologize for creating an issue to ask a question. But my intention was to see if the gitbook can be improved since you have a way of explaining things in very simple language.

One of the areas of difficulty is grokking the OOP and the FP parts in a real F# project. Thanks for breaking down dependency injection so plainly in the book.

Here is where you register your dependencies, with the function above you are saying: "When a dependency of type ITodoStore is required, return an instance of InMemoryTodoStore".

I just wanted to probe this a bit further.

In most sample apps including Tabula-Rasa, I see ILogger inserted via dependency injection. I can see 2 possible goals to this

  1. Hydrate a singleton instance of an ILogger instance and pass it around via the context
  2. Makes the ILogger interface extensible.

My noobie question is.... Why not wrap the logger in a separate module and use it everywhere ? What would be wrong with this approach ?

Logger.fs

module Logger

open System
open Serilog

// Create a single instance of logger and mark it private
// Read log level as environment variable and configure the logger
let private _logger =
    LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger()

// The logger instance is created only once and is accessible only via a val in the module
let Log = _logger

DBWriter.fs

module DBWriter

open DBClient
open Logger

let writeToDB = 
            Log.Information("Wrote to DB")

Or maybe you can write a blog post sometime to include an explanation about dependency injection in a way that is simple and understandable and does not include the words partial application and reader monad. Maybe a simpler version of this. https://bartoszsypytkowski.com/dealing-with-complex-dependency-injection-in-f

Zaid-Ajaj commented 4 years ago

Hi @kaashyapan, no worries at all. Issues are perfect place for questions.

Why not wrap the logger in a separate module and use it everywhere ? What would be wrong with this approach ?

It has to do with how the logger is initialized in the first place. Creating a global logger works OK. However, that is not possible when you want to hook into the logging and configuration utilities (among other really useful things) provided by AspNet core which themselves can provide request specific loggers (that understand the context of a single request).

Integrating into those useful blocks requires that you use DI the way AspNet core expects: by asking for the registered services from the HttpContext.GetService<'T> function. At first I didn't like this solution but I think it makes this whole DI business easier if you follow the frameworks path.

As for the documentation. I am planning on updating it with the following pattern that I think makes for the easiest way to DI with Fable.Remoting and AspNet core apps:

// given a protocol 
type IServerApi = { GetData : unit -> Async<string> } 

Implement the protocol as follows:

// note using constructor injection
type ServerApi(ILogger<ServerApi> logger, IConfiguration config) =  
   member this.GetData() = 
     async {
        logger.LogInformation("Executing function {Function}", "GetData")
        return "The data"
     }

  member this.Build() : IServerApi = 
      {  GetData = this.GetData }

Register a singleton of ServerApi and then creating a web service out of it:

let webApi =
    Remoting.createApi()
    |> Remoting.fromContext (fun ctx -> ctx.GetService<ServerApi>().Build())
    |> Remoting.withRouteBuilder routerPaths
    |> Remoting.buildHttpHandler

This technique is used in SAFE.Simplified and I am really happy with it. I'll be updating the docs to include this too (PR would be nice though)

Hope this helps, if you have questions just let me know :smile:

kaashyapan commented 4 years ago

Thanks for the explanation. You are too kind.