2013-02-12 5 views
21

私は怪物のWCFサービスをより管理しやすいものにリファクタリングしようとしています。 執筆時点では、コンストラクタを介して約9件の依存関係があり、ユニットテストは非常に困難です。"手続き型" WCFサービスのリファクタリング

サービスはステートマシンを介してローカルステートを処理しており、パラメータの検証を行い、フォルト例外をスローし、実際の操作を実行し、パブリケーション/サブチャネル経由でパブリケーションイベントを発生させます。このコードは、他のすべてのサービスコールと非常によく似ています。

おそらくAspect-Oriented ProgrammingまたはWCFの振る舞いを介して、私はこれらのもの(引数の検証、pub/sub通知)を別々に行うことができますが、一般的なアプローチは間違っていると私に伝えています。 "

私の目標は、実際の操作の実行をpub/sub通知やおそらくエラー処理などから切り離すことです。

DDDCQRSなどの頭文字語やその他の技術がここに役立つのだろうかと思いますか?私は残念ながら、定義を超えた概念にはそれほど精通していません。

ここではそのようなWCF操作の(単純化)の例です:

public void DoSomething(DoSomethingData data) 
{ 
    if (!_stateMachine.CanFire(MyEvents.StartProcessing)) 
    { 
     throw new FaultException(...); 
    } 

    if (!ValidateArgument(data)) 
    { 
     throw new FaultException(...); 
    } 

    var transitionResult = 
     _stateMachine.Fire(MyEvents.StartProcessing); 

    if (!transitionResult.Accepted) 
    { 
     throw new FaultException(...); 
    } 

    try 
    { 
     // does the actual something 
     DoSomethingInternal(data); 

     _publicationChannel.StatusUpdate(new Info 
     { 
      Status = transitionResult.NewState 
     }); 
    } 
    catch (FaultException<MyError> faultException) 
    { 
     if (faultException.Detail.ErrorType == 
      MyErrorTypes.EngineIsOffline) 
     { 
      TryFireEvent(MyServiceEvent.Error, 
       faultException.Detail); 
     } 
     throw; 
    } 
} 

答えて

43

あなたが変装したコマンドの素晴らしい例がある持っているもの。あなたがここでやっていることについてニースは、あなたのサービスメソッドがすでに単一の引数をとっているということです。DoSomethingData。これはです。コマンドメッセージはです。あなたがここに欠けているもの

は、コマンドハンドラを超える一般的な抽象化である:

public interface ICommandHandler<TCommand> 
{ 
    void Handle(TCommand command); 
} 

リファクタリングの少しで、あなたのサービスメソッドは、次のようになります。

// Vanilla dependency. 
ICommandHandler<DoSomethingData> doSomethingHandler; 

public void DoSomething(DoSomethingData data) 
{ 
    this.doSomethingHandler.Handle(data); 
} 

そしてもちろん、 ICommandHandler<DoSomethingData>の実装が必要です。あなたのケースでは、次のようになります。

public class DoSomethingHandler : ICommandHandler<DoSomethingData> 
{ 
    public void Handle(DoSomethingData command) 
    { 
     // does the actual something 
     DoSomethingInternal(command); 
    } 
} 

今、あなたは何を引数の検証、缶火災、出版・チャネル・ステータスの更新とエラー処理のように実装これらの横断的関心事について、不思議に思われるかもしれません。まあ、ええ、彼らはすべてのクロスカッティングの懸念、WCFサービスクラスとビジネスロジック(DoSomethingHandler)の両方を心配するべきではありません。

アスペクト指向プログラミングを適用する方法はいくつかあります。ポストシャープのようなコード製織ツールを使うのが好きな人もいます。これらのツールの欠点は、ユニットテストをもっと難しくすることです。クロスカッティングの問題がすべて織り込まれているためです。

2番目の方法は、インターセプトを使用することです。動的プロキシ生成といくつかの反映を使用します。しかし、私がもっと好きなバリエーションがあります。それはデコレータを適用することです。これについての良い点は、これが私の経験ではクロスカット問題を適用する最もクリーンな方法であるということです。

のは、あなたの検証のためにデコレータを見てみましょう:

public class WcfValidationCommandHandlerDecorator<T> : ICommandHandler<T> 
{ 
    private IValidator<T> validator; 
    private ICommandHandler<T> wrapped; 

    public ValidationCommandHandlerDecorator(IValidator<T> validator, 
     ICommandHandler<T> wrapped) 
    { 
     this.validator = validator; 
     this.wrapped = wrapped; 
    } 

    public void Handle(T command) 
    { 
     if (!this.validator.ValidateArgument(command)) 
     { 
      throw new FaultException(...); 
     } 

     // Command is valid. Let's call the real handler. 
     this.wrapped.Handle(command); 
    } 
} 

このWcfValidationCommandHandlerDecorator<T>がジェネリック型であるので、我々は、すべてのコマンドハンドラのまわりでそれをラップすることができます。例えば:

var handler = new WcfValidationCommandHandlerDecorator<DoSomethingData>(
    new DoSomethingHandler(), 
    new DoSomethingValidator()); 

そして、あなたはどのように簡単にスローされた例外を処理するデコレータを作成することができます。

public class WcfExceptionHandlerCommandHandlerDecorator<T> : ICommandHandler<T> 
{ 
    private ICommandHandler<T> wrapped; 

    public ValidationCommandHandlerDecorator(ICommandHandler<T> wrapped) 
    { 
     this.wrapped = wrapped; 
    } 

    public void Handle(T command) 
    { 
     try 
     { 
      // does the actual something 
      this.wrapped.Handle(command); 

      _publicationChannel.StatusUpdate(new Info 
      { 
       Status = transitionResult.NewState 
      }); 
     } 
     catch (FaultException<MyError> faultException) 
     { 
      if (faultException.Detail.ErrorType == MyErrorTypes.EngineIsOffline) 
      { 
       TryFireEvent(MyServiceEvent.Error, faultException.Detail); 
      } 

      throw; 
     } 
    } 
} 

は、私はちょうどこのデコレータでコードを包んどのように見ましたか?私たちは、再び元をラップするために、このデコレータを使用することができます。もちろん

var handler = 
    new WcfValidationCommandHandlerDecorator<DoSomethingData>(
     new WcfExceptionHandlerCommandHandlerDecorator<DoSomethingData>(
      new DoSomethingHandler()), 
    new DoSomethingValidator()); 

このすべては、コードの非常に多くのように思えるし、あなたが持っているすべてはイエスよりも1つのWCFサービスの方法であれば、これはおそらく過剰です。しかし、もしあなたが十数人いるなら、それは本当に面白いものになり始めます。何百というものがあれば?あなたがこのようなテクニックを使用していないなら、私はコードベースを維持している開発者になりたくありません。

リファクタリングの数分後に、ICommandHandler<TCommand>インターフェイスに依存するWCFサービスクラスになります。すべてのクロスカッティングの問題はデコレータに配置され、もちろんDIライブラリによってすべてが結び付けられます。私は、あなたが知っていると思う少数;-)すべてのあなたのWCFサービスクラスはうんざり同じように見えるが開始されますので、あなたが、改善できる一つのことは、おそらく存在し、これを行って

// Vanilla dependency. 
ICommandHandler<FooData> handler; 

public void Foo(FooData data) 
{ 
    this.handler.Handle(data); 
} 

それがします新しいコマンドと新しいハンドラを書くために退屈し始めます。維持するWCFサービスはまだあります。

があなたの代わりに何ができるか、このように、単一の方法で単一のクラスとWCFサービスを作成することです:

[ServiceKnownType("GetKnownTypes")] 
public class CommandService 
{ 
    [OperationContract] 
    public void Execute(object command) 
    { 
     Type commandHandlerType = typeof(ICommandHandler<>) 
      .MakeGenericType(command.GetType()); 

     dynamic commandHandler = Bootstrapper.GetInstance(commandHandlerType); 

     commandHandler.Handle((dynamic)command); 
    } 

    public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider) 
    { 
     // create and return a list of all command types 
     // dynamically using reflection that this service 
     // must accept. 
    } 
} 

を今、あなたが持っているすべてを単一の方法でWCFサービスは、その変更決して。 ServiceKnownTypeAttributeGetKnownTypesを指します。 WCFは起動時にこのメソッドを呼び出して、どの型を受け入れる必要があるかを確認します。アプリケーションのメタデータに基づいてリストを返すと、WCFサービスの1行を変更することなく、システムにコマンドを追加したり削除したりすることができます。

新しいWCF固有のデコレータをしばらく追加することがありますが、通常はWCFサービスに配置する必要があります。おそらく、他のデコレータはより一般的なものであり、ビジネスレイヤ自体に配置される可能性があります。 MVCアプリケーションで再利用される可能性があります。

あなたの質問はCQRSに関するものでしたが、私の答えはそれとは関係ありません。まあ...何も過言ではありません。 CQRSはこのパターンを広範囲に使用しますが、CQRSは一歩進んでいます。 CQRSは、コマンドをキューに入れ、それらを非同期に処理させるコラボレーティブドメインに関するものです。私の答えは、他の手では、ちょうどSOLIDデザイン原則を適用することです。固体はどこでも良いです。コラボレーションドメインだけでなく、

この詳細については、command handlersの適用に関する記事をお読みください。その後、my article about applying this principle to WCF servicesを読んでください。私の答えはそれらの記事の要約です。

幸運。

+1

すごい、スティーブン、あなたの信じられないほど詳細な答えをありがとう!私は 'ICommand/ICommandHandler'パターンを実装することを考えました。これを処理するのが"慣用的な "方法であることを確認したかっただけです。私はあなたが私のことを安心してくれたことをうれしく思っています:) –

+0

これはどういうわけか、状態マシンのクエリをカプセル化する必要があるかどうかという疑問を残します。しかし、現時点では、サービス自体に残しておきます。 –

+0

私は本当にそれについてコメントすることはできません。私はあなたのこの状態マシンが何であるか分かりません。しかし、私はデコレータでカプセル化することができると確信しています。さらに、このパターンのハングアップを取得すると、コードにできるすべての新しい改善が見られるようになります。あなたの新しい人生にようこそ;-) – Steven

関連する問題