Working with Dependency Injection frameworks - jacentino/DbFun GitHub Wiki

Dependency Injection frameworks relay on objects, that can be wired-up dynamically, thus we can't define our database API-s as modules.

We must use classes with carefully designed interfaces instead.

Services

Simplest way is defining inerfaces with query functions as properties:

type IBloggingService = 
    abstract member GetBlog: (int -> DbCall<Blog>)
    abstract member GetPosts: (int -> DbCall<List<Post>>)
    abstract member GetComments: (int -> DbCall<List<Comment>>)

type BloggingService(query: QueryBuilder) = 

    interface IBloggingService with         
        member val GetBlog = query.Sql<int, Blog>(...)
        member val GetPosts = query.Sql<int, Post list>(...)
        member val getComments = query.Sql<int, Comment list>(...)

Of course, queries can be defined as methods, but property-based code is more concise thanks to member val construct.

In method based approach, additional fields are needed:

type IBloggingService = 
    abstract member GetBlog: int -> DbCall<Blog>
    ...

type BloggingService(query: QueryBuilder) = 

    let getBlog = query.Sql<int, Blog>(...)
    ...

    interface IBloggingService with         
        member __.GetBlog id = getBlog id
        ...

Configuration

We must define our run function differently. Since it's impossible to pass generic functions as parameters, it should be defined as a generic method of some object:

module DB = 
    let createConnection () = new SqlConnection(connectionString) :> IDbConnection

    let query = 
        let config = QueryConfig.Default createConnection
        <Some configuration code>
        QueryBuilder(config)

    type IRunner = 
        abstract member Run: DbCall<'T> -> Async<'T>

    let runner = 
        { new IRunner with
            member __.Run f = DbCall.Run(createConnection, f)
        }

Since query and runner are stateless objects, we can register them in a DI container as singletons:

    builder.Services.AddSingleton<IBloggingService, BloggingService>()
    builder.Services.AddSingleton(DB.query)
    builder.Services.AddSingleton(DB.runner)

Usage

Let's call our query directly from a controller:

[<ApiController>]
[<Route("[controller]")>]
type BlogController (bloggingService: IBloggingService, runner: DB.IRunner) =
    inherit ControllerBase()

    [<HttpGet>]
    member this.Get(id: int) =
        bloggingService.GetBlog(id)
        |> DbCall.Map this.Ok
        |> runner.Run
⚠️ **GitHub.com Fallback** ⚠️