معماری 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.csvar 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.cspublic 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 را در خط لوله اضافه کنیم و تمام است، نیازی به اضافه کردن ویژگی جدید در خط لوله نیست.نظر شما در مورد این ویژگی جدید چیست؟ آیا آن را به عنوان یک افزودنی ارزشمند برای پروژه های خود می بینید یا قصد دارید به معماری سنتی لایه ای/پیازی پایبند باشید؟ از کد نویسی لذت ببرید!!!