معماری Clean Architecture در ASP.NET Core؛ ساختاردهی لایهها در پروژههای مدرن .NET
اقبالدار: «به شخصه، علاقه زیادی به معماری Clean داشته و بیشتر پروژه ها را براساس این معماری پیش می برم.»
دقت بفرمائید که این معماری براساس ASP.NET CORE MVC بنا شده است. هرچند که استفاده از این معماری محدود به هیچ زبان برنامه نویسی نخواهد بود.
با تکیه بر یک نمونهی واقعی از ساختار پروژه، نحوهی پیادهسازی Clean Architecture را در ASP.NET Core MVC بررسی کنیم. این پروژه شامل شش لایهی مستقل است:
- Endpoint.Site (لایهی UI یا Web)
- FestivalsHome.Application
- FestivalsHome.Domain
- FestivalsHome.Persistence
- FestivalsHome.Infrastructure
- FestivalsHome.Common
هدف Clean Architecture چیست؟
ایدهی اصلی Clean Architecture توسط Robert C. Martin (عمو باب) مطرح شد. او معتقد است که نرمافزار باید به تغییرات مقاوم و در عین حال به توسعه باز باشد.
در این معماری، وابستگیها همیشه از بیرون به درون جریان دارند. یعنی لایههای خارجی (مانند UI یا پایگاه داده) به لایههای درونی (Business Rules و Domain) وابستهاند، نه برعکس.
به بیان سادهتر:
«فریمورکها باید قابل تعویض باشند، نه مغز سیستم شما.»
ساختار کلی پروژه
در پروژهی نمونهی ما، ساختار پوشهها و وابستگیها به شکل زیر است:
Endpoint.Site
│ ├── Controllers
│ ├── Views
│ ├── wwwroot
│ └── Program.cs
│
├── FestivalsHome.Application
│ ├── Interfaces
│ ├── Services
│ └── DTOs
│
├── FestivalsHome.Domain
│ ├── Entities
│ └── ValueObjects
│
├── FestivalsHome.Persistence
│ ├── Context
│ ├── Configurations
│ └── Repositories
│
├── FestivalsHome.Infrastructure
│ ├── Adapters
│ ├── Mappers
│ └── Utilities
│
└── FestivalsHome.Common
├── Enums
├── Constants
└── Extensions
۱. لایهی Endpoint (Site)
این لایه همان پروژهی ASP.NET Core MVC شماست.
تمام کنترلرها، ویوها و APIها در این بخش قرار دارند.
تنها وابستگی مستقیم این لایه باید به لایهی Application باشد، زیرا همهی سرویسها و منطقهای کاری از آنجا در اختیار رابط کاربری قرار میگیرند.
در فایل csproj مربوط به Endpoint داریم:
در اینجا، Endpoint فقط سرویسها را از Application مصرف میکند، و برای ثبت وابستگیها از Dependency Injection استفاده میشود.
۲. لایهی Application
مغز اصلی منطق تجاری پروژه در اینجا قرار دارد.
در این بخش سرویسها و اینترفیسهای موردنیاز تعریف میشوند؛ بدون هیچ وابستگی به Entity Framework یا زیرساخت خاصی.
نمونهی تعریف IDataBaseContext در این لایه:
namespace FestivalsHome.Application.Interfaces.Contexts
{
public interface IDataBaseContext : IDisposable
{
DatabaseFacade Database { get; }
// فایلها
// جشنوارهها
// کاربران و ادمینها
int SaveChanges(bool acceptAllChangesOnSuccess);
int SaveChanges();
Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default);
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}
}
همچنین سرویسها و الگوهای Repositories در این بخش تعریف میشوند.
برای مثال، در Application ما داریم:
public interface IUserPaymentRepository
{
Task AddAsync(UserPayment entity, CancellationToken ct = default);
Task SaveChangesAsync(CancellationToken ct = default);
}
توجه کنید که در اینجا هیچ اشارهای به EF Core وجود ندارد؛ تنها تعهد به رفتار وجود دارد.
۳. لایهی Domain
این لایه شامل Entities و ValueObjects است؛ یعنی مدلهای اصلی دامنهی کسبوکار.
در Clean Architecture، لایهی Domain کاملاً مستقل است و حتی نباید به EF Core وابسته باشد.
نمونه:
public class UserPayment
{
public int Id { get; set; }
public decimal Amount { get; set; }
public DateTime InsertDate { get; set; }
}
۴. لایهی Persistence
این بخش مسئول ارتباط با پایگاه داده و پیادهسازی EF Core است.
در اینجا کلاسهای DbContext, EntityTypeConfiguration و Repositoryهای EF قرار میگیرند.
نمونهای از DataBaseContext:
public class DataBaseContext : DbContext, IDataBaseContext
{
public DataBaseContext(DbContextOptions options) : base(options) { }
public DbSet UserPayments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("dbo");
modelBuilder.ApplyConfiguration(new UserPaymentConfiguration());
}
public override int SaveChanges()
{
var entries = ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted)
.ToList();
foreach (var entry in entries)
{
var inserted = entry.Property("InsertDate");
var updated = entry.Property("UpdateDate");
var deleted = entry.Property("DeleteDate");
switch (entry.State)
{
case EntityState.Added:
if (inserted != null) inserted.CurrentValue = DateTime.Now;
break;
case EntityState.Modified:
if (updated != null) updated.CurrentValue = DateTime.Now;
break;
case EntityState.Deleted:
if (deleted != null)
{
deleted.CurrentValue = DateTime.Now;
entry.State = EntityState.Modified;
}
break;
}
}
return base.SaveChanges();
}
}
پیادهسازی Repository در این لایه:
public class EfUserPaymentRepository : IUserPaymentRepository
{
private readonly DataBaseContext _context;
public EfUserPaymentRepository(DataBaseContext context)
{
_context = context;
}
public async Task AddAsync(UserPayment entity, CancellationToken ct = default)
=> await _context.UserPayments.AddAsync(entity, ct);
public Task SaveChangesAsync(CancellationToken ct = default)
=> _context.SaveChangesAsync(ct);
}
۵. لایهی Infrastructure
در این بخش پیادهسازی ابزارها و سرویسهای Cross-Cutting (سراسری) قرار دارد؛
چیزهایی که به لایهی خاصی وابسته نیستند اما در کل سیستم استفاده میشوند، مانند:
-
ارسال ایمیل
-
ثبت لاگ
-
ارسال SMS
-
فایلسیستم
-
Mapster یا AutoMapper
برای مثال، اینترفیس زیر در Application تعریف میشود:
public interface IEmailSender
{
Task SendAsync(string to, string subject, string body);
}
و پیادهسازی آن در Infrastructure:
public class SmtpEmailSender : IEmailSender
{
public async Task SendAsync(string to, string subject, string body)
{
using var client = new SmtpClient("smtp.mailserver.com");
var message = new MailMessage("noreply@site.com", to, subject, body);
await client.SendMailAsync(message);
}
}
۶. لایهی Common
این بخش برای دادههای عمومی و ثابت مانند Enums، Constants، Helperهای عمومی استفاده میشود.
مثلاً:
public enum UserRole
{
Admin,
Guest,
User
}
public static class AppConstants
{
public const string DefaultCulture = "fa-IR";
}
نحوهی وابستگی بین لایهها
اصول وابستگی در Clean Architecture همیشه از بیرون به درون است:
Endpoint.Site → Application → Domain
↓ ↓
Infrastructure Persistence
-
Application فقط Domain و Common را میشناسد.
-
Infrastructure و Persistence به Application وابستهاند تا بتوانند اینترفیسها را پیادهسازی کنند.
-
Endpoint تنها لایهای است که همه را کنار هم جمع میکند.
ثبت وابستگیها (Dependency Injection)
در Clean Architecture، هر لایه خودش متد Extension برای DI دارد:
// FestivalsHome.Persistence
public static class PersistenceServiceRegistration
{
public static IServiceCollection AddPersistence(this IServiceCollection services, IConfiguration config)
{
services.AddDbContext(options =>
options.UseSqlServer(config.GetConnectionString("DefaultConnection")));
services.AddScoped();
return services;
}
}
// FestivalsHome.Infrastructure
public static class InfrastructureServiceRegistration
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration config)
{
services.AddScoped();
return services;
}
}
و در Program.cs:
builder.Services.AddPersistence(builder.Configuration); builder.Services.AddInfrastructure(builder.Configuration);
نتیجهگیری
پیادهسازی Clean Architecture در ASP.NET Core باعث میشود:
-
کد تمیزتر و قابل تستتر شود.
-
وابستگی به فریمورکها (مثل EF Core) از منطق کسبوکار جدا بماند.
-
بتوانید بخشهایی مثل دیتابیس یا سرویس ایمیل را بهراحتی تغییر دهید بدون اینکه منطق برنامه تحت تأثیر قرار گیرد.
-
تیمهای مختلف بتوانند بهصورت موازی روی بخشهای مجزا کار کنند.
این معماری نه تنها برای پروژههای بزرگ سازمانی بلکه برای استارتاپها و سامانههای مقیاسپذیر نیز انتخابی ایدهآل است.
«Clean Architecture یعنی طراحی نرمافزاری که حتی اگر همهی فریمورکها را عوض کنید، منطق اصلی آن همچنان پابرجا بماند
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.