آموزش Clean Architecture با C#

تاریخ: 1404/12/4 ساعت: 11:53 بازدید: 26

Clean Architecture چیست و چرا باید آن را یاد بگیری؟

اگر تا حالا پروژه‌ای نوشتی که بعد از چند ماه تبدیل به یک کد اسپاگتی درهم‌برهم شده و هر تغییر کوچکی توش هزار جای دیگه رو خراب می‌کنه، پس این مقاله دقیقاً برای توئه!

Clean Architecture یا معماری تمیز، یک رویکرد طراحی نرم‌افزاریه که توسط Robert C. Martin (معروف به Uncle Bob) معرفی شده. هدف اصلیش اینه که کدت رو به شکلی بنویسی که:

  • مستقل از فریمورک باشه (وابسته به ASP.NET یا هر فریمورک دیگه‌ای نشه)
  • قابل تست باشه (Unit Test نوشتن راحت باشه)
  • مستقل از UI باشه (بتونی Web، Desktop یا Mobile رو جایگزین کنی)
  • مستقل از دیتابیس باشه (SQL Server رو بذاری کنار، MongoDB بذاری)
  • مستقل از سرویس‌های خارجی باشه

به زبون ساده‌تر: وقتی Clean Architecture پیاده می‌کنی، هسته اصلی منطق کسب‌وکارت از همه چیز جدا می‌مونه و می‌تونی راحت هر بخشی رو عوض کنی بدون اینکه کل سیستم خراب بشه.

لایه‌های Clean Architecture به زبان ساده

Clean Architecture از چهار لایه اصلی تشکیل شده که مثل پیاز روی هم قرار می‌گیرن. قانون طلایی اینه: وابستگی‌ها فقط به سمت داخل می‌رن.

۱. لایه Domain (هسته اصلی)

این لایه قلب پروژه‌ته و به هیچ چیز خارجی وابسته نیست. شامل:

  • Entities: موجودیت‌های اصلی کسب‌وکار مثل User، Product، Order
  • Value Objects: اشیاء بدون هویت مستقل مثل Money، Address
  • Domain Events: رویدادهای کسب‌وکار
  • Exceptions: خطاهای مربوط به منطق دامین

۲. لایه Application (منطق برنامه)

این لایه Use Case های سیستم رو مدیریت می‌کنه. فقط به لایه Domain وابسته‌ست. شامل:

  • Interfaces / Abstractions: قراردادهایی که لایه‌های بیرونی باید پیاده کنن
  • Commands & Queries (CQRS): درخواست‌های نوشتن و خواندن
  • DTOs: اشیاء انتقال داده
  • Validators: اعتبارسنجی ورودی‌ها

۳. لایه Infrastructure (زیرساخت)

این لایه پیاده‌سازی جزئیات فنی رو انجام می‌ده. شامل:

  • Database: Entity Framework Core، Dapper و …
  • Email Service: ارسال ایمیل
  • File Storage: آپلود فایل
  • External APIs: سرویس‌های پرداخت، SMS و …

۴. لایه Presentation (رابط کاربری)

این لایه ارتباط با کاربر نهایی رو مدیریت می‌کنه. در ASP.NET Core شامل Controllers، Minimal APIs و Middlewareها میشه.

ساختار پوشه‌بندی پروژه Clean Architecture در C#

یکی از سوالات رایج اینه که ساختار فولدرها در پروژه واقعی چطور باشه. اینجا یه ساختار استاندارد داریم:


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/

پیاده‌سازی عملی: مثال کامل با C#

بیا یه مثال واقعی بزنیم. می‌خوایم یه سیستم ساده مدیریت محصول بسازیم.

گام اول: Domain Layer - ساختن Entity


// 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;

}

}

گام دوم: Application Layer - تعریف Interface و Command


// 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;

}

}

گام سوم: Infrastructure Layer - پیاده‌سازی Repository


// 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);

}

گام چهارم: تزریق وابستگی (Dependency Injection)


// 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 + MediatR در Clean Architecture

CQRS (Command Query Responsibility Segregation) یعنی جدا کردن عملیات نوشتن (Command) از عملیات خواندن (Query). این الگو با MediatR در C# بسیار راحت پیاده می‌شه.

  • Command: داده رو تغییر می‌ده (Create, Update, Delete)
  • Query: فقط داده می‌خونه (Get, List, Search)
  • Handler: منطق اجرای Command یا Query رو داره
  • MediatR: واسطی که Command/Query رو به Handler مربوطه وصل می‌کنه

با این رویکرد، هر Feature یه پوشه مستقل داره که شامل Command + Handler + Validator + Response میشه. کد تمیز، قابل نگهداری و مقیاس‌پذیر!

🚀 آیا می‌خواهید سایت شما هم مثل رقبا در صفحه اول گوگل باشد و زنگ‌خورهایتان چند برابر شود؟

همان‌طور که یک معماری تمیز، پروژه نرم‌افزاری شما را به سمت موفقیت می‌برد، یک استراتژی سئوی حرفه‌ای هم کسب‌وکار آنلاین شما را متحول می‌کند.

سئوی سایت خود را به متخصصان ما بسپارید. همین حالا برای مشاوره رایگان با ما تماس بگیرید:

📞 09190994063  |  09376846692

اعتبارسنجی با FluentValidation

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 با سایر معماری‌ها

ممکنه بپرسی تفاوت Clean Architecture با معماری لایه‌ای سنتی (N-Layer) یا Onion Architecture چیه؟

  • N-Layer سنتی: وابستگی از UI به BLL به DAL. مشکل اینه که همه چیز به دیتابیس وابسته‌ست
  • Onion Architecture: مشابه Clean Architecture، هسته مستقل از زیرساخته. Uncle Bob ایده‌هاش رو از اینجا گرفت
  • Hexagonal Architecture: تمرکز روی Ports و Adapters. مفهوم مشابه ولی اصطلاح متفاوت
  • Clean Architecture: ترکیب بهترین ایده‌ها با قوانین مشخص‌تر و نام‌گذاری استاندارد

نکات پیشرفته و اشتباهات رایج

اشتباهات رایج که باید ازشون پرهیز کنی:

  • Anemic Domain Model: وقتی Entity ها فقط Property دارن و هیچ منطقی ندارن. این با روح DDD (Domain Driven Design) مخالفه
  • وابستگی معکوس نقض‌شده: وقتی لایه Application مستقیماً به Infrastructure وابسته میشه. همیشه از Interface استفاده کن
  • Fat Controller: منطق کسب‌وکار نباید توی Controller باشه. Controller فقط باید Request رو بگیره و به MediatR پاس بده
  • Overengineering: برای پروژه‌های خیلی کوچک، Clean Architecture ممکنه زیادی پیچیده باشه. تناسب رو رعایت کن
  • EF Entity در لایه Application: هرگز IQueryable از EF Core رو به لایه Application برنگردون

نکات پیشرفته برای پیاده‌سازی بهتر:

  • از Result Pattern به جای throw Exception استفاده کن تا خطاها رو بهتر مدیریت کنی
  • Domain Events رو برای ارتباط بین Aggregate ها بکار ببر
  • با MediatR Pipeline Behaviors می‌تونی Logging، Caching، Validation رو به صورت Cross-Cutting پیاده کنی
  • Unit Test نوشتن برای Application Layer بدون نیاز به دیتابیس، یکی از بزرگترین مزایای این معماریه

ابزارها و پکیج‌های پیشنهادی

برای پیاده‌سازی حرفه‌ای Clean Architecture در C#، این پکیج‌ها رو پیشنهاد می‌کنم:

  • MediatR: برای CQRS و Mediator Pattern
  • FluentValidation: برای اعتبارسنجی تمیز و خوانا
  • AutoMapper یا Mapster: برای Mapping بین لایه‌ها
  • Entity Framework Core: برای ORM در لایه Infrastructure
  • Serilog: برای Logging ساختاریافته
  • xUnit + Moq: برای Unit Testing
  • ErrorOr یا OneOf: برای Result Pattern

💡 یک نکته مهم برای موفقیت دیجیتال شما:

بهترین معماری نرم‌افزاری دنیا هم اگر در فضای مجازی دیده نشه، ارزشش برای کسب‌وکار شما صفره. دقیقاً مثل Clean Architecture که هسته محصول رو از دنیای بیرون محافظت می‌کنه، یک استراتژی سئوی قوی هم کسب‌وکار شما رو در برابر رقبا حفظ می‌کنه. همین حالا با شماره‌های 09190994063 و 09376846692 تماس بگیر تا سئوی سایتت رو به دست متخصصان واقعی بسپاری.

سوالات متداول (FAQ)

۱. آیا Clean Architecture برای پروژه‌های کوچک هم مناسب است؟

نه لزوماً! Clean Architecture برای پروژه‌های متوسط تا بزرگ که قرار است در طول زمان رشد کنند و چند توسعه‌دهنده روی آن‌ها کار می‌کنند، ایده‌آل است. برای یک پروژه MVP کوچک یا یک اپ شخصی ساده، ممکن است این معماری بیش از حد پیچیده باشد و سرعت توسعه را کاهش دهد. قانون کلی: اگر پروژه قرار است رشد کند، از همان ابتدا Clean Architecture را پیاده کن.

۲. تفاوت Clean Architecture با DDD (Domain Driven Design) چیست؟

این دو مفهوم مکمل هم هستند و نه جایگزین. Clean Architecture یک الگوی معماری است که نحوه سازماندهی کد را مشخص می‌کند. DDD یک رویکرد طراحی است که تمرکزش روی مدل‌سازی دامین کسب‌وکار است. در اکثر پروژه‌های حرفه‌ای، هر دو با هم استفاده می‌شوند؛ از DDD برای طراحی لایه Domain و از Clean Architecture برای سازماندهی کلی پروژه.

۳. آیا می‌توان Blazor یا MVC را به جای Web API در Clean Architecture استفاده کرد؟

بله، کاملاً! یکی از بزرگترین مزایای Clean Architecture همین است. لایه Presentation می‌تواند هر چیزی باشد: ASP.NET Core Web API، Blazor، MVC، gRPC یا حتی یک Console Application. چون لایه‌های Domain و Application به لایه Presentation وابسته نیستند، می‌توانی بدون تغییر در منطق کسب‌وکار، رابط کاربری را کاملاً عوض کنی.

۴. بهترین منابع برای یادگیری عمیق‌تر Clean Architecture چیست؟

بهترین منابع عبارتند از: کتاب 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 در Clean Architecture اجباری است؟

خیر، MediatR اجباری نیست. این کتابخانه صرفاً پیاده‌سازی الگوی Mediator و CQRS را راحت‌تر می‌کند. می‌توانی بدون MediatR هم Clean Architecture کاملاً سالمی داشته باشی. برخی توسعه‌دهندگان ترجیح می‌دهند از Service ها به جای Command/Query Handler استفاده کنند. مهم رعایت اصل جداسازی مسئولیت‌ها و قانون وابستگی به سمت داخل است، نه ابزار خاصی که استفاده می‌کنی.

۶. چطور Unit Test برای Clean Architecture بنویسیم؟

یکی از بزرگترین مزایای Clean Architecture، قابلیت تست بودن بالای آن است. برای تست لایه Application، کافی است Interface های Repository و Service ها را با Moq یا NSubstitute Mock کنی و Handler ها را مستقیماً تست کنی. نیازی به بالا آوردن کل سرور یا دیتابیس نداری. این باعث می‌شود تست‌ها سریع، مطمئن و مستقل از محیط اجرا باشند.

نظرات کاربران