Clean Architecture in .NET 2026: CQRS, MediatR en Interviewvragen voor Ontwikkelaars
Een uitgebreide gids over Clean Architecture in .NET met CQRS en MediatR. Inclusief best practices, codevoorbeelden en veelgestelde interviewvragen voor .NET-architecten.

Clean Architecture heeft zich gevestigd als het voorkeursarchitectuurpatroon voor .NET-applicaties. In combinatie met CQRS (Command Query Responsibility Segregation) en MediatR ontstaan onderhoudbare, testbare en schaalbare systemen. Dit artikel behandelt de praktische implementatie van deze patterns in .NET 9 en bereidt voor op technische sollicitatiegesprekken.
Clean Architecture scheidt businesslogica van technische details zoals databases en frameworks. Deze scheiding maakt onafhankelijke tests mogelijk en vergemakkelijkt toekomstige technologiewisselingen aanzienlijk.
Wat is Clean Architecture?
Clean Architecture, ook bekend als Onion Architecture of Hexagonal Architecture, organiseert code in concentrische lagen. De binnenste laag bevat de domeinlogica, terwijl buitenste lagen verantwoordelijk zijn voor infrastructuur en presentatie.
De kernprincipes omvatten:
- Onafhankelijkheid van frameworks: De businesslogica kent geen frameworks
- Testbaarheid: Businessregels kunnen worden getest zonder UI, database of externe services
- Onafhankelijkheid van de UI: De gebruikersinterface kan worden vervangen zonder wijziging van businessregels
- Onafhankelijkheid van de database: Oracle, SQL Server of MongoDB zijn uitwisselbaar
Projectstructuur in .NET
Een typische Clean Architecture-oplossing in .NET bestaat uit vier hoofdprojecten:
// Projectstructuur
MyApp.Domain/ // Entiteiten, Value Objects, Domain Events
MyApp.Application/ // Use Cases, Commands, Queries, Interfaces
MyApp.Infrastructure/ // Databasetoegang, externe services
MyApp.WebApi/ // Controllers, Middleware, DI-configuratieDe afhankelijkheidsrichting wijst altijd naar binnen. Infrastructure en WebApi refereren naar Application, maar nooit andersom.
CQRS: Commands en Queries Scheiden
CQRS scheidt lees- en schrijfoperaties in afzonderlijke modellen. Commands veranderen de staat, Queries leveren data zonder neveneffecten.
// Command Definitie
public record CreateProductCommand(
string Name,
decimal Price,
string Description
) : IRequest<ProductDto>;
// Query Definitie
public record GetProductByIdQuery(Guid Id) : IRequest<ProductDto?>;
// Command Handler
public class CreateProductCommandHandler
: IRequestHandler<CreateProductCommand, ProductDto>
{
private readonly IProductRepository _repository;
private readonly IUnitOfWork _unitOfWork;
public CreateProductCommandHandler(
IProductRepository repository,
IUnitOfWork unitOfWork)
{
_repository = repository;
_unitOfWork = unitOfWork;
}
public async Task<ProductDto> Handle(
CreateProductCommand request,
CancellationToken cancellationToken)
{
var product = new Product(
request.Name,
request.Price,
request.Description);
await _repository.AddAsync(product, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return product.ToDto();
}
}MediatR Configureren
MediatR implementeert het Mediator-pattern en ontkoppelt verzenders van ontvangers. De configuratie in .NET 9 gebeurt in Program.cs:
var builder = WebApplication.CreateBuilder(args);
// MediatR registreren
builder.Services.AddMediatR(cfg => {
cfg.RegisterServicesFromAssembly(
typeof(CreateProductCommand).Assembly);
// Pipeline Behaviors toevoegen
cfg.AddBehavior<ValidationBehavior<,>>();
cfg.AddBehavior<LoggingBehavior<,>>();
});
// FluentValidation voor Commands
builder.Services.AddValidatorsFromAssembly(
typeof(CreateProductCommandValidator).Assembly);Pipeline Behaviors voor Cross-Cutting Concerns
Pipeline Behaviors maken de implementatie van dwarsdoorsnijdende aspecten mogelijk zoals validatie, logging en caching:
public class ValidationBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(
IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (!_validators.Any())
return await next();
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(
_validators.Select(v =>
v.ValidateAsync(context, cancellationToken)));
var failures = validationResults
.SelectMany(r => r.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
return await next();
}
}Domain Events met MediatR
Domain Events signaleren belangrijke businessgebeurtenissen en maken losjes gekoppelde reacties mogelijk:
// Domain Event Definitie
public record ProductCreatedEvent(Guid ProductId, string Name)
: INotification;
// Event Handler
public class ProductCreatedEventHandler
: INotificationHandler<ProductCreatedEvent>
{
private readonly IEmailService _emailService;
private readonly ILogger<ProductCreatedEventHandler> _logger;
public ProductCreatedEventHandler(
IEmailService emailService,
ILogger<ProductCreatedEventHandler> logger)
{
_emailService = emailService;
_logger = logger;
}
public async Task Handle(
ProductCreatedEvent notification,
CancellationToken cancellationToken)
{
_logger.LogInformation(
"Product aangemaakt: {ProductName}",
notification.Name);
await _emailService.SendProductNotificationAsync(
notification.ProductId,
cancellationToken);
}
}Repository Pattern en Unit of Work
Het Repository Pattern abstraheert de datatoegang en maakt het domein onafhankelijk van de persistentielaag:
// Interface in de Application Layer
public interface IProductRepository
{
Task<Product?> GetByIdAsync(
Guid id,
CancellationToken cancellationToken = default);
Task<IReadOnlyList<Product>> GetAllAsync(
CancellationToken cancellationToken = default);
Task AddAsync(
Product product,
CancellationToken cancellationToken = default);
void Update(Product product);
void Delete(Product product);
}
// Implementatie in de Infrastructure Layer
public class ProductRepository : IProductRepository
{
private readonly ApplicationDbContext _context;
public ProductRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<Product?> GetByIdAsync(
Guid id,
CancellationToken cancellationToken = default)
{
return await _context.Products
.Include(p => p.Category)
.FirstOrDefaultAsync(
p => p.Id == id,
cancellationToken);
}
public async Task AddAsync(
Product product,
CancellationToken cancellationToken = default)
{
await _context.Products.AddAsync(product, cancellationToken);
}
}Minimal APIs met MediatR
.NET 9 maakt elegante Minimal APIs mogelijk in combinatie met MediatR:
// Endpoint Definitie
app.MapPost("/api/products", async (
CreateProductCommand command,
ISender sender,
CancellationToken cancellationToken) =>
{
var result = await sender.Send(command, cancellationToken);
return Results.Created($"/api/products/{result.Id}", result);
})
.WithName("CreateProduct")
.WithOpenApi();
app.MapGet("/api/products/{id:guid}", async (
Guid id,
ISender sender,
CancellationToken cancellationToken) =>
{
var result = await sender.Send(
new GetProductByIdQuery(id),
cancellationToken);
return result is not null
? Results.Ok(result)
: Results.NotFound();
})
.WithName("GetProductById")
.WithOpenApi();Klaar om je .NET gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Unit Testing met Clean Architecture
De scheiding in lagen vereenvoudigt het testen aanzienlijk:
public class CreateProductCommandHandlerTests
{
private readonly Mock<IProductRepository> _repositoryMock;
private readonly Mock<IUnitOfWork> _unitOfWorkMock;
private readonly CreateProductCommandHandler _handler;
public CreateProductCommandHandlerTests()
{
_repositoryMock = new Mock<IProductRepository>();
_unitOfWorkMock = new Mock<IUnitOfWork>();
_handler = new CreateProductCommandHandler(
_repositoryMock.Object,
_unitOfWorkMock.Object);
}
[Fact]
public async Task Handle_ValidCommand_CreatesProduct()
{
// Arrange
var command = new CreateProductCommand(
"Test Product",
99.99m,
"Beschrijving");
// Act
var result = await _handler.Handle(
command,
CancellationToken.None);
// Assert
Assert.NotNull(result);
Assert.Equal(command.Name, result.Name);
_repositoryMock.Verify(
r => r.AddAsync(
It.IsAny<Product>(),
It.IsAny<CancellationToken>()),
Times.Once);
_unitOfWorkMock.Verify(
u => u.SaveChangesAsync(It.IsAny<CancellationToken>()),
Times.Once);
}
}Veelgestelde Interviewvragen over Clean Architecture
Vraag 1: Wat is het verschil tussen Clean Architecture en traditionele N-Tier-architectuur?
In N-Tier-architecturen wijzen afhankelijkheden van boven naar beneden: Presentation → Business → Data Access. Clean Architecture inverteert deze afhankelijkheid: het Domein staat centraal en heeft geen afhankelijkheden naar buitenste lagen. Infrastructure implementeert interfaces die in de Application Layer zijn gedefinieerd.
Vraag 2: Wanneer moet CQRS worden toegepast?
CQRS is geschikt voor systemen met verschillende lees- en schrijfvereisten. Typische toepassingen zijn: hoge leesbelasting bij zeldzame schrijfoperaties, complexe domeinlogica bij schrijfoperaties, of wanneer verschillende leesmodellen nodig zijn voor verschillende consumenten.
Vraag 3: Hoe worden circulaire afhankelijkheden tussen lagen voorkomen?
Door consequente toepassing van het Dependency Inversion Principle: interfaces worden gedefinieerd in de binnenste laag (Application), implementaties in de buitenste laag (Infrastructure). Dependency Injection configureert de toewijzing tijdens runtime.
Vraag 4: Wat zijn de nadelen van Clean Architecture?
Hogere initiële inspanning, meer boilerplate-code en verhoogde complexiteit voor kleine projecten. De voordelen wegen pas op bij middelgrote tot grote applicaties met langetermijnonderhoudsperspectief.
Vraag 5: Hoe worden Domain Events behandeld in gedistribueerde systemen?
Voor gedistribueerde systemen is MediatR niet voldoende. Integration Events worden verspreid via message brokers zoals RabbitMQ, Azure Service Bus of Kafka. Het Outbox Pattern waarborgt consistentie tussen databaseoperaties en event-publishing.
Best Practices voor 2026
-
Vertical Slices in combinatie met Clean Architecture: Features worden georganiseerd als zelfstandige eenheden, terwijl de scheiding in lagen binnen elke slice behouden blijft.
-
Result Pattern in plaats van Exceptions: Commands en Queries retourneren Result-objecten die succes of falen expliciet modelleren.
-
Source Generators voor boilerplate: .NET Source Generators verminderen repetitieve code voor mappings en validaties.
-
Aspire voor Cloud-Native ontwikkeling: .NET Aspire vereenvoudigt de orkestratie van Clean Architecture-applicaties in gecontaineriseerde omgevingen.
Conclusie
Clean Architecture met CQRS en MediatR vormt een solide fundament voor schaalbare .NET-applicaties. De duidelijke scheiding van verantwoordelijkheden vergemakkelijkt onderhoud, testen en doorontwikkeling. Voor technische sollicitatiegesprekken is het begrip van de onderliggende principes even belangrijk als de praktische implementatie. De investering in een schone architectuur betaalt zich op lange termijn terug door verminderde complexiteit en verhoogde ontwikkelaarsproductiviteit.
Zie jij de bug in .NET?
Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Geschreven door
Anthony Fillion-MailletOprichter van SharpSkill
Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.
Bijgewerkt op 15 september 2026
Tags
Delen
Gerelateerde artikelen

Clean Code Architecture C#: Complete Gids en Sollicitatievragen 2026
Uitgebreide gids over Clean Code Architecture in C# met SOLID-principes, gelaagde architectuur en de meest voorkomende technische sollicitatievragen voor .NET-ontwikkelaars.

.NET 10 in 2026: Nieuwe Features, Native AOT en C# 14 voor Sollicitatiepreparatie
.NET 10 verschijnt als Long-Term Support release met Native AOT-verbeteringen, C# 14 extension members, de field keyword en file-based apps. Een complete gids over nieuwe features, prestatiewinst en interview-ready kennis voor .NET-ontwikkelaars in 2026.

.NET MAUI in 2026: Cross-Platform Ontwikkeling en Sollicitatievragen
.NET MAUI tutorial voor 2026: cross-platform apps bouwen met .NET 10, handlers, MVVM, HybridWebView. Inclusief sollicitatievragen.