デコレーターを使用して、アスペクト指向プログラミングをプロジェクトに適用するプロトタイプを構築しようとしています。私のプロジェクトの一部では、(単純な CRUD のために) 一般的なリポジトリを使用しますが、最終的には、コマンドおよびクエリ ハンドラーも組み込む予定です (これらは、ProcessCustomerOrders などの特定のタスクを実行します)。また、ここで例として挙げたい分野横断的な懸念事項は、セキュリティとロギングです。
また、サンプル コードは Decorator パターンを使用したものではなく、コンテキストを提供するためにこのプロトタイプ用に配置したコードの例にすぎないこともわかっています。
Proxy パターンや Code Weaving パターンなど、AOP (またはクロスカッティング コンサーン) を実装する他の方法があることは理解していますが、これらのパターンに精通していないため、それらの間のトレードオフがわかりません。
ここでコンソール アプリを使用しているのは、連鎖的に「新しく」作成した場合にどのように見えるかを示すためだけです。
私の質問は次のとおりです。
(1) Simple Injector (ブートストラップ クラス) を使用してこれを接続し、順序を同じに保つにはどうすればよいですか?
(2) これはデコレータ パターンの適切な使用法ですか (基本抽象またはインターフェイス クラスまたはデコレータ ベースを使用していないため)?
(3) 2 つの異なるバージョンを注入せずに、同じリポジトリで ILogger サービスの複数の実装 (DatabaseLogger と ConsoleLogger など) を利用するクリーンな方法はありますか?
(4) 実際のログ記録は Repository メソッドに実装され、ILogger サービスは Repository クラスに挿入されますが、ロガーを配線して汎用リポジトリを引き続き使用するよりも、これを行うためのより良い方法はありますか?
(5) このプロトタイプでのリポジトリの使用方法に基づいて、プロキシ パターンまたはコード ウィービング パターンを使用する必要がありますか?
また、このデザインに関する一般的な批評も歓迎します。
プロトタイプコード:
public class Program
{
public static void Main(string[] args)
{
var e = new Entity
{
Id = 1,
Name = "Example Entity",
Description = "Used by Decorators",
RowGuild = Guid.NewGuid()
};
Controller controller =
new Controller(
new GenericRepository<Entity>(
new ClientManagementContext(),
new ConsoleLogger()
),
new WebUser()
);
controller.Create(e);
}
}
public static class RepositoryBoostrapper
{
public static void Bootstrap(Container container)
{
container.RegisterOpenGeneric(typeof(IGenericRepository<>), typeof(GenericRepository<>));
}
}
public class Entity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Guid RowGuild { get; set; }
public byte[] RowVersion { get; set; }
}
public class Controller
{
private readonly IGenericRepository<Entity> _repository;
private readonly IUserSecurity _userSecurity;
public Controller(IGenericRepository<Entity> repository, IUserSecurity userSecurity)
{
_repository = repository;
_userSecurity = userSecurity;
}
// Displays all Entities on a web page view
public ActionResult Index() {
IEnumerable<Entity> e = null;
User user = User.Identity.Name;
if (_userSecurity.ValidateUser(user))
{
e = _repository.ReadTs();
}
return View(e);
}
public ActionResult Create(Entity e) {
User user = User.Identity.Name;
if (_userSecurity.ValidateUser(user))
{
if (ModelState.IsValid)
{
_repository.CreateT(e);
return RedirectToAction("Index");
}
}
return View(e);
}
}
public interface IGenericRepository<T>
{
T ReadTById(object id);
IEnumerable<T> ReadTs();
void UpdateT(T entity);
void CreateT(T entity);
void DeleteT(T entity);
}
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private readonly ClientManagementContext _context;
private readonly ILogger _logger;
public GenericRepository(ClientManagementContext context, ILogger logger)
{
_context = context;
_logger = logger;
}
public T ReadTById(object id) {
return _context.Set<T>().Find(id);
}
public IEnumerable<T> ReadTs() {
return _context.Set<T>().AsNoTracking().AsEnumerable();
}
public void UpdateT(T entity) {
var watch = Stopwatch.StartNew();
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
_logger.Log(typeof(T).Name +
" executed in " +
watch.ElapsedMilliseconds + " ms.");
}
public void CreateT(T entity) {
var watch = Stopwatch.StartNew();
_context.Entry(entity).State = EntityState.Added;
_context.SaveChanges();
_logger.Log(typeof(T).Name +
" executed in " +
watch.ElapsedMilliseconds + " ms.");
}
public void DeleteT(T entity) {
_context.Entry(entity).State = EntityState.Deleted;
_context.SaveChanges();
}
}
public class Logger
{
private readonly ILogger _logger;
public Logger(ILogger logger)
{
_logger = logger;
}
public void Log(string message)
{
_logger.Log(message);
}
}
public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class DatabaseLogger : ILogger
{
public void Log(string message)
{
// database logging
}
}
public interface IUserSecurity
{
bool ValidateUser(User user);
}
public class UserSecurity
{
private readonly IUserSecurity _userSecurity;
public UserSecurity(IUserSecurity userSecurity)
{
_userSecurity = userSecurity;
}
public bool ValidateUser(User user)
{
return _userSecurity.ValidateUser(user);
}
}
public class WebUser : IUserSecurity
{
public bool ValidateUser(User user)
{
// validate MVC user
return true;
}
}
UPDATE @Stevenの回答に基づく:
デコレーターとリポジトリーのシンプルなインジェクター DI:
public static class RepositoryBoostrapper
{
public static void Bootstrap(Container container)
{
container.RegisterOpenGeneric(
typeof(IGenericRepository<>),
typeof(GenericRepository<>));
container.RegisterDecorator(
typeof(IGenericRepository<>),
typeof(LoggingRepositoryDecorator<>));
container.RegisterDecorator(
typeof(IGenericRepository<>),
typeof(SecurityRepositoryDecorator<>));
}
}
コントローラーによって呼び出されるデコレーター チェーンの順序は、コントローラー (チェック) > セキュリティ (OK の場合は続行して呼び出しを許可する) > レポ (永続化レイヤーを更新してから) > ログ (何らかの機能へ) > そして戻る必要があります。コントローラーへ。
新しいコントローラ クラス:
public class Controller
{
private readonly IGenericRepository<Entity> securityGenericRepository;
public Controller(
IGenericRepository<Entity> securityGenericRepository)
{
this.securityGenericRepository = securityGenericRepository;
}
// Displays all Entities on a web page view
public bool Index() {
var e = new Entity
{
Id = 1,
Name = "Example Entity",
Description = "Used by Decorators",
RowGuild = Guid.NewGuid()
};
this.securityGenericRepository.CreateT(e);
return false;
}
public ActionResult Create(Entity e) {
if (ModelState.IsValid)
{
this.securityGenericRepository.CreateT(e);
return RedirectToAction("Index");
}
return View(e);
}
}
上記のコードの抜粋に関する質問:
戻り値に基づいてコントローラで何らかのアクションを実行したい場合 (たとえば、Security Decorator から bool を返す)、IGenericRepository インターフェイス (したがって GenericRepository クラス) を変更する必要がありますか? つまり、Repo クラスと Security Decorator クラスはどちらも同じインターフェイスを実装しているため、Security メソッドの戻り値やパラメーターを変更したい場合は、Repository メソッドも変更する必要があるのでしょうか?
また、IGenericRepository の Security 実装を Controller に渡すだけですか?
また、ロガーは次のように変更されました。
public class LoggingRepositoryDecorator<T> : IGenericRepository<T>
{
private readonly IGenericRepository<T> decoratee;
private readonly ILogger logger;
public LoggingRepositoryDecorator(IGenericRepository<T> decoratee, ILogger logger)
{
this.decoratee = decoratee;
this.logger = logger;
}
// ...
public void CreateT(T entity)
{
var watch = Stopwatch.StartNew();
this.decoratee.CreateT(entity);
this.logger.Log(typeof(T).Name + " executed in " +
watch.ElapsedMilliseconds + " ms.");
}
// ...
}
上記では、Decoratee を呼び出して、Decorator の機能を上に追加するだけです。
最後に、セキュリティ デコレータ:
public class SecurityRepositoryDecorator<T> : IGenericRepository<T>
{
private readonly IGenericRepository<T> decoratee;
private readonly IUserSecurity userSecurity;
private User user;
public SecurityRepositoryDecorator(
IGenericRepository<T> decoratee,
IUserSecurity userSecurity)
{
this.decoratee = decoratee;
this.userSecurity = userSecurity;
this.user = User.Identity.Name;
}
// ...
public void CreateT(T entity)
{
if (userSecurity.ValidateUser(user))
this.decoratee.CreateT(entity);
}
// ...
}
上記で理解できないのは、ロガーがどこで/いつ呼び出されるのかということです。
更新 2:
Decorator パターンが正常に動作するようです。すべての素晴らしい答えをくれたスティーブンに感謝します。
プロトタイプの主な機能:
public static void Main(string[] args)
{
var container = new Container();
PrototypeBoostrapper.Bootstrap(container);
IRepository<Entity> repository =
new ValidateUserDecorator<Entity>(
new LoggingDecorator<Entity>(
new Repository<Entity>(
new PrototypeContext()),
new ConsoleLogger()),
new ClaimsPrincipal());
var controller = new Controller(repository);
var e = new Entity
{
Id = 1,
Name = "Example Entity",
Description = "Used by Decorators",
RowGuild = Guid.NewGuid()
};
controller.Create(e);
}
検証 (セキュリティ) デコレーター:
public class ValidateUserDecorator<T> : IRepository<T>
{
private readonly IRepository<T> decoratee;
//private readonly IUserSecurity userSecurity;
private IPrincipal User { get; set; }
public ValidateUserDecorator(
IRepository<T> decoratee,
IPrincipal principal)
{
this.decoratee = decoratee;
User = principal;
}
//..
public void CreateT(T entity)
{
if (!User.IsInRole("ValidRoleToExecute"))
throw new ValidationException();
this.decoratee.CreateT(entity);
}
//..
ロギング デコレータ:
public class LoggingDecorator<T> : IRepository<T>
{
private readonly IRepository<T> decoratee;
private readonly ILogger logger;
public LoggingDecorator(IRepository<T> decoratee, ILogger logger)
{
this.decoratee = decoratee;
this.logger = logger;
}
// ..
public void CreateT(T entity)
{
var watch = Stopwatch.StartNew();
this.decoratee.CreateT(entity);
this.logger.Log(typeof(T).Name + " executed in " +
watch.ElapsedMilliseconds + " ms.");
}
// ..
汎用リポジトリ:
public class Repository<T> : IRepository<T> where T : class
{
private readonly PrototypeContext _context;
public Repository(PrototypeContext context)
{
_context = context;
}
//..
public void CreateT(T entity) {
_context.Entry(entity).State = EntityState.Added;
_context.SaveChanges();
}
//..
コントローラー:
public class Controller
{
private readonly IRepository<Entity> repository;
public Controller(
IRepository<Entity> repository) {
this.repository = repository;
}
// ..
public bool Create(Entity e) {
this.repository.CreateT(e);
return true;
}
// ..