IConfiguration vs IOptions NET
Synchronous and Asynchronous in .NET Core
Model Binding and Validation in ASP.NET Core
ControllerBase vs Controller in ASP.NET Core
ConfigureServices and Configure methods
IHostedService interface in .NET Core
ASP.NET Core request processing
| Onion Architecture in .NET | Micro-Frontends | |
Hexagonal Architecture |
Hexagonal Architecture, coined by Alistair Cockburn, is a software design pattern that separates the core business logic from external systems like databases, UIs, or APIs. It promotes flexibility, testability, and adaptability by using ports and adapters.
The solution is typically split into multiple projects to enforce dependency rules.
This project contains the Product entity, the IProductRepository port, and a CreateProductUseCase application service.
// ProductService.Core/Entities/Product.cs
public class Product
{
public Guid Id { get; init; } = Guid.NewGuid();
public string Name { get; private set; }
public decimal Price { get; private set; }
public Product(string name, decimal price)
{
// Business rules and validation
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Product name cannot be empty.", nameof(name));
if (price <= 0)
throw new ArgumentException("Price must be greater than zero.", nameof(price));
Name = name;
Price = price;
}
}
// ProductService.Core/Ports/IProductRepository.cs
// This is a driven (outbound) port.
public interface IProductRepository
{
Task AddAsync(Product product);
Task<Product?> GetByIdAsync(Guid id);
}
// ProductService.Core/Application/CreateProductUseCase.cs
public class CreateProductUseCase
{
private readonly IProductRepository _productRepository;
public CreateProductUseCase(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public async Task Execute(string name, decimal price)
{
var product = new Product(name, price);
await _productRepository.AddAsync(product);
}
}
This project, which references the ProductService.Core project, provides the concrete implementation of the IProductRepository port.
// ProductService.Infrastructure/Adapters/Persistence/ProductRepository.cs
public class ProductRepository : IProductRepository
{
// In a real application, this would interact with a database
private readonly Dictionary<Guid, Product> _products = new();
public Task AddAsync(Product product)
{
_products[product.Id] = product;
return Task.CompletedTask;
}
public Task<Product?> GetByIdAsync(Guid id)
{
_products.TryGetValue(id, out var product);
return Task.FromResult(product);
}
}
This project, referencing both the Core and Infrastructure layers, serves as the entry point and wires everything together.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Register Core services
builder.Services.AddScoped<CreateProductUseCase>();
// Register Adapters
builder.Services.AddScoped<IProductRepository, ProductRepository>();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapPost("/products", async (string name, decimal price, CreateProductUseCase useCase) =>
{
await useCase.Execute(name, price);
return Results.Ok();
});
app.Run();
Sources: GeeksforGeeks, Java Code Geeks, Everestek Blog
| Onion Architecture in .NET | Micro-Frontends | |