2

ハンドラーから生成されたデータをハンドラーに使用させたいという問題があります。

  1. UpdateUserProfileImageCommandHandlerAuthorizeDecorator
  2. UpdateUserProfileImageCommandHandlerUploadDecorator
  3. UpdateUserProfileImageCommandHandler

私の問題は、アーキテクチャとパフォーマンスの両方です。

UpdateUserCommandHandlerAuthorizeDecoratorリポジトリ (エンティティ フレームワーク) を呼び出して、ユーザーを承認します。エンティティを使用および変更し、チェーンに送信する必要がある、これに似た他のデコレータがあります。

UpdateUserCommandHandlerユーザーをデータベースに保存するだけです。現在、別のリポジトリ呼び出しを行ってエンティティを更新する必要がありますが、以前のデコレータからエンティティを操作できた可能性があります。

私の問題は、コマンドがユーザー ID と更新するいくつかのプロパティのみを受け入れることです。Authorize デコレーターからユーザー エンティティを取得する場合、チェーンの上流でそのエンティティを処理するにはどうすればよいですか? Userそのプロパティをコマンドに追加して作業してもよろしいですか?

コード:

public class UpdateUserProfileImageCommand : Command
{
    public UpdateUserProfileImageCommand(Guid id, Stream image)
    {
        this.Id = id;
        this.Image = image;
    }

    public Stream Image { get; set; }

    public Uri ImageUri { get; set; }
}

public class UpdateUserProfileImageCommandHandlerAuthorizeDecorator : ICommandHandler<UpdateUserProfileImageCommand>
{
    public void Handle(UpdateUserProfileImageCommand command)
    {
         // I would like to use this entity in `UpdateUserProfileImageCommandHandlerUploadDecorator`
         var user = userRespository.Find(u => u.UserId == command.Id);

         if(userCanModify(user, currentPrincipal))
         {
             decoratedHandler(command);
         }

    }
}

public class UpdateUserProfileImageCommandHandlerUploadDecorator : ICommandHandler<UpdateUserProfileImageCommand>
{
    public void Handle(UpdateUserProfileImageCommand command)
    {
         // Instead of asking for this from the repository again, I'd like to reuse the entity from the previous decorator
         var user = userRespository.Find(u => u.UserId == command.Id);

         fileService.DeleteFile(user.ProfileImageUri);

         var command.ImageUri = fileService.Upload(generatedUri, command.Image);

         decoratedHandler(command);       

    }
}

public class UpdateUserProfileImageCommandHandler : ICommandHandler<UpdateUserProfileImageCommand>
{
    public void Handle(UpdateUserProfileImageCommand command)
    {
         // Again I'm asking for the user...
         var user = userRespository.Find(u => u.UserId == command.Id);

         user.ProfileImageUri = command.ImageUri;     

         // I actually have this in a PostCommit Decorator.
         unitOfWork.Save();
    }
}
4

3 に答える 3

4

パフォーマンスのためだけに余分なデータを渡そうとしないでください。また、デコレータを使用すると、契約を変更することはできません。代わりに、そのユーザー エンティティをキャッシュできるようにする必要があります。これは通常、リポジトリ実装の責任です。Entity Framework を使用すると、これは実際にはかなり簡単です。呼び出すことができDbSet.Find(id)、EF は最初にキャッシュ内のエンティティを検索します。これにより、データベースへの不要なラウンド トリップが回避されます。私はいつもこれをしています。

Find(key)そのため、EF のメソッドにマップするorGetByIdメソッドをリポジトリに追加するだけでFind(key)完了です。

さらに、私はピートに同意します。デコレーターは、主に分野横断的な問題に使用する必要があります。デコレーターに他のものを追加しても問題ない場合がありますが、コア ビジネス ロジックをハンドラーとそのデコレーターの両方に分割しているようです。ファイルをディスクに書き込むことは、コア ロジックに長くかかります。単一の責任を遵守することについて慎重になるかもしれませんが、単一の責任を複数のクラスに分割しているように思えます。これは、コマンド ハンドラーが大きくなければならないという意味ではありません。Pete が言ったように、おそらくこれをサービスに抽出し、このサービスをハンドラーに注入したいと思うでしょう。

承認の検証は分野横断的な問題であるため、これをデコレーターに含めることは問題ないように思えますが、現在の実装にはいくつかの問題があります。まず第一に、このようにすると、多くの非汎用デコレーターが発生し、多くのメンテナンスにつながります。さらに、ユーザーが許可されていない場合は、実行を黙ってスキップしますが、これは通常、必要なものではありません。

黙ってスキップする代わりに、例外をスローすることを検討し、ユーザーが通常の状況でこの機能を呼び出せないようにします。これは、例外がスローされた場合、コードにバグがあるか、ユーザーがシステムをハッキングしていることを意味します。例外をスローせずに黙ってスキップすると、バグを見つけるのがはるかに難しくなる可能性があります。

もう 1 つの考慮事項は、この承認ロジックを汎用デコレーターとして実装しようとすることです。たとえば、generc 承認デコレータまたは検証デコレータを使用します。これは常に可能であるとは限りませんが、コマンドを属性でマークできる場合があります。たとえば、私が現在取り組んでいるシステムでは、コマンドを次のようにマークします。

[PermittedRole(Role.LabManagement)]

実行中のコマンドの属性をAuthorizationVerifierCommandHandlerDecorator<TCommand>チェックし、現在のユーザーがそのコマンドの実行を許可されているかどうかを確認する があります。

アップデート

これはあなたがどのように見えると私が思うかの例ですUpdateUserProfileImageCommandHandler:

public class UpdateUserProfileImageCommandHandler 
    : ICommandHandler<UpdateUserProfileImageCommand>
{
    private readonly IFileService fileService;

    public UpdateUserProfileImageCommandHandler(IFileService fileService)
    {
        this.fileService = fileService;
    }

    public void Handle(UpdateUserProfileImageCommand command)
    {
         var user = userRespository.GetById(command.Id);

         this.fileService.DeleteFile(user.ProfileImageUri);

         command.ImageUri = this.fileService.Upload(generatedUri, command.Image);

         user.ProfileImageUri = command.ImageUri;     
    }
}
于 2013-11-11T08:08:08.787 に答える
1

そもそもデコレータを介してこれを行うのはなぜですか?

検証

通常のアプローチは、コマンドを送信する前に必要なすべての検証をクライアントに実行させることです。作成/発行/実行されるコマンドは、送信前にすべての (合理的な) 検証を実行する必要があります。一意性など、事前に 100% 検証できないものがあるため、「合理的」を含めます。確かに、コマンドを送信する前に、コマンドを実行するための承認を行うことができます。

コマンド ハンドラの分割

コマンド処理ロジックの一部だけを処理し、コマンド オブジェクトを強化するデコレータを用意するのは、私にはやり過ぎのように思えます。IMHO、デコレーターは、ロギング、トランザクション、または認証などの追加機能を使用して特定の操作を拡張するために使用する必要があります(ただし、コマンドハンドラーの装飾には適用されないと思います)。

画像をアップロードし、データベースに新しい画像 URL を割り当てることは、1 つのコマンド ハンドラーの責任のようです。これら 2 つの異なる操作の詳細を抽象化する必要がある場合は、ハンドラーに抽象化を行うクラス ( など) を挿入しますIUserimageUploader

一般的

通常、コマンドは不変であると見なされ、一度作成されたコマンドは変更しないでください。これは、操作を完了するために必要なすべての情報をコマンドに事前に含める必要があることを強制するのに役立ちます。

于 2013-11-11T03:30:26.547 に答える