Skip to content

Clean Architecture

Principes

RIVORA Framework applique les principes de Clean Architecture de Robert C. Martin :

  • Independance des frameworks : le domaine metier ne depend d'aucun framework
  • Testabilite : la logique metier se teste sans UI, DB, ou serveur web
  • Independance de la UI : l'API REST peut etre remplacee par gRPC ou GraphQL
  • Independance de la DB : 4 providers supportes sans changer le domaine

Couches

Domain (centre)

Contient les entites, value objects, domain events et specifications. Zero dependance externe.

csharp
public class Product : AggregateRoot
{
    public string Name { get; private set; }
    public Money Price { get; private set; }
    public ProductStatus Status { get; private set; }

    public void Activate()
    {
        Status = ProductStatus.Active;
        AddDomainEvent(new ProductActivatedEvent(Id));
    }
}

Application

Orchestre les cas d'utilisation via CQRS (MediatR) :

csharp
public class CreateProductCommand : IRequest<ProductDto>
{
    public string Name { get; init; } = default!;
    public decimal Price { get; init; }
}

public class CreateProductHandler : IRequestHandler<CreateProductCommand, ProductDto>
{
    private readonly IProductRepository _repo;
    private readonly IMapper _mapper;

    public CreateProductHandler(IProductRepository repo, IMapper mapper)
    {
        _repo = repo;
        _mapper = mapper;
    }

    public async Task<ProductDto> Handle(CreateProductCommand cmd, CancellationToken ct)
    {
        var product = new Product(cmd.Name, cmd.Price);
        await _repo.AddAsync(product, ct);
        return _mapper.Map<ProductDto>(product);
    }
}

Infrastructure

Implemente les interfaces definies dans Domain/Application :

csharp
public class ProductRepository : BaseRepository<Product>, IProductRepository
{
    public ProductRepository(ApplicationDbContext context) : base(context) { }

    public async Task<Product?> GetByNameAsync(string name, CancellationToken ct)
    {
        return await _context.Products
            .FirstOrDefaultAsync(p => p.Name == name, ct);
    }
}

Presentation

Controllers minces qui delegent a MediatR :

csharp
[ApiController]
[Route("api/v1/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IMediator _mediator;

    [HttpPost]
    public async Task<ActionResult<ProductDto>> Create(CreateProductCommand command)
        => Ok(await _mediator.Send(command));

    [HttpGet("{id}")]
    public async Task<ActionResult<ProductDto>> Get(Guid id)
        => Ok(await _mediator.Send(new GetProductQuery(id)));
}

Validation

FluentValidation integre au pipeline MediatR :

csharp
public class CreateProductValidator : AbstractValidator<CreateProductCommand>
{
    public CreateProductValidator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
        RuleFor(x => x.Price).GreaterThan(0);
    }
}

Les erreurs de validation retournent automatiquement un 400 Bad Request avec les details.

Released under the MIT License.