BootstrapperでAutomapperを構成すると、Open-Closed Principleに違反しますか?
-
05-07-2019 - |
質問
BootstrapperでAutomapperを設定していますが、 Application_Start()
で Bootstrap()
を呼び出していますが、これは間違っていると言われています。新しいマッピングを追加するたびに Bootstrapper
クラスを変更するため、Open-Closed Principleに違反しています。
どう思いますか、私は本当にこの原則に違反しますか?
public static class Bootstrapper
{
public static void BootStrap()
{
ModelBinders.Binders.DefaultBinder = new MyModelBinder();
InputBuilder.BootStrap();
ConfigureAutoMapper();
}
public static void ConfigureAutoMapper()
{
Mapper.CreateMap<User, UserDisplay>()
.ForMember(o => o.UserRolesDescription,
opt => opt.ResolveUsing<RoleValueResolver>());
Mapper.CreateMap<Organisation, OrganisationDisplay>();
Mapper.CreateMap<Organisation, OrganisationOpenDisplay>();
Mapper.CreateMap<OrganisationAddress, OrganisationAddressDisplay>();
}
}
解決
単一の責任原則(SRP)とオープン/クローズド原則(OCP)の2つの原則に違反していると主張します。
ブートストラップクラスを変更する理由は複数あるため、SRPに違反しています。モデルバインディングまたは自動マッパー構成を変更した場合。
システムの別のサブコンポーネントを設定するために追加のブートストラップコードを追加すると、OCPに違反することになります。
通常これを処理する方法は、次のインターフェイスを定義することです。
public interface IGlobalConfiguration
{
void Configure();
}
ブートストラップが必要なシステムの各コンポーネントに対して、そのインターフェイスを実装するクラスを作成します。
public class AutoMapperGlobalConfiguration : IGlobalConfiguration
{
private readonly IConfiguration configuration;
public AutoMapperGlobalConfiguration(IConfiguration configuration)
{
this.configuration = configuration;
}
public void Configure()
{
// Add AutoMapper configuration here.
}
}
public class ModelBindersGlobalConfiguration : IGlobalConfiguration
{
private readonly ModelBinderDictionary binders;
public ModelBindersGlobalConfiguration(ModelBinderDictionary binders)
{
this.binders = binders;
}
public void Configure()
{
// Add model binding configuration here.
}
}
Ninjectを使用して依存関係を注入します。 IConfiguration
は、静的な AutoMapper
クラスの基礎となる実装であり、 ModelBinderDictionary
は ModelBinders.Binder
オブジェクトです。次に、 IGlobalConfiguration
インターフェースを実装するクラスの指定されたアセンブリをスキャンし、それらのクラスをコンポジットに追加する NinjectModule
を定義します。
public class GlobalConfigurationModule : NinjectModule
{
private readonly Assembly assembly;
public GlobalConfigurationModule()
: this(Assembly.GetExecutingAssembly()) { }
public GlobalConfigurationModule(Assembly assembly)
{
this.assembly = assembly;
}
public override void Load()
{
GlobalConfigurationComposite composite =
new GlobalConfigurationComposite();
IEnumerable<Type> types =
assembly.GetExportedTypes().GetTypeOf<IGlobalConfiguration>()
.SkipAnyTypeOf<IComposite<IGlobalConfiguration>>();
foreach (var type in types)
{
IGlobalConfiguration configuration =
(IGlobalConfiguration)Kernel.Get(type);
composite.Add(configuration);
}
Bind<IGlobalConfiguration>().ToConstant(composite);
}
}
次に、Global.asaxファイルに次のコードを追加します。
public class MvcApplication : HttpApplication
{
public void Application_Start()
{
IKernel kernel = new StandardKernel(
new AutoMapperModule(),
new MvcModule(),
new GlobalConfigurationModule()
);
Kernel.Get<IGlobalConfiguration>().Configure();
}
}
現在、ブートストラップコードはSRPとOCPの両方に準拠しています。 IGlobalConfiguration
インターフェースを実装するクラスを作成することにより、ブートストラップコードを簡単に追加できます。また、グローバル構成クラスには変更する理由が1つしかありません。
他のヒント
完全に閉じるには、マッピング登録ごとに静的イニシャライザーを使用できますが、それはやり過ぎです。
実際には、リバースエンジニアリングを可能にするという観点から、ある程度集中化しておくと便利です。
NInjectでは、プロジェクトまたはサブシステム(プロジェクトのセット)ごとに Module
を持つという概念がありますが、これは賢明な妥協のようです。
これは古いものですが、 Bootstrapperというオープンソースライブラリを作成したことを知りたいと思うかもしれません。 この問題を正確に処理します。あなたはそれをチェックアウトしたいかもしれません。 OCの原則を破らないようにするには、IMapCreaterを実装する別のクラスでマッパーを定義する必要があります。 Boostrapperはリフレクションを使用してこれらのクラスを見つけ、起動時にすべてのマッパーを初期化します
クラスに変更する理由が複数あるという点で、違反しているという単一の責任原則がある場合。
個人的には、AutoMapperのすべての構成が行われたConfigureAutoMapperクラスがあります。しかし、それは個人的な選択にかかっていると主張することができます。
Omu、アプリのスタートアップルーチンでIoCコンテナーをブートストラップする場合、同様の質問に取り組んでいます。 IoCの場合、変更を追加する際にアプリ全体に設定を振りかけるのではなく、設定を一元化することの利点を説明しました。 AutoMapperを構成する場合、集中化の利点はそれほど重要ではないと思います。 AutoMapperコンテナーをIoCコンテナーまたはService Locatorに取得できる場合、アセンブリごと、静的コンストラクター、または分散型でマッピングを1回構成するというRuben Bartelinkの提案に同意します。
基本的には、ブートストラップを集中化するか分散化するかを決定する問題だと思います。スタートアップルーチンのオープン/クローズドプリンシパルについて懸念がある場合は、分散化を行ってください。ただし、OCPへの準拠は、1か所で行われるすべてのブートストラップの価値と引き換えにダイヤルダウンできます。別のオプションは、AutoMapperにそのような概念があると仮定して、ブートストラッパーにレジストリの特定のアセンブリをスキャンさせることです。