- Clean Architecture
- Clean Code
- SOLID Principles
- KISS Principle
- DRY Principle
- Fail Fast Principle
- Common Closure Principle
- Common Reuse Principle
- Acyclic Dependencies Principle
- Mediator Pattern
- Result Pattern
- Folder-By-Feature Structure
- Simple and evolutionary architecture.
- Standardized and centralized flow for validation, security, log, result, etc.
- Avoid cyclical references.
- Avoid unnecessary dependency injection.
- Segregation by feature instead of technical type.
- Single responsibility for each request and response.
- Simplicity of unit testing.
var builder = WebApplication.CreateBuilder(args);
builder.Host.Serilog();
builder.Services.AddContextInMemoryDatabase();
builder.Services.AddJsonStringLocalizer();
builder.Services.AddClassesMatchingInterfaces();
builder.Services.AddMediator();
builder.Services.AddResponseCompression();
builder.Services.AddControllers().AddJsonOptions();
builder.Services.AddSwaggerGen();
var application = builder.Build();
application.UseException();
application.UseLocalization();
application.UseSwagger();
application.UseSwaggerUI();
application.UseHttpsRedirection();
application.UseResponseCompression();
application.MapControllers();
application.Run();
[ApiController]
[Route("customers")]
public class CustomerController : ControllerBase
{
private readonly IMediator _mediator;
public CustomerController(IMediator mediator) => _mediator = mediator;
[HttpPost]
public IActionResult Add(AddCustomerRequest request) => _mediator.HandleAsync<AddCustomerRequest, AddCustomerResponse>(request).PostResult();
[HttpDelete("{id:long}")]
public IActionResult Delete(long id) => _mediator.HandleAsync(new DeleteCustomerRequest(id)).DeleteResult();
[HttpGet("{id:long}")]
public IActionResult Get(long id) => _mediator.HandleAsync<GetCustomerRequest, GetCustomerResponse>(new GetCustomerRequest(id)).GetResult();
[HttpGet]
public IActionResult List() => _mediator.HandleAsync<ListCustomerRequest, ListCustomerResponse>(new ListCustomerRequest()).GetResult();
[HttpPut("{id:long}")]
public IActionResult Update(UpdateCustomerRequest request) => _mediator.HandleAsync(request).PutResult();
}
public sealed record AddCustomerRequest(string Name, string Email);
public sealed class AddCustomerRequestValidator : AbstractValidator<AddCustomerRequest>
{
public AddCustomerRequestValidator()
{
RuleFor(request => request.Name).Name();
RuleFor(request => request.Email).Email();
}
}
public sealed record AddCustomerResponse(long Id);
public sealed record AddCustomerHandler : IHandler<AddCustomerRequest, AddCustomerResponse>
{
private readonly ICustomerFactory _customerFactory;
private readonly ICustomerRepository _customerRepository;
private readonly IUnitOfWork _unitOfWork;
public AddCustomerHandler
(
ICustomerFactory customerFactory,
ICustomerRepository customerRepository,
IUnitOfWork unitOfWork
)
{
_customerFactory = customerFactory;
_customerRepository = customerRepository;
_unitOfWork = unitOfWork;
}
public async Task<Result<AddCustomerResponse>> HandleAsync(AddCustomerRequest request)
{
var customer = _customerFactory.Create(request);
_customerRepository.Add(customer);
await _unitOfWork.SaveChangesAsync();
var response = new AddCustomerResponse(customer.Id);
return Result<AddCustomerResponse>.Success(response);
}
}
public sealed record DeleteCustomerRequest(long Id);
public sealed class DeleteCustomerRequestValidator : AbstractValidator<DeleteCustomerRequest>
{
public DeleteCustomerRequestValidator()
{
RuleFor(request => request.Id).Id();
}
}
public sealed record DeleteCustomerHandler : IHandler<DeleteCustomerRequest>
{
private readonly ICustomerRepository _customerRepository;
private readonly IUnitOfWork _unitOfWork;
public DeleteCustomerHandler
(
ICustomerRepository customerRepository,
IUnitOfWork unitOfWork
)
{
_customerRepository = customerRepository;
_unitOfWork = unitOfWork;
}
public async Task<Result> HandleAsync(DeleteCustomerRequest request)
{
_customerRepository.Delete(request.Id);
await _unitOfWork.SaveChangesAsync();
return Result.Success();
}
}
public sealed record GetCustomerRequest(long Id);
public sealed class GetCustomerRequestValidator : AbstractValidator<GetCustomerRequest>
{
public GetCustomerRequestValidator()
{
RuleFor(request => request.Id).Id();
}
}
public sealed record GetCustomerResponse(CustomerModel Customer);
public sealed record GetCustomerHandler : IHandler<GetCustomerRequest, GetCustomerResponse>
{
private readonly ICustomerFactory _customerFactory;
private readonly ICustomerRepository _customerRepository;
public GetCustomerHandler
(
ICustomerFactory customerFactory,
ICustomerRepository customerRepository
)
{
_customerFactory = customerFactory;
_customerRepository = customerRepository;
}
public async Task<Result<GetCustomerResponse>> HandleAsync(GetCustomerRequest request)
{
var customer = await _customerRepository.GetAsync(request.Id);
if (customer is null) return Result<GetCustomerResponse>.Success();
var model = _customerFactory.Create(customer);
var response = new GetCustomerResponse(model);
return Result<GetCustomerResponse>.Success(response);
}
}
public sealed record ListCustomerRequest;
public sealed record ListCustomerResponse(IEnumerable<CustomerModel> Customers);
public sealed record ListCustomerHandler : IHandler<ListCustomerRequest, ListCustomerResponse>
{
private readonly ICustomerFactory _customerFactory;
private readonly ICustomerRepository _customerRepository;
public ListCustomerHandler
(
ICustomerFactory customerFactory,
ICustomerRepository customerRepository
)
{
_customerFactory = customerFactory;
_customerRepository = customerRepository;
}
public async Task<Result<ListCustomerResponse>> HandleAsync(ListCustomerRequest request)
{
var customers = await _customerRepository.ListAsync();
if (!customers.Any()) return Result<ListCustomerResponse>.Success();
var models = customers.Select(_customerFactory.Create);
var response = new ListCustomerResponse(models);
return Result<ListCustomerResponse>.Success(response);
}
}
public sealed record UpdateCustomerRequest(long Id, string Name, string Email);
public sealed class UpdateCustomerRequestValidator : AbstractValidator<UpdateCustomerRequest>
{
public UpdateCustomerRequestValidator()
{
RuleFor(request => request.Id).Id();
RuleFor(request => request.Name).Name();
RuleFor(request => request.Email).Email();
}
}
public sealed record UpdateCustomerHandler : IHandler<UpdateCustomerRequest>
{
private readonly ICustomerFactory _customerFactory;
private readonly ICustomerRepository _customerRepository;
private readonly IUnitOfWork _unitOfWork;
public UpdateCustomerHandler
(
ICustomerFactory customerFactory,
ICustomerRepository customerRepository,
IUnitOfWork unitOfWork
)
{
_customerFactory = customerFactory;
_customerRepository = customerRepository;
_unitOfWork = unitOfWork;
}
public async Task<Result> HandleAsync(UpdateCustomerRequest request)
{
var customer = _customerFactory.Create(request);
_customerRepository.Update(customer);
await _unitOfWork.SaveChangesAsync();
return Result.Success();
}
}
public sealed record CustomerModel(long Id, string Name, string Email);
public interface ICustomerFactory
{
Customer Create(AddCustomerRequest request);
Customer Create(UpdateCustomerRequest request);
CustomerModel Create(Customer customer);
}
public sealed record CustomerFactory : ICustomerFactory
{
public Customer Create(AddCustomerRequest request) => new(default, new Name(request.Name), new Email(request.Email));
public Customer Create(UpdateCustomerRequest request) => new(request.Id, new Name(request.Name), new Email(request.Email));
public CustomerModel Create(Customer customer) => new(customer.Id, customer.Name.Value, customer.Email.Value);
}