اقبالدار: «به شخصه، علاقه زیادی به معماری Clean داشته و بیشتر پروژه ها را براساس این معماری پیش می برم.»
دقت بفرمائید که این معماری براساس ASP.NET CORE MVC بنا شده است. هرچند که استفاده از این معماری محدود به هیچ زبان برنامه نویسی نخواهد بود.
با تکیه بر یک نمونهی واقعی از ساختار پروژه، نحوهی پیادهسازی Clean Architecture را در ASP.NET Core MVC بررسی کنیم. این پروژه شامل شش لایهی مستقل است:
ایدهی اصلی 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
این لایه همان پروژهی ASP.NET Core MVC شماست.
تمام کنترلرها، ویوها و APIها در این بخش قرار دارند.
تنها وابستگی مستقیم این لایه باید به لایهی Application باشد، زیرا همهی سرویسها و منطقهای کاری از آنجا در اختیار رابط کاربری قرار میگیرند.
در فایل csproj مربوط به Endpoint داریم:
در اینجا، Endpoint فقط سرویسها را از Application مصرف میکند، و برای ثبت وابستگیها از Dependency Injection استفاده میشود.
مغز اصلی منطق تجاری پروژه در اینجا قرار دارد.
در این بخش سرویسها و اینترفیسهای موردنیاز تعریف میشوند؛ بدون هیچ وابستگی به 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 وجود ندارد؛ تنها تعهد به رفتار وجود دارد.
این لایه شامل 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; }
}
این بخش مسئول ارتباط با پایگاه داده و پیادهسازی 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);
}
در این بخش پیادهسازی ابزارها و سرویسهای 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);
}
}
این بخش برای دادههای عمومی و ثابت مانند 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 تنها لایهای است که همه را کنار هم جمع میکند.
در 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 نظر
هنوز نظری برای این مقاله ثبت نشده است.