اگر تا حالا پروژهای نوشتی که بعد از چند ماه تبدیل به یک کد اسپاگتی درهمبرهم شده و هر تغییر کوچکی توش هزار جای دیگه رو خراب میکنه، پس این مقاله دقیقاً برای توئه!
Clean Architecture یا معماری تمیز، یک رویکرد طراحی نرمافزاریه که توسط Robert C. Martin (معروف به Uncle Bob) معرفی شده. هدف اصلیش اینه که کدت رو به شکلی بنویسی که:
به زبون سادهتر: وقتی Clean Architecture پیاده میکنی، هسته اصلی منطق کسبوکارت از همه چیز جدا میمونه و میتونی راحت هر بخشی رو عوض کنی بدون اینکه کل سیستم خراب بشه.
Clean Architecture از چهار لایه اصلی تشکیل شده که مثل پیاز روی هم قرار میگیرن. قانون طلایی اینه: وابستگیها فقط به سمت داخل میرن.
این لایه قلب پروژهته و به هیچ چیز خارجی وابسته نیست. شامل:
این لایه Use Case های سیستم رو مدیریت میکنه. فقط به لایه Domain وابستهست. شامل:
این لایه پیادهسازی جزئیات فنی رو انجام میده. شامل:
این لایه ارتباط با کاربر نهایی رو مدیریت میکنه. در ASP.NET Core شامل Controllers، Minimal APIs و Middlewareها میشه.
یکی از سوالات رایج اینه که ساختار فولدرها در پروژه واقعی چطور باشه. اینجا یه ساختار استاندارد داریم:
MyProject.sln ├── src/ │ ├── MyProject.Domain/ │ │ ├── Entities/ │ │ ├── ValueObjects/ │ │ ├── Events/ │ │ └── Exceptions/ │ │ │ ├── MyProject.Application/ │ │ ├── Interfaces/ │ │ ├── Features/ │ │ │ ├── Users/ │ │ │ │ ├── Commands/ │ │ │ │ └── Queries/ │ │ ├── DTOs/ │ │ └── Validators/ │ │ │ ├── MyProject.Infrastructure/ │ │ ├── Persistence/ │ │ ├── Services/ │ │ └── DependencyInjection.cs │ │ │ └── MyProject.WebAPI/ │ ├── Controllers/ │ ├── Middlewares/ │ └── Program.cs │ └── tests/ ├── MyProject.UnitTests/ └── MyProject.IntegrationTests/
بیا یه مثال واقعی بزنیم. میخوایم یه سیستم ساده مدیریت محصول بسازیم.
// MyProject.Domain/Entities/Product.cs
namespace MyProject.Domain.Entities;
public class Product
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public decimal Price { get; private set; }
public int Stock { get; private set; }
public DateTime CreatedAt { get; private set; }
private Product() { } // For EF Core
public static Product Create(string name, decimal price, int stock)
{
if (string.IsNullOrWhiteSpace(name))
throw new DomainException(“نام محصول نمیتواند خالی باشد”);
if (price <= 0)
throw new DomainException(“قیمت باید بزرگتر از صفر باشد”);
return new Product
{
Id = Guid.NewGuid(),
Name = name,
Price = price,
Stock = stock,
CreatedAt = DateTime.UtcNow
};
}
public void UpdatePrice(decimal newPrice)
{
if (newPrice <= 0)
throw new DomainException(“قیمت جدید نامعتبر است”);
Price = newPrice;
}
}
// MyProject.Application/Interfaces/IProductRepository.cs
namespace MyProject.Application.Interfaces;
public interface IProductRepository
{
Task GetByIdAsync(Guid id, CancellationToken ct = default);
Task> GetAllAsync(CancellationToken ct = default);
Task AddAsync(Product product, CancellationToken ct = default);
Task SaveChangesAsync(CancellationToken ct = default);
}
// MyProject.Application/Features/Products/Commands/CreateProduct/
// CreateProductCommand.cs
public record CreateProductCommand(
string Name,
decimal Price,
int Stock) : IRequest;
// CreateProductCommandHandler.cs
public class CreateProductCommandHandler
: IRequestHandler
{
private readonly IProductRepository _repository;
public CreateProductCommandHandler(IProductRepository repository)
=> _repository = repository;
public async Task Handle(
CreateProductCommand request,
CancellationToken cancellationToken)
{
var product = Product.Create(
request.Name,
request.Price,
request.Stock);
await _repository.AddAsync(product, cancellationToken);
await _repository.SaveChangesAsync(cancellationToken);
return product.Id;
}
}
// MyProject.Infrastructure/Persistence/Repositories/ProductRepository.cs
public class ProductRepository : IProductRepository
{
private readonly ApplicationDbContext _context;
public ProductRepository(ApplicationDbContext context)
=> _context = context;
public async Task GetByIdAsync(Guid id, CancellationToken ct)
=> await _context.Products.FindAsync(new object[] { id }, ct);
public async Task> GetAllAsync(CancellationToken ct)
=> await _context.Products.ToListAsync(ct);
public async Task AddAsync(Product product, CancellationToken ct)
=> await _context.Products.AddAsync(product, ct);
public async Task SaveChangesAsync(CancellationToken ct)
=> await _context.SaveChangesAsync(ct);
}
// MyProject.Infrastructure/DependencyInjection.cs
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddDbContext(options =>
options.UseSqlServer(
configuration.GetConnectionString(“DefaultConnection”)));
services.AddScoped();
return services;
}
}
// Program.cs
builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddApplication(); // MediatR, FluentValidation, etc.
CQRS (Command Query Responsibility Segregation) یعنی جدا کردن عملیات نوشتن (Command) از عملیات خواندن (Query). این الگو با MediatR در C# بسیار راحت پیاده میشه.
با این رویکرد، هر Feature یه پوشه مستقل داره که شامل Command + Handler + Validator + Response میشه. کد تمیز، قابل نگهداری و مقیاسپذیر!
🚀 آیا میخواهید سایت شما هم مثل رقبا در صفحه اول گوگل باشد و زنگخورهایتان چند برابر شود؟
همانطور که یک معماری تمیز، پروژه نرمافزاری شما را به سمت موفقیت میبرد، یک استراتژی سئوی حرفهای هم کسبوکار آنلاین شما را متحول میکند.
سئوی سایت خود را به متخصصان ما بسپارید. همین حالا برای مشاوره رایگان با ما تماس بگیرید:
📞 09190994063 | 09376846692
FluentValidation یکی از بهترین کتابخانهها برای اعتبارسنجی در لایه Application هست. اینجوری منطق Validation از Controller جدا میشه:
// CreateProductCommandValidator.cs public class CreateProductCommandValidator : AbstractValidator{ public CreateProductCommandValidator() { RuleFor(x => x.Name) .NotEmpty().WithMessage(“نام محصول الزامی است”) .MaximumLength(200).WithMessage(“نام نباید بیشتر از ۲۰۰ کاراکتر باشد”); RuleFor(x => x.Price) .GreaterThan(0).WithMessage(“قیمت باید بزرگتر از صفر باشد”); RuleFor(x => x.Stock) .GreaterThanOrEqualTo(0).WithMessage(“موجودی نمیتواند منفی باشد”); } }
با استفاده از ValidationBehavior در MediatR Pipeline، اعتبارسنجی به صورت خودکار قبل از هر Command اجرا میشه. دیگه نیازی نیست توی هر Controller کد تکراری بنویسی!
ممکنه بپرسی تفاوت Clean Architecture با معماری لایهای سنتی (N-Layer) یا Onion Architecture چیه؟
برای پیادهسازی حرفهای Clean Architecture در C#، این پکیجها رو پیشنهاد میکنم:
💡 یک نکته مهم برای موفقیت دیجیتال شما:
بهترین معماری نرمافزاری دنیا هم اگر در فضای مجازی دیده نشه، ارزشش برای کسبوکار شما صفره. دقیقاً مثل Clean Architecture که هسته محصول رو از دنیای بیرون محافظت میکنه، یک استراتژی سئوی قوی هم کسبوکار شما رو در برابر رقبا حفظ میکنه. همین حالا با شمارههای 09190994063 و 09376846692 تماس بگیر تا سئوی سایتت رو به دست متخصصان واقعی بسپاری.
نه لزوماً! Clean Architecture برای پروژههای متوسط تا بزرگ که قرار است در طول زمان رشد کنند و چند توسعهدهنده روی آنها کار میکنند، ایدهآل است. برای یک پروژه MVP کوچک یا یک اپ شخصی ساده، ممکن است این معماری بیش از حد پیچیده باشد و سرعت توسعه را کاهش دهد. قانون کلی: اگر پروژه قرار است رشد کند، از همان ابتدا Clean Architecture را پیاده کن.
این دو مفهوم مکمل هم هستند و نه جایگزین. Clean Architecture یک الگوی معماری است که نحوه سازماندهی کد را مشخص میکند. DDD یک رویکرد طراحی است که تمرکزش روی مدلسازی دامین کسبوکار است. در اکثر پروژههای حرفهای، هر دو با هم استفاده میشوند؛ از DDD برای طراحی لایه Domain و از Clean Architecture برای سازماندهی کلی پروژه.
بله، کاملاً! یکی از بزرگترین مزایای Clean Architecture همین است. لایه Presentation میتواند هر چیزی باشد: ASP.NET Core Web API، Blazor، MVC، gRPC یا حتی یک Console Application. چون لایههای Domain و Application به لایه Presentation وابسته نیستند، میتوانی بدون تغییر در منطق کسبوکار، رابط کاربری را کاملاً عوض کنی.
بهترین منابع عبارتند از: کتاب Clean Architecture نوشته Robert C. Martin (Uncle Bob)، کانال یوتیوب Milan Jovanović که محتوای عالی درباره Clean Architecture در .NET دارد، مخزن GitHub با نام ardalis/CleanArchitecture که یک Template آماده و حرفهای است، و دورههای Jason Taylor در پلتفرمهای آموزشی بینالمللی. همچنین مطالعه کتابهای DDD مثل “Domain-Driven Design” اثر Eric Evans بسیار مفید است.
خیر، MediatR اجباری نیست. این کتابخانه صرفاً پیادهسازی الگوی Mediator و CQRS را راحتتر میکند. میتوانی بدون MediatR هم Clean Architecture کاملاً سالمی داشته باشی. برخی توسعهدهندگان ترجیح میدهند از Service ها به جای Command/Query Handler استفاده کنند. مهم رعایت اصل جداسازی مسئولیتها و قانون وابستگی به سمت داخل است، نه ابزار خاصی که استفاده میکنی.
یکی از بزرگترین مزایای Clean Architecture، قابلیت تست بودن بالای آن است. برای تست لایه Application، کافی است Interface های Repository و Service ها را با Moq یا NSubstitute Mock کنی و Handler ها را مستقیماً تست کنی. نیازی به بالا آوردن کل سرور یا دیتابیس نداری. این باعث میشود تستها سریع، مطمئن و مستقل از محیط اجرا باشند.