2015-12-10 2 views
5

NinjectのようなものでWebApiを使用するとき、F#でDIを処理する方法を頭に入れようとしています。例えばFを使用しているときに、Api Controllerの新しいメソッドごとにDIコンテナに触れないようにする方法#

、C#で、私は私のコンテナを配線するとき、私は単純に、たとえば、タイプがに解決何DIを言うだろう:コントローラのコンストラクタで必要とされるとき

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

私のAPIコントローラがこれを自動的に配線します。

偉大な今、私はコンテナに再び触れることなく、一日中、クラス&クラスにメソッドを追加できます。

しかし、私はこれを完全に間違っていない限り、部分的なアプリケーションを作成し、コントローラに渡します。メソッドを追加するたびに、コンテナで再度ワイヤリングする必要があります。たぶんこれは正しいですが、私は確信はありませんが、より多くの配線のように思えます。

私が何を意味するかを明確にするために、代表的なREST APIを使用しましょう。 CRUDを持つ各エンティティについて -

カスタマー(作成、読み取り、更新、削除)

私は各機能をコントローラに注入する必要がありますか?

:私はそれをバインドするとき、私は、のような何かをするだろう、私のコンテナで今

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次に、新しい部分アプリケーションを渡すために上のコードを変更する必要がありますか?

これは間違っていると感じます - 私はこれに間違って近づいていますか?

答えて

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コンテナによって提供される自動化により、この問題はあなたから隠されます。

私は不都合が利点を上回っていると思うので、DIコンテナの代わりにPure DIを使用することをお勧めします。

これは、C#、Visual Basic .NET、またはJavaのオブジェクト指向プログラミング向けですが、F#の関数型プログラミングにも等しく適用されます。

機能のないF#コードベースでは、クラスやインターフェイスをまったく使用しません。代わりに、私は一緒に関数を構成したいと思います。

などの混合コードベース。 ASP.NET Web APIでは、できるだけ早くOOPとFPの間の橋渡しをしました。そのコントローラの全体コードベースだ

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

:私は私のTest-Driven Development with F#トーク(expanded material available on Pluralsight)で説明したように、私はこのようなコントローラに機能を注入したいです。すべての動作はimpによって実装されています。アプリケーションの起動コード、imp

このよう構成されている:

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

上記のReservationsControllerが単一Post方法を定義することを主張することができます。コントローラがより多くのメソッドを公開する必要がある場合はどうなりますか?

この場合、メソッドごとに実装機能を挿入します。 REST APIでは、どんなコントローラーでも2-3の方法しかないはずです。実際には2-3の依存関係を意味します。私の意見では、これは完全に受け入れられる数の依存関係です。

  • GET
  • POST
  • POSTGET

  • 2-3メソッドの最大の理由は、適切なRESTfulな設計では、リソースは、いくつかの対話パターンに従う傾向があることです

  • PUT,GET
  • DELETEGET
  • PUTDELETEGET

完全な組合せは(POSTGETPUTDELETE)REST設計臭いであるが、そのすべてが完全に異なる議論です。

+0

DIコンテナの遅延バインディングに関する問題点があります。いずれにしても最初は厄介だと感じましたが、V1を完成させた後ではなく、今はあまりインターフェースがなく、純粋な機能性を持っています。そして最後に面倒さが少なくなりました:-) – schmoopy

+1

その話( "Look No Mocks" 'imp'関数そのものをテストしたことはありませんでした。そうした場合、その関数は(どのように配線したいかによって)3つまたは4つの別のパラメータで終了してしまいます。これは非常に簡単な玩具の例です。最終的には、このような「ルート」関数は、元の質問が実際に行っていたものである、膨大なパラメータ(ツリーの下のすべての依存関係の推移的閉包)で終わります。 –

+0

@FyodorSoikin DIコンテナを使用してC#で上記のようなことをやっていたのですが、同様に単体テストを強く感じることはありませんでした。私の[Outside-In Test-Driven Development](http://bit.ly/outside-in-tdd)Pluralsightコースには、その多くのサンプル(C#)があり、デザインを変更するものではありません。この結論はF#についても当てはまる。ロンドンの* Progressive F#Tutorials 2014 *で、私は上記のimp関数のためにF#で書かれた同じ種類の統合テストを示しましたが、何も変更されませんでした。 –

0

基本的には、オブジェクト指向フレームワークで機能コードスタイルを使用しています。 WebAPIではコントローラをインスタンス化する必要があったため、何らかの形でOOを機能的なアプローチに橋渡しする必要があります。

DIコンテナの関数値を設定するのは、コンストラクタの引数に手動でバインドする必要があるため、やや面倒です。私は、(静的)関数呼び出しをラップするクラスを作成するアダプタパターンベースのアプローチをお勧めします。

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

、まだ

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

と結合する。この方法で、あなたの変更や追加は、あなたのDIバインディングでそのむしろ、CustomerFunctionAdapterです。

関連する問題