コンテキスト / 質問
私は、データを永続化する必要があり、通常はリポジトリパターンを使用することになった多数の .NET プロジェクトに取り組んできました。コードベースのスケーラビリティを犠牲にすることなく、できるだけ多くの定型コードを削除するための優れた戦略を知っている人はいますか?
継承戦略
リポジトリ コードの多くはボイラー プレートであり、繰り返す必要があるため、通常、基本クラスを作成して、例外処理、ログ記録、トランザクション サポートなどの基本といくつかの基本的な CRUD メソッドをカバーします。
public abstract class BaseRepository<T> where T : IEntity
{
protected void ExecuteQuery(Action query)
{
//Do Transaction Support / Error Handling / Logging
query();
}
//CRUD Methods:
public virtual T GetByID(int id){}
public virtual IEnumerable<T> GetAll(int id){}
public virtual void Add (T Entity){}
public virtual void Update(T Entity){}
public virtual void Delete(T Entity){}
}
したがって、これは単純なドメインの場合にうまく機能し、エンティティごとに DRY リポジトリ クラスをすばやく作成できます。ただし、ドメインがより複雑になると、これは崩壊し始めます。更新を許可しない新しいエンティティが導入されたとしましょう。基本クラスを分割し、Update メソッドを別のクラスに移動できます。
public abstract class BaseRepositorySimple<T> where T : IEntity
{
protected void ExecuteQuery(Action query);
public virtual T GetByID(int id){}
public virtual IEnumerable<T> GetAll(int id){}
public virtual void Add (T entity){}
public void Delete(T entity){}
}
public abstract class BaseRepositoryWithUpdate<T> :
BaseRepositorySimple<T> where T : IEntity
{
public virtual void Update(T entity){}
}
このソリューションはうまく拡張できません。一般的なメソッドを持つエンティティがいくつかあるとします: public virtual void Archive(T entity){}
ただし、アーカイブできる一部のエンティティは更新もできますが、他のエンティティはできません。したがって、私の継承ソリューションは機能しません。このシナリオに対処するには、2 つの新しい基本クラスを作成する必要があります。
構成戦略
Compositon パターンを調査しましたが、これには多くの定型コードが残っているようです。
public class MyEntityRepository : IGetByID<MyEntity>, IArchive<MyEntity>
{
private Archiver<MyEntity> _archiveWrapper;
private GetByIDRetriever<MyEntity> _getByIDWrapper;
public MyEntityRepository()
{
//initialize wrappers (or pull them in
//using Constructor Injection and DI)
}
public MyEntity GetByID(int id)
{
return _getByIDWrapper(id).GetByID(id);
}
public void Archive(MyEntity entity)
{
_archiveWrapper.Archive(entity)'
}
}
MyEntityRepository にボイラープレート コードが読み込まれるようになりました。これを自動的に生成するために使用できるツール/パターンはありますか?
MyEntityRepository を次のようなものに変えることができれば、それははるかに理想的だと思います。
[Implement(Interface=typeof(IGetByID<MyEntity>),
Using = GetByIDRetriever<MyEntity>)]
[Implement(Interface=typeof(IArchive<MyEntity>),
Using = Archiver<MyEntity>)
public class MyEntityRepository
{
public MyEntityRepository()
{
//initialize wrappers (or pull them in
//using Constructor Injection and DI)
}
}
アスペクト指向プログラミング
これには AOP フレームワーク、特にPostSharpとそのComposition Aspectを使用することを検討しましたが、これでうまくいくように見えますが、リポジトリを使用するには Post.Cast<>() を呼び出す必要があります。コードに非常に奇妙な匂い。AOP を使用してコンポジターのボイラープレート コードを取り除くためのより良い方法があるかどうか、誰でも知っていますか?
カスタム コード ジェネレーター
他のすべてが失敗した場合は、ボイラー プレート コードを部分的なコード ファイルに生成できるカスタム コード ジェネレーター Visual Studio プラグインの作成に取り組むことができると思います。これを行うツールはすでにありますか?
[Implement(Interface=typeof(IGetByID<MyEntity>),
Using = GetByIDRetriever<MyEntity>)]
[Implement(Interface=typeof(IArchive<MyEntity>),
Using = Archiver<MyEntity>)
public partial class MyEntityRepository
{
public MyEntityRepository()
{
//initialize wrappers (or pull them in
//using Constructor Injection and DI)
}
}
//Generated Class file
public partial class MyEntityRepository : IGetByID<MyEntity>, IArchive<MyEntity>
{
private Archiver<MyEntity> _archiveWrapper;
private GetByIDRetriever<MyEntity> _getByIDWrapper;
public MyEntity GetByID(int id)
{
return _getByIDWrapper(id).GetByID(id);
}
public void Archive(MyEntity entity)
{
_archiveWrapper.Archive(entity)'
}
}
拡張方法
最初に質問を書いたときにこれを追加するのを忘れていました(申し訳ありません)。また、拡張メソッドを試してみました:
public static class GetByIDExtenions
{
public T GetByID<T>(this IGetByID<T> repository, int id){ }
}
ただし、これには 2 つの問題があります。a) 拡張メソッド クラスの名前空間を覚えてどこにでも追加する必要があることと、b) 拡張メソッドがインターフェイスの依存関係を満たすことができないことです。
public interface IMyEntityRepository : IGetByID<MyEntity>{}
public class MyEntityRepository : IMyEntityRepository{}
更新: T4 テンプレートは可能な解決策でしょうか?