# Clean Architecture .NET 2026: CQRS, MediatR, dan Pertanyaan Interview Developer > Panduan lengkap Clean Architecture di .NET dengan CQRS dan MediatR 14. Pelajari pola arsitektur berlapis, pipeline behaviors, dan persiapan interview developer senior. - Published: 2026-09-15 - Updated: 2026-09-15 - Author: Anthony Fillion-Maillet - Tags: clean-architecture, cqrs, mediatr, dotnet, interview - Reading time: 8 min --- Clean Architecture di .NET memisahkan logika bisnis dari infrastruktur, membuat aplikasi lebih mudah diuji, dipelihara, dan di-scale. Dikombinasikan dengan CQRS (Command Query Responsibility Segregation) dan MediatR, pola arsitektur ini memberikan pendekatan terstruktur untuk membangun aplikasi enterprise yang sering dinilai dalam interview teknis. > **Penting untuk Interview** > > Pertanyaan Clean Architecture muncul di 70% interview senior .NET. Interviewer mengharapkan kandidat menjelaskan dependency rule: dependency mengarah ke dalam, dengan domain layer tidak memiliki dependency pada framework eksternal. ## Layer Clean Architecture dan Dependency Rule [Template Clean Architecture dari Ardalis](https://github.com/ardalis/CleanArchitecture) untuk ASP.NET Core 10 mengorganisir kode ke dalam empat layer konsentris. Setiap layer memiliki tanggung jawab spesifik dan aturan dependency yang ketat. ```csharp // Core/Domain Layer - Tanpa dependency eksternal // src/Core/Domain/Entities/Order.cs namespace Core.Domain.Entities; public class Order { public Guid Id { get; private set; } public string CustomerEmail { get; private set; } public List Lines { get; private set; } = new(); public OrderStatus Status { get; private set; } public DateTime CreatedAt { get; private set; } // Logika domain dienkapsulasi dalam entity public void AddLine(Product product, int quantity) { if (Status != OrderStatus.Draft) throw new InvalidOperationException("Cannot modify a submitted order"); var existingLine = Lines.FirstOrDefault(l => l.ProductId == product.Id); if (existingLine is not null) { existingLine.IncreaseQuantity(quantity); return; } Lines.Add(new OrderLine(product.Id, product.Price, quantity)); } public void Submit() { if (!Lines.Any()) throw new InvalidOperationException("Cannot submit an empty order"); Status = OrderStatus.Submitted; } } ``` Domain layer berisi entities, value objects, dan domain services. Layer ini tidak mengetahui apapun tentang database, HTTP, atau framework eksternal. ## Pola CQRS: Memisahkan Read dari Write CQRS membagi operasi menjadi Commands (operasi write yang mengubah state) dan Queries (operasi read yang mengembalikan data). Pemisahan ini memungkinkan scaling dan optimisasi independen untuk setiap jalur. ```csharp // UseCases Layer - Commands dan Queries // src/UseCases/Orders/Commands/CreateOrder/CreateOrderCommand.cs namespace UseCases.Orders.Commands.CreateOrder; public record CreateOrderCommand( string CustomerEmail, List Lines ) : IRequest>; public record OrderLineDto(Guid ProductId, int Quantity); ``` Commands mengembalikan data minimal, biasanya hanya indikator sukses atau ID yang baru dibuat. Queries mengembalikan DTO yang dioptimalkan untuk konsumer. ```csharp // src/UseCases/Orders/Queries/GetOrderById/GetOrderByIdQuery.cs namespace UseCases.Orders.Queries.GetOrderById; public record GetOrderByIdQuery(Guid OrderId) : IRequest>; public record OrderDetailsDto( Guid Id, string CustomerEmail, string Status, decimal TotalAmount, List Lines ); ``` ## MediatR 14: Pipeline Behaviors dan Implementasi Handler [MediatR 14.2](https://www.nuget.org/packages/MediatR) menyediakan infrastruktur messaging untuk CQRS. Setiap command atau query memiliki tepat satu handler, menegakkan single responsibility. ```csharp // src/UseCases/Orders/Commands/CreateOrder/CreateOrderHandler.cs namespace UseCases.Orders.Commands.CreateOrder; public class CreateOrderHandler : IRequestHandler> { private readonly IOrderRepository _orderRepository; private readonly IProductRepository _productRepository; private readonly IUnitOfWork _unitOfWork; public CreateOrderHandler( IOrderRepository orderRepository, IProductRepository productRepository, IUnitOfWork unitOfWork) { _orderRepository = orderRepository; _productRepository = productRepository; _unitOfWork = unitOfWork; } public async Task> Handle( CreateOrderCommand request, CancellationToken cancellationToken) { // Load products untuk validasi dan mendapatkan harga var productIds = request.Lines.Select(l => l.ProductId).ToList(); var products = await _productRepository .GetByIdsAsync(productIds, cancellationToken); if (products.Count != productIds.Count) return Result.NotFound("One or more products not found"); // Membuat order menggunakan logika domain var order = new Order(request.CustomerEmail); foreach (var line in request.Lines) { var product = products.First(p => p.Id == line.ProductId); order.AddLine(product, line.Quantity); } await _orderRepository.AddAsync(order, cancellationToken); await _unitOfWork.SaveChangesAsync(cancellationToken); return Result.Success(order.Id); } } ``` ## Pipeline Behaviors untuk Cross-Cutting Concerns Pipeline behaviors di MediatR menangani logging, validasi, caching, dan manajemen transaksi tanpa mengotori handlers. ```csharp // src/UseCases/Common/Behaviors/ValidationBehavior.cs namespace UseCases.Common.Behaviors; public class ValidationBehavior : IPipelineBehavior where TRequest : IRequest { private readonly IEnumerable> _validators; public ValidationBehavior(IEnumerable> validators) { _validators = validators; } public async Task Handle( TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { if (!_validators.Any()) return await next(); var context = new ValidationContext(request); var validationResults = await Task.WhenAll( _validators.Select(v => v.ValidateAsync(context, cancellationToken))); var failures = validationResults .SelectMany(r => r.Errors) .Where(f => f is not null) .ToList(); if (failures.Any()) throw new ValidationException(failures); return await next(); } } ``` Daftarkan behaviors sesuai urutan eksekusi. Validasi biasanya berjalan pertama, diikuti logging, kemudian handling transaksi. ```csharp // src/Web/Program.cs builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(CreateOrderCommand).Assembly); cfg.AddOpenBehavior(typeof(ValidationBehavior<,>)); cfg.AddOpenBehavior(typeof(LoggingBehavior<,>)); cfg.AddOpenBehavior(typeof(TransactionBehavior<,>)); }); ``` ## Infrastructure Layer: Implementasi Repository Infrastructure layer mengimplementasikan interface yang didefinisikan di core layer. Repository Entity Framework Core menerjemahkan operasi domain ke panggilan database. ```csharp // src/Infrastructure/Data/Repositories/OrderRepository.cs namespace Infrastructure.Data.Repositories; public class OrderRepository : IOrderRepository { private readonly AppDbContext _context; public OrderRepository(AppDbContext context) { _context = context; } public async Task GetByIdAsync( Guid id, CancellationToken cancellationToken = default) { return await _context.Orders .Include(o => o.Lines) .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); } public async Task AddAsync( Order order, CancellationToken cancellationToken = default) { await _context.Orders.AddAsync(order, cancellationToken); } public async Task> GetByCustomerEmailAsync( string email, CancellationToken cancellationToken = default) { return await _context.Orders .Where(o => o.CustomerEmail == email) .OrderByDescending(o => o.CreatedAt) .ToListAsync(cancellationToken); } } ``` ## Pertanyaan Interview: Apa yang Harus Diketahui Developer Senior Interview teknis untuk posisi senior .NET sering mencakup pertanyaan Clean Architecture. Berikut pola-pola yang dinilai interviewer: **Q: Mengapa domain layer tidak memiliki dependency pada framework eksternal?** Domain layer berisi aturan bisnis yang harus tetap stabil terlepas dari perubahan infrastruktur. Jika domain bergantung pada Entity Framework, beralih ke Dapper atau database berbeda memerlukan perubahan logika bisnis. Dependency rule memastikan infrastruktur adalah detail yang dapat ditukar tanpa mempengaruhi behavior inti. **Q: Kapan CQRS berlebihan?** CQRS menambah kompleksitas yang tidak diperlukan aplikasi CRUD sederhana. Jika model read dan write hampir identik, jika tidak ada kebutuhan scaling terpisah, dan jika tim kecil, arsitektur yang lebih sederhana lebih tepat. CQRS bersinar ketika model read sangat berbeda dari model write, ketika event sourcing diperlukan, atau ketika beban read dan write perlu scaling independen. **Q: Bagaimana menangani transaksi lintas multiple aggregates?** ```csharp // Transaction behavior membungkus seluruh eksekusi handler public class TransactionBehavior : IPipelineBehavior where TRequest : IRequest { private readonly IUnitOfWork _unitOfWork; public TransactionBehavior(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public async Task Handle( TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { // Commands yang memodifikasi multiple aggregates menggunakan transaksi eksplisit if (request is not ICommand) return await next(); await using var transaction = await _unitOfWork .BeginTransactionAsync(cancellationToken); try { var response = await next(); await transaction.CommitAsync(cancellationToken); return response; } catch { await transaction.RollbackAsync(cancellationToken); throw; } } } ``` Untuk operasi yang mencakup multiple aggregates, gunakan eventual consistency dengan domain events bila memungkinkan. Transaksi sinkron lintas aggregates mengindikasikan potensi masalah desain: aggregates tersebut mungkin seharusnya digabungkan. ## Optimisasi Read-Side dengan Model Query Terpisah Queries melewati domain layer sepenuhnya ketika performa penting. Akses database langsung dengan [Dapper](https://github.com/DapperLib/Dapper) atau raw SQL menghindari overhead materialisasi entity graph penuh. ```csharp // src/Infrastructure/Queries/GetOrderByIdQueryHandler.cs namespace Infrastructure.Queries; public class GetOrderByIdQueryHandler : IRequestHandler> { private readonly IDbConnection _connection; public GetOrderByIdQueryHandler(IDbConnection connection) { _connection = connection; } public async Task> Handle( GetOrderByIdQuery request, CancellationToken cancellationToken) { const string sql = """ SELECT o.Id, o.CustomerEmail, o.Status, o.CreatedAt, SUM(ol.UnitPrice * ol.Quantity) AS TotalAmount FROM Orders o LEFT JOIN OrderLines ol ON ol.OrderId = o.Id WHERE o.Id = @OrderId GROUP BY o.Id, o.CustomerEmail, o.Status, o.CreatedAt """; var order = await _connection.QueryFirstOrDefaultAsync( new CommandDefinition(sql, new { request.OrderId }, cancellationToken: cancellationToken)); return order is null ? Result.NotFound($"Order {request.OrderId} not found") : Result.Success(order); } } ``` ## Persiapan Interview Clean Architecture: Poin-Poin Utama - Dependency rule menyatakan bahwa dependency source code hanya boleh mengarah ke dalam. Domain layer tidak mengetahui database, framework, atau mekanisme delivery. - CQRS memisahkan commands (perubahan state) dari queries (pengambilan data). Commands melewati domain model dan validasi. Queries dapat melewati domain entities untuk performa. - MediatR 14 merutekan requests ke handlers. Pipeline behaviors menangani cross-cutting concerns seperti validasi, logging, dan transaksi secara composable. - Interface repository berada di core layer. Implementasi berada di infrastructure. Inversi ini memungkinkan penukaran EF Core dengan Dapper tanpa menyentuh logika bisnis. - Jawaban interview harus mencakup trade-offs. Clean Architecture menambah kompleksitas awal tetapi memberikan hasil di aplikasi besar, berumur panjang dengan multiple developers. - Modul [ASP.NET Core Clean Architecture](/technologies/dotnet/interview-questions/clean-architecture) di SharpSkill mencakup pola tambahan termasuk Specification, Result objects, dan Domain Events. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/dotnet/clean-architecture-dotnet-cqrs-mediatr-interview-questions-2026