وحید چشمی
وحید چشمی
خواندن ۶ دقیقه·۱ سال پیش

معماری vertical slice در NET8.

معرفی

بسیاری از ما با معماری تمیز آشنا هستیم، امروزه به این رویکرد معماری لایه ای سنتی گفته می شود.

یک معماری سنتی لایه‌ای یا پیازی کد را بر اساس technical concern در لایه‌های مختلف سازمان‌دهی می‌کند. در این رویکرد، هر لایه مسئولیت فردی در سیستم دارد.

سپس لایه ها به یکدیگر وابسته هستند و می توانند با یکدیگر همکاری کنند.

در برنامه های وب، لایه های زیر ممکن است اعمال شوند:

  • ارائه(Presentation)
  • کاربرد(Application)
  • دامنه(Domain)
  • زیر ساخت(Infrastructure)
رویکرد معماری پیاز
رویکرد معماری پیاز

مشکلات معماری لایه ای سنتی

  • به شدت به لایه‌ها متصل است، به آن بستگی دارد و قابلیت نگهداری مشکل خواهد بود
  • تغییر موازی بین توسعه دهندگان ممکن است دشوار باشد، به این معنی که با تغییر بخش های مختلف، برنامه ممکن است دچار اختلال شود.

معماری برش عمودی

معماری برش عمودی یک الگوی معماری است که کدها را به جای الگوهای فنی بر اساس ویژگی ها سازماندهی می کند.

معماری برش عمودی
معماری برش عمودی

همانطور که در تصویر می بینید، ایده معماری برش عمودی در مورد گروه بندی کدها بر اساس عملکردهای تجاری است و همه کدهای مربوطه را در کنار هم قرار می دهد.

در معماری لایه ای هدف این است که به لایه های افقی فکر کنیم، اما در معماری عمودی باید به لایه های عمودی فکر کنیم، در این مورد باید همه چیز را به عنوان یک ویژگی قرار دهیم، یعنی نیازی به لایه های مشترک مانند repositories, services, infrastructure, و حتی controllers نداریم و فقط باید روی ویژگی ها تمرکز کنیم.

توسعه:

حالا بیایید نگاهی بیندازیم که چگونه می‌توانیم از این رویکرد پیروی کنیم، من یک minimal API ساده در NET 8 ایجاد خواهم کرد.

در اینجا راه حل ما شبیه خواهد بود:

راه حل معماری برش عمودی

مرحله 1: بسته های لازم

ما باید بسته های زیر را نصب کنیم

dotnet add package MediatR dotnet add Microsoft.EntityFrameworkCore dotnet add Microsoft.EntityFrameworkCore.Design dotnet add Microsoft.EntityFrameworkCore.SqlServer

مرحله 2:Entity را اضافه کنید

بیایید با ایجاد یک موجودیت شروع کنیم.

public class Book { public long Id { get; set; } public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; }

مرحله 3: پیکربندی DbContext

باید dbContext را در پوشه پایگاه داده اضافه کنیم

public class ApplicationDbContext:DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options) { } public DbSet<Book> Books { get; set; } }

مرحله 4: سرویس ها و میان افزارها را پیکربندی کنید

زمان آن است که سرویس ها را پیکربندی کنیم و همچنین middleware را در API خود اضافه کنیم

appsettings.Development.json

{ &quotLogging&quot : { &quotLogLevel&quot : { &quotDefault&quot : &quotInformation&quot , &quotMicrosoft.AspNetCore&quot : &quotWarning&quot } } , &quotConnectionStrings&quot : { &quotDatabase&quot : &quotServer=localhost;Database=VSA;Integrated&quot Security=true;MultipleActiveResultSets=true;TrustServerCertificate=Yes;&quot } }

Program.cs

var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<ApplicationDbContext>(opt => { opt.UseSqlServer(builder.Configuration.GetConnectionString(&quotDatabase&quot)); }); builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)); var app = builder.Build(); app.Run();

بنابراین، ما فقط dbContext را برای استفاده از سرور SQL و همچنین سرویس MediatR را ثبت کردیم.

مرحله 5: ایجاد ویژگی کتاب

مرحله بعدی این است که ویژگی را در معماری خود اعمال کنیم، در این مورد باید هر ویژگی را از هم جدا کنیم، در این مثال یک ویژگی کتاب ایجاد می کنم که همه کتاب ها را ایجاد کرده و دریافت می کند.

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

public static class CreateBook { public sealed class CreateBookCommand:IRequest<long> { public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; } internal sealed class Handler : IRequestHandler<CreateBookCommand, long> { private readonly ApplicationDbContext _dbContext; public Handler(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task<long> Handle(CreateBookCommand request, CancellationToken cancellationToken) { var book = new Book { Name = request.Name, Description = request.Description }; await _dbContext.AddAsync(book, cancellationToken); await _dbContext.SaveChangesAsync(cancellationToken); return book.Id; } } public static void AddEndpoint(this IEndpointRouteBuilder app) { app.MapPost(&quotapi/books&quot, async (CreateBookRequest request, ISender sender) => { var bookId = await sender.Send(request); return Results.Ok(bookId); }); } }

قراردادها

public class CreateBookRequest { public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; }

همانطور که می بینید، همه ویژگی را در کلاس static اضافه کردیم، یک ورودی ایجاد می کند که از IRequest برای ایجاد یک دستور استفاده می کند، سپس IRequest را به کنترل کننده آن اضافه می کند و کتاب را ایجاد می کند و در نهایت نقطه پایانی را برای فراخوانی توسط مشتری.

برای اعمال نقطه پایانی که به این میان افزار در خط لوله نیاز داریم، به سادگی می توانیم آن را در کلاس program.cs اضافه کنیم.

var app = builder.Build(); CreateBook.AddEndpoint(app); ...

مرحله 6: قابلیت دریافت همه کتاب ها

بیایید یک پرس و جو ایجاد کنیم تا همه کتاب ها را انتخاب کرده و به مشتری برگردانیم.

public static class GetAllBooks { public sealed class Query : IRequest<List<Book>> { } internal sealed class QueryHandler : IRequestHandler<Query, List<Book>> { private readonly ApplicationDbContext _dbContext; public QueryHandler(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task<List<Book>> Handle(Query request, CancellationToken cancellationToken) => await _dbContext.Books.ToListAsync(cancellationToken: cancellationToken); } public static void AddEndpoint(this IEndpointRouteBuilder app) { app.MapGet(&quotapi/books&quot, async (ISender sender) => { var books=await sender.Send(new GetAllBooks.Query()); return Results.Ok(books); }); } }

همچنین، باید Endpoint را در Pipeline اضافه کنیم:

var app = builder.Build(); CreateBook.AddEndpoint(app); GetAllBooks.AddEndpoint(app); ...

مسئله:

همانطور که می بینید، برای هر ویژگی که نیاز داریم میان افزار endpoint را اضافه کنیم، در اینجا بسته دیگری وجود دارد که می توانیم نصب کرده و از آن برای خلاص شدن از اضافه کردن میان افزار جدید استفاده کنیم.

dotnet add package Carter

برای استفاده از کارتر، باید ویژگی ها را اصلاح کنیم
CreateBook.cs

public static class CreateBook { public sealed class CreateBookCommand:IRequest<long> { public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; } internal sealed class Handler : IRequestHandler<CreateBookCommand, long> { private readonly ApplicationDbContext _dbContext; public Handler(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task<long> Handle(CreateBookCommand request, CancellationToken cancellationToken) { var book = new Book { Name = request.Name, Description = request.Description }; await _dbContext.AddAsync(book, cancellationToken); await _dbContext.SaveChangesAsync(cancellationToken); return book.Id; } } } public class CreateBookEndpoint : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapPost(&quotapi/books&quot, async (CreateBookRequest request, ISender sender) => { var bookId = await sender.Send(request); return Results.Ok(bookId); }); } }
GetAllBooks.cs public static class GetAllBooks { public sealed class Query : IRequest<List<Book>> { } internal sealed class QueryHandler : IRequestHandler<Query, List<Book>> { private readonly ApplicationDbContext _dbContext; public QueryHandler(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task<List<Book>> Handle(Query request, CancellationToken cancellationToken) => await _dbContext.Books.ToListAsync(cancellationToken: cancellationToken); } } public class GetAllBooksEndpoint : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapGet(&quotapi/books&quot, async (ISender sender) => { var books=await sender.Send(new GetAllBooks.Query()); return Results.Ok(books); }); } }

و آخرین مرحله ثبت Carter در Middleware است

var app = builder.Build(); app.MapCarter();

ما فقط باید میان افزار MapCarter را در خط لوله اضافه کنیم و تمام است، نیازی به اضافه کردن ویژگی جدید در خط لوله نیست.

نظر شما در مورد این ویژگی جدید چیست؟ آیا آن را به عنوان یک افزودنی ارزشمند برای پروژه های خود می بینید یا قصد دارید به معماری سنتی لایه ای/پیازی پایبند باشید؟

از کد نویسی لذت ببرید!!!

معماریبرنامه نویسیسی شارپمعماری نرم افزار
ســلام، من وحید هستم، چند سالی هست که دستم رو کیبورده و کد میزنم. دوست دارم چیزی که تجربه میکنم رو با شما به اشتراک بزارم.https://youtube.com/@devlife013
شاید از این پست‌ها خوشتان بیاید