F# DIコンテナ
Resultコンピュテーション式を導入
open System
open Microsoft.Extensions.DependencyInjection
open FSharp.Reflection
// Resultコンピュテーション式の定義
type ResultBuilder() =
member _.Return(x) = Ok x
member _.Bind(m, k) = Result.bind k m
member _.ReturnFrom(m) = m
let result = ResultBuilder()
// 依存関係のインターフェースと実装
type IFoo = abstract member DoFoo : unit -> string
type IBar = abstract member DoBar : unit -> string
type IBaz = abstract member DoBaz : unit -> string
type IQux = abstract member DoQux : unit -> string
type FooImpl() = interface IFoo with member _.DoFoo() = "Foo!"
type BarImpl() = interface IBar with member _.DoBar() = "Bar!"
type BazImpl() = interface IBaz with member _.DoBaz() = "Baz!"
type QuxImpl() = interface IQux with member _.DoQux() = "Qux!"
// DI解決のインターフェース
type IDependencyResolver =
abstract Resolve<'a> : unit -> 'a option
abstract ResolveAll<'a> : unit -> 'a seq
type DependencyResolver(sp: IServiceProvider) =
interface IDependencyResolver with
member _.Resolve<'a>() =
match sp.GetService(typeof<'a>) with
| null -> None
| service -> Some(service :?> 'a)
member _.ResolveAll<'a>() =
sp.GetServices(typeof<'a>) |> Seq.cast<'a>
// スコープ管理
type ScopedServices private (sp: IServiceProvider) =
member _.CreateResolver() = DependencyResolver(sp) :> IDependencyResolver
interface IDisposable with
member _.Dispose() = match sp with | :? IDisposable as d -> d.Dispose() | _ -> ()
static member Create(configure: IServiceCollection -> IServiceProvider) =
let services = ServiceCollection()
configure services |> ignore
new ScopedServices(services.BuildServiceProvider())
// 依存関係の解決ヘルパー
let private resolveDependency (resolver: IDependencyResolver) (t: Type) =
let resolveMethod = typedefof<IDependencyResolver>.GetMethod("Resolve").MakeGenericMethod(t)
match resolveMethod.Invoke(resolver, [||]) with
| null -> None
| opt when opt.GetType().IsGenericType && opt.GetType().GetGenericTypeDefinition() = typedefof<obj option> ->
let someValue = opt.GetType().GetProperty("Value").GetValue(opt)
if someValue = null then None else Some someValue
| _ -> None
// inject関数
let inject (resolver: IDependencyResolver) (f: 'tuple -> int -> int -> int -> 'result) =
let funcType = f.GetType()
if not (FSharpType.IsFunction funcType) then
Error(sprintf "Invalid function type: %s is not a function" funcType.FullName)
else
let (domainType, _) = FSharpType.GetFunctionElements(funcType)
if not (FSharpType.IsTuple domainType) then
Error(sprintf "Invalid function domain type: %s is not a tuple" domainType.FullName)
else
let paramTypes = FSharpType.GetTupleElements(domainType)
let resolvedArgs = paramTypes |>
Array.map (resolveDependency resolver)
match resolvedArgs |> Array.tryFind Option.isNone with
| Some _ ->
let missingDeps =
paramTypes
|> Array.mapi (fun i t -> if resolvedArgs.[i].IsNone then Some(string t) else None)
|> Array.choose id
|> Array.toList
Error(sprintf "Failed to resolve dependencies for %A: %s" f (String.concat ", " missingDeps))
| None ->
let tuple = FSharpValue.MakeTuple(resolvedArgs |>
Array.map Option.get, domainType) |> unbox<'tuple>
Ok(fun x y z -> f tuple x y z)
// ビジネスロジック関数(異なる引数と戻り値型)
let processWithThree (deps: IFoo * IBar * IBaz) x y z =
let (foo, bar, baz) = deps
sprintf "%s %s %s (x=%d, y=%d, z=%d)" (foo.DoFoo()) (bar.DoBar()) (baz.DoBaz()) x y z
let processWithFour (deps: IFoo * IBar * IBaz * IQux) x y z =
x y z // intを返す
let processWithTwo (deps: IBar * IQux) x y z =
let (bar, qux) = deps
(bar.DoBar(), qux.DoQux()) // (string * string)を返す
// ビジネスロジック(成功ケースのみ)
let businessLogic p3Fn p4Fn p2Fn x y z =
let p3Result = p3Fn x y z
let p4Result = p4Fn x y z
let p2Result = p2Fn x y z
sprintf "All succeeded - p3: %s, p4: %d (sum), p2: %A" p3Result p4Result p2Result
// DI設定
let configureServices (services: IServiceCollection) useQux =
services.AddTransient<IFoo, FooImpl>() |> ignore
services.AddTransient<IBar, BarImpl>() |> ignore
services.AddTransient<IBaz, BazImpl>() |> ignore
if useQux then services.AddTransient<IQux, QuxImpl>() |> ignore
services.BuildServiceProvider()
[<EntryPoint>]
let main argv =
let useQux = Environment.GetEnvironmentVariable("USE_QUX") = "true"
use services = ScopedServices.Create(fun sc -> configureServices sc useQux)
let resolver = services.CreateResolver()
let result = result {
let! p3 = inject resolver processWithThree
let! p4 = inject resolver processWithFour
let! p2 = inject resolver processWithTwo
return businessLogic p3 p4 p2 42 58 100
}
match result with
| Ok output -> printfn "%s" output
| Error err -> printfn "Business Logic Failed:\n%s" err
0