/AlphaCentauri

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.

Primary LanguageC#MIT LicenseMIT

AlphaCentauri

Principles and Patterns

  • 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

Benefits

  • 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.

API

Program

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();

Controller

[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();
}

APPLICATION

Add

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);
    }
}

Delete

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();
    }
}

Get

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);
    }
}

List

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);
    }
}

Update

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();
    }
}

Model

public sealed record CustomerModel(long Id, string Name, string Email);

Factory

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);
}