デコレーターを同じジェネリック型の別の依存関係に登録する

何が起こっているのかを確認するために、コード ベースを調査する必要がありました。これを Simple Injector の実装における不具合と呼ぶかもしれませんが、IMO は公正なトレードオフです。 Simple Injector のデコレーター サブシステムは、オープン ジェネリック型とオープン ジェネリック デコレーターを操作するという考えに基づいています。デコレーターの登録時に行うチェックは、デコレーターのコンストラクターにデコ対象が 1 つしかないかどうかを確認することです。このチェックは、デコレータを適用する必要があるオープンなジェネリック抽象化を使用して行われます。あなたの場合 ICommandHandler<T> .その時点では、一般的な ICommandHandler<T> のみ 2 つのコンストラクタ パラメータがこの型に一致します。

これらの事前条件チェックを改善することは可能ですが、これは実際には非常に厄介であり、この機能の有用性は非常に限られています。非汎用デコレータにのみ役立つため、制限があります。たとえば、次のデコレータを見てください:

public class GenericDecorator<TCommand> : ICommandHandler<TCommand> {
    public GenericDecorator(
        ICommandHandler<TCommand> decoratee,
        ICommandHandler<LoggingCommand> dependency)
    {
    }
}

このデコレータは汎用的で、どのデコレータにも適用できるため、はるかに便利です。しかし、ICommandHandler<LoggingCommand> を解決するとどうなるか ?これにより循環依存グラフが発生し、Simple Injector は (明らかに) そのグラフを作成できず、例外をスローします。その場合、デコレータは 2 つの ICommandHandler<LoggingCommand> を持つため、スローする必要があります。 引数。最初は装飾者になり、あなたの Logger が注入されます 、2番目は通常の依存関係になり、 GenericDecorator<LoggingCommand> が注入されます もちろん、これは再帰的です。

したがって、問題はあなたの設計にあると私は主張します。一般に、他のコマンド ハンドラーからコマンド ハンドラーを作成することはお勧めしません。 ICommandHandler<T> プレゼンテーション層がビジネス層と通信する方法を定義する、ビジネス層の上にある抽象化である必要があります。ビジネス層が内部で使用するメカニズムではありません。これをやり始めると、依存関係の構成が非常に複雑になります。 DeadlockRetryCommandHandlerDecorator<T> を使用したグラフの例を次に示します。 そして TransactionCommandHandlerDecorator<T> :

new DeadlockRetryCommandHandlerDecorator<MessageCommand>(
    new TransactionCommandHandlerDecorator<MessageCommand>(
        new MessageSender()))

この場合、DeadlockRetryCommandHandlerDecorator<T> そして TransactionCommandHandlerDecorator<T> MessageSender に適用されます コマンド ハンドラー。 MessageLogger を適用するとどうなるか見てみましょう デコレータも:

new DeadlockRetryCommandHandlerDecorator<MessageCommand>(
    new TransactionCommandHandlerDecorator<MessageCommand>(
        new MessageLogger(
            new MessageSender(),
            new DeadlockRetryCommandHandlerDecorator<MessageLogger>(
                new TransactionCommandHandlerDecorator<MessageLogger>(
                    new Logger())))))

2 番目の DeadlockRetryCommandHandlerDecorator<T> があることに注意してください そして2番目の TransactionCommandHandlerDecorator<T> オブジェクトグラフで。トランザクション内にトランザクションがあり、(トランザクション内で) ネストされたデッドロックが再試行されるとはどういう意味ですか?これにより、アプリケーションで深刻な信頼性の問題が発生する可能性があります (データベースのデッドロックにより、操作がトランザクションのない接続で続行されるため)。

ネストされている場合に正しく機能するように、ネストされていることを検出できるようにデコレーターを作成することは可能ですが、これにより、デコレーターの実装がはるかに難しく、脆弱になります。 IMO 時間の無駄です。

したがって、コマンド ハンドラーをネストする代わりに、コマンド ハンドラーとコマンド ハンドラー デコレーターを他の抽象化に依存させます。あなたの場合、デコレータに ILogger を使用させて変更することで、問題を簡単に修正できます ある種のインターフェース:

public class MessageLogger : ICommandHandler<MessageCommand> {
    private ICommandHandler<MessageCommand> innerHandler;
    private ILogger logger;

    public MessageLogger(
        ICommandHandler<MessageCommand> innerHandler, ILogger logger) {
        this.innerHandler = innerHandler;
        this.logger = logger;
    }

    public void Execute(MessageCommand command) {
        innerHandler.Execute(command);

        logger.Log(command.Message);
    }
}

あなたはまだ ICommandHandler<LogCommand> を持つことができます プレゼンテーション層が直接ログを記録する必要がある場合の実装ですが、その場合、その実装はその ILogger に依存するだけです。 同様に:

public class LogCommandHandler : ICommandHandler<LogCommand> {
    private ILogger logger;

    public LogCommandHandler(ILogger logger) {
        this.logger = logger;
    }

    public void Execute(LogCommand command) {
        logger(string.Format("Message \"{0}\" sent at {1}",
            command.LogMessage, DateTime.Now));
    }
}

これは、どちらの方法でも議論できるエッジ ケースですが、実際には、Simple Injector はあなたがしようとしていることを明示的にサポートしていません。

デコレーターは通常、特定の抽象化のすべて (または一部) に共通のロジックを適用する必要があります。例では ICommandHandler です。 .つまり MessageLogger ICommandHandler を装飾するように設計されています のとそのまま ICommandHandler のデコレータです は ICommandHandler を 1 つだけ取ることができます そのコンストラクタで。さらに、このようなものを許可するには、クリーンな設計で回避するのが最善の恐ろしい循環チェックの連なりが必要になります!

そのため、通常は、デコレータがデコレートしている型と同じインターフェイス (およびジェネリック パラメータ) を持つデコレータを定義します

public class MessageLogger<TCommand> : ICommandHandler<TCommand>
    where TCommand : <some criteria e.g. MessageCommand>
{
    //....
}

あなたの問題を軽減するために私が考えることができる最初の解決策は、メディエーターを作成して直接的な依存関係を取り除くことです:

public class LoggerMediator
{
    private readonly ICommandHandler<LogCommand> logger;

    public LoggerMediator(ICommandHandler<LogCommand> logger)
    {
        this.logger = logger;
    }

    public void Execute(LogCommand command)
    {
        this.logger.Execute(command);
    }
}

MessageLogger を変更してください メディエーターを使用します。

public class MessageLogger<TCommand> : ICommandHandler<TCommand>
    where TCommand : MessageCommand
{
    private ICommandHandler<TCommand> innerHandler;
    private LoggerMediator logger;

    public MessageLogger(
        ICommandHandler<TCommand> innerHandler,
        LoggerMediator logger)
    {
        this.innerHandler = innerHandler;
        this.logger = logger;
    }

    public void Execute(TCommand command)
    {
        innerHandler.Execute(command);

        var logCommand = new LogCommand
        {
            LogMessage = command.Message,
            Time = DateTime.Now
        };
        logger.Execute(logCommand);
    }
}

ところで、このように登録を簡素化できます

var container = new Container();
container.RegisterManyForOpenGeneric(
    typeof(ICommandHandler<>), 
    typeof(ICommandHandler<>).Assembly);
container.Register<LoggerMediator>();
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(MessageLogger<>));
container.Verify();

更新

ここで私のコード ベースを調べてみると、同様の要件があり、1 つの追加クラス (汎用コマンド メディエーター) で解決したことがわかりました。

public class CommandHandlerMediator<TCommand>
{
    private readonly ICommandHandler<TCommand> handler;

    public CommandHandlerMediator(ICommandHandler<TCommand> handler)
    {
        this.handler = handler;
    }

    public void Execute(TCommand command)
    {
        this.handler.Execute(command);
    }
}

このように登録:

container.RegisterOpenGeneric(
    typeof(CommandHandlerMediator<>), 
    typeof(CommandHandlerMediator<>));

次のように参照されます:

public class MessageLogger<TCommand> : ICommandHandler<TCommand>
    where TCommand : <some criteria e.g. MessageCommand>
{
    private ICommandHandler<TCommand> decorated;
    private CommandHandlerMediator<LogCommand> logger;

    public MessageLogger(
        ICommandHandler<TCommand> decorated,
        CommandHandlerMediator<LogCommand> logger)
    {
        this.innerHandler = innerHandler;
        this.logger = logger;
    }

    //....

}

1 つの新しいクラスで、すべてのハンドラーがソートされます。