یکی از مشکلاتی که در برخی از طراحیهایی که تا کنون دیدهام وجود دارد، عدم استفاده از قابلیت ماژولار نویسی تنظیمات Autofac و عدم استفاده از Interfaceها برای ارتباط بین قسمتهای مختلف سیستم است. به این صورت که تمام تنظیمات مربوط به Autofac را در بالاترین لایه سمت سرور خود یعنی Service یا Web انجام میدهند که باعث میشود این لایه به تمامی لایههای پایین خود از جمله DataAccess دسترسی مستقیم داشته باشد. در یک سیستم بزرگ به دلایل بسیار از جمله حساسیت دادهها، مدیریت درست بر روی قسمتهای مختلف و یکبار نویسی هر قسمت، بهتر است تمام تغییرات از فیلتر Business و بصورت مدیریت شده بر روی دادههای ما صورت بپذیرد. در این نوع سیستم ما نمیتوانیم دسترسی مستقیمی را به لایه DataAccess به تمام توسعه دهندگان بدهیم و امیدوار باشیم که کسی از آن استفاده نکند. برای رفع این مشکل میتوانیم تنظیمات قسمت Autofac را در هر لایه بصورت جداگانه انجام دهیم. به اینصورت که لایههای پایینتر اطلاعات خود را تنها در اختیار لایههایی که باید به آنها دسترسی داشته باشند، قرار میدهند و در نهایت با تجمیع این اطلاعات در بالاترین لایه میتوانیم بصورت کنترل شدهی از آنها استفاده کنیم. دسترسی هر قسمت نیز تنها میتواند از طریق Interface هایی که در اختیار سایر قسمتها گذاشته میشود صورت بپذیرد. به این صورت میتوان از طریق Interfaceها دسترسیهای کنترل شدهای را در اختیار سایر قسمتهایی که بصورت مستقیم یا غیر مستقیم به لایه مربوطه دسترسی دارند، قرار دهیم. در اینجا نکته دیگری را که باید در نظر بگیرید این است که هدف اصلی DI، حذف وابستگیهای کامل و تبدیل آنها به وابستگیهای محدود است و این عمل از طریق Interfaceها صورت میپذیرد. پس قاعدتا نباید بی دلیل بصورت مستقیم از کلاسها استفاده شود، یا آنها را در اختیار سایر لایهها قرار بدهیم. تمام ارتباطات میتوانند از طریق Interfaceها بصورت کاملا کنترل شده انجام بپذیرند.
طراحی لایه DataAccess
در این نوع طراحی، هر لایه تنها از طریق Interfaceها به لایه بالاتر از خودش سرویس میدهد و در مواردی که ما در نظر بگیریم، میتوانیم به لایههای دیگر نیز دسترسیهای غیر مستقیم و کنترل شدهای را بدهیم. بطور مثال هر کلاس Repository میتواند بهصورت Internal تعریف شود؛ پس تنها در لایه DataAccess در دسترس است. برای دسترسی سایر لایهها به Repositoryها، هر Repository میتواند از یک IRepository (این Interface دسترسی خواندنی نوشتنی به کلاس Repository دارد) که در لایه DataAccess بصورت public تعریف شده و تنها در لایه Business قابل دسترس است و یک IRepositoryReadOnly (این Interface دسترسی فقط خواندنی به کلاس Repository دارد) که در قسمت Common تعریف شده، ارث ببرد. به این صورت همان طور که در شکل بالا نیز میبینید، دسترسیهایی که از بیرون به لایه DataAccess صورت میگیرد، به دو قسمت تقسیم میشوند: اول دسترسی کامل به IRepository که تنها از طریق Business صورت میپذیرد و دوم دسترسی از طریق IRepositoryReadOnly که میتواند از هر جایی در سیستم که به قسمت Common دسترسی دارد صورت بپذیرد (البته استفاده از IRepositoryReadOnly به سیاستهایی که شما در نظر میگیرید بستگی دارد). با این کار مطمئن میشوید که تغییرات تنها میتوانند از طریق Business صورت بپذیرند. همچنین حتی در صورتیکه نیاز بدانید، یک دسترسی فقط خواندنی را نیز به توسعه دهندگان سایر قسمتها دادهاید.
حال با توجه به توضیحات فوق، تنظیمات مربوط به Autofac را در لایه DataAccess انجام میدهیم.
طراحی تنظیمات Autofac در لایه DataAccess public class DataAccessSetupDependency : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<EFContext>().As<IDbContext>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(x => x.Namespace.EndsWith("Repositories"))
.AsImplementedInterfaces();
}
}
همانطور که میبینید در این قسمت تنها تنظیمات وابستگیهای لایه DataAccess در ماژول DataAccessSetupDependency انجام شدهاست.
طراحی لایه Business
در لایه Business نیز دسترسیهای خود را تنها از طریق Interface هایی که کلاسهای Business از آنها ارث بردهاند و آنها را پیاده سازی کردهاند، به قسمت Web میدهید.
طراحی تنظیمات Autofac در لایه Business public class BusinessSetupDependency : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterAssemblyModules(typeof(SqlServerDataAccess.SetupDependencies.DataAccessSetupDependency).Assembly);
//builder.RegisterAssemblyModules(typeof(CassandraDataAccess.SetupDependecies.SetupDependency).Assembly);
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(x => x.Namespace.EndsWith("Business.Core"))
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(x => x.Namespace.EndsWith("Business.Businesses"))
.AsImplementedInterfaces();
}
}
همانطور که میبینید در ماژول BusinessSetupDependency ابتدا تنظیمات وابستگیهای موجود در لایه DataAccess بدست آمده و در ادامه سایر تنظیمات موجود در لایه Business انجام شدهاند.
طراحی لایه Web
جمع بندی تمام تنظیمات Autofac در لایه Web public class WebSetupDependency : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterAssemblyModules(typeof(Business.SetupDependencies.BusinessSetupDependency).Assembly);
builder.RegisterControllers(Assembly.GetExecutingAssembly());
//سایر تنظیمات
var container = builder.Build();
CommonDependencyResolver.SetContainer(container);
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
public class AutofacConfig
{
public static void SetupContainer()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly());
var container = builder.Build();
CommonDependencyResolver.SetContainer(container);
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
ماژول WebSetupDependency در واقع جمع بندی از تمام وابستگیهای لایههای موجود در سیستم است. بصورتیکه ابتدا تنظیمات وابستگیهای لایه Business که خود شامل تنظیمات وابستگیهای لایه DataAccess نیز هست، بدست میآیند و سپس لایه Web تنظیمات مرتبط با خودش را بر روی آنها اعمال میکند.
همچنین کلاس AutofacConfig مسئول جمع بندی تمام ماژولها و ایجاد Container آنهاست و سپس این Container را در DependencyResolver ثبت میکند. نکتهای را که باید در اینجا در نظر بگیرید، CommonDependencyResolver است که مسئول ثبت Container در قسمت Common است. به این صورت دیگر تنها لایهای که به Container دسترسی دارد، لایه Web نیست. در واقع با ثبت Container در قسمت Common شما دسترسی کنترل شدهای را از Container به سایر لایههای سیستم دادهاید و در این حالت در صورتیکه لایههای دیگر مانند DataAccess یا Business به Container نیاز پیدا کنند، میتوانند از طریق CommonDependencyResolver این دسترسی را داشته باشند.
جمع بندی
با طراحی ماژولار تنظیمات Autofac و استفاده از Interfaceها برای ارتباط با دیگر قسمتها، دیگر نیازی نیست دسترسیهای بیموردی از یک لایه را به سایر قسمتها داد و دیگر نیازی نیست شما نگران این باشید که ممکن است یکی از توسعه دهندگان بهدلایلی مانند کم تجربگی، کاری را خارج از زیرساختی که شما یا گروه پیاده سازی زیرساخت، پیاده سازی کردهاید انجام دهد. همه چیز آنطور که شما میخواهید و برنامه ریزی کردهاید، انجام میشود.