Clean Code Architecture C#: Guia Completo e Perguntas de Entrevista 2026
Domine os princípios de Clean Code e Clean Architecture em C# para criar aplicações .NET manuteníveis e testáveis. Prepare-se para entrevistas técnicas com exemplos práticos.

Clean Code Architecture em C# combina os princípios de Clean Code de Robert C. Martin com o padrão Clean Architecture para produzir aplicações .NET manuteníveis, testáveis e escaláveis. As entrevistas técnicas focam cada vez mais nesses conceitos porque revelam como um candidato pensa sobre design de software além de simplesmente fazer o código funcionar.
Quando os entrevistadores perguntam sobre Clean Architecture, esperam que os candidatos expliquem a regra de dependência: as dependências do código fonte apontam para dentro, em direção às políticas de nível superior. A camada Domain não conhece nada sobre Infrastructure, não o contrário.
Princípios SOLID como Base do Clean Code em C#
Os princípios SOLID formam a espinha dorsal do Clean Code em C#. A documentação oficial da Microsoft sobre fundamentos .NET recomenda esses padrões para aplicações empresariais. Cada princípio aborda um problema de manutenção específico.
Princípio da Responsabilidade Única (SRP): Uma classe tem apenas uma razão para mudar. O OrderService abaixo lida apenas com o processamento de pedidos, delegando persistência e notificações para componentes separados.
public class OrderService
{
private readonly IOrderRepository _orderRepository;
private readonly INotificationService _notificationService;
public OrderService(
IOrderRepository orderRepository,
INotificationService notificationService)
{
_orderRepository = orderRepository;
_notificationService = notificationService;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
// Validates and creates the domain entity
var order = Order.Create(request.CustomerId, request.Items);
// Persists through the repository abstraction
await _orderRepository.AddAsync(order);
// Notifies through a separate service
await _notificationService.SendOrderConfirmationAsync(order);
return order;
}
}Princípio Aberto/Fechado (OCP): Classes permanecem abertas para extensão mas fechadas para modificação. Novos métodos de pagamento requerem novas classes, não mudanças nas existentes.
public interface IPaymentProcessor
{
string PaymentMethod { get; }
Task<PaymentResult> ProcessAsync(Payment payment);
}
// StripePaymentProcessor.cs
public class StripePaymentProcessor : IPaymentProcessor
{
public string PaymentMethod => "Stripe";
public async Task<PaymentResult> ProcessAsync(Payment payment)
{
// Stripe-specific implementation
var charge = await _stripeClient.CreateChargeAsync(payment.Amount);
return new PaymentResult(charge.Id, charge.Status == "succeeded");
}
}
// PaymentService.cs
public class PaymentService
{
private readonly IEnumerable<IPaymentProcessor> _processors;
public PaymentService(IEnumerable<IPaymentProcessor> processors)
{
_processors = processors;
}
public async Task<PaymentResult> ProcessPaymentAsync(
Payment payment, string method)
{
var processor = _processors
.FirstOrDefault(p => p.PaymentMethod == method)
?? throw new NotSupportedException($"Payment method {method} not supported");
return await processor.ProcessAsync(payment);
}
}Adicionar suporte ao PayPal significa adicionar uma classe PayPalPaymentProcessor. O PaymentService permanece inalterado.
As Quatro Camadas da Clean Architecture em .NET
Clean Architecture organiza o código em camadas concêntricas. O repositório Clean Architecture de Jason Taylor fornece um template .NET amplamente adotado. Cada camada tem responsabilidades explícitas e as dependências fluem para dentro.
| Camada | Responsabilidade | Dependências |
|---|---|---|
| Domain | Entidades, objetos de valor, eventos de domínio | Nenhuma |
| Application | Casos de uso, DTOs, interfaces | Domain |
| Infrastructure | Banco de dados, APIs externas, sistema de arquivos | Application, Domain |
| Presentation | Controllers, views, endpoints de API | Application |
public class Customer
{
public Guid Id { get; private set; }
public string Email { get; private set; }
public CustomerStatus Status { get; private set; }
private Customer() { } // EF Core constructor
public static Customer Create(string email)
{
if (string.IsNullOrWhiteSpace(email))
throw new DomainException("Email cannot be empty");
return new Customer
{
Id = Guid.NewGuid(),
Email = email.ToLowerInvariant(),
Status = CustomerStatus.Active
};
}
public void Deactivate()
{
if (Status == CustomerStatus.Inactive)
throw new DomainException("Customer already inactive");
Status = CustomerStatus.Inactive;
}
}A entidade Domain encapsula as regras de negócio. Ela valida seus próprios invariantes e expõe comportamento através de métodos, não setters.
Setters privados impedem que código externo coloque entidades em estados inválidos. O método Deactivate() aplica a regra de que clientes inativos não podem ser desativados novamente. Este padrão aparece frequentemente em implementações de Domain-Driven Design e Clean Architecture.
Padrões de Injeção de Dependência para Clean Architecture
A Injeção de Dependência (DI) habilita a inversão de dependência requerida pela Clean Architecture. O .NET 10 inclui um container DI integrado que suporta injeção por construtor, tempos de vida com escopo e serviços com chave introduzidos no .NET 8.
var builder = WebApplication.CreateBuilder(args);
// Domain services - typically transient
builder.Services.AddTransient<IOrderValidator, OrderValidator>();
// Application services - scoped per request
builder.Services.AddScoped<IOrderService, OrderService>();
// Infrastructure - scoped to share DbContext
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
// External clients - singleton with HttpClient pooling
builder.Services.AddHttpClient<IPaymentGateway, StripeGateway>(client =>
{
client.BaseAddress = new Uri("https://api.stripe.com/v1/");
});
// Keyed services for multiple implementations (.NET 8+)
builder.Services.AddKeyedScoped<INotificationService, EmailNotificationService>("email");
builder.Services.AddKeyedScoped<INotificationService, SmsNotificationService>("sms");
var app = builder.Build();A camada Application define as interfaces. A camada Infrastructure as implementa. A camada Presentation (ou raiz de composição) conecta tudo.
Padrão Repository com Entity Framework Core 9
O padrão Repository abstrai o acesso a dados por trás de interfaces centradas no domínio. O EF Core 9, incluído com .NET 10, fornece o ORM subjacente.
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default);
Task<IReadOnlyList<Order>> GetByCustomerAsync(Guid customerId, CancellationToken ct = default);
Task AddAsync(Order order, CancellationToken ct = default);
void Update(Order order);
}
// Infrastructure/Repositories/OrderRepository.cs
public class OrderRepository : IOrderRepository
{
private readonly ApplicationDbContext _context;
public OrderRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
{
return await _context.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == id, ct);
}
public async Task<IReadOnlyList<Order>> GetByCustomerAsync(
Guid customerId, CancellationToken ct = default)
{
return await _context.Orders
.Where(o => o.CustomerId == customerId)
.OrderByDescending(o => o.CreatedAt)
.ToListAsync(ct);
}
public async Task AddAsync(Order order, CancellationToken ct = default)
{
await _context.Orders.AddAsync(order, ct);
}
public void Update(Order order)
{
_context.Orders.Update(order);
}
}O repositório retorna entidades de domínio, não DTOs. O mapeamento para DTOs acontece na camada Application, mantendo o Domain puro.
Pronto para mandar bem nas entrevistas de .NET?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Testes Unitários de Componentes Clean Architecture
Clean Architecture facilita os testes porque as dependências são injetadas através de interfaces. xUnit e NSubstitute fornecem o framework de testes e a biblioteca de mocks para .NET.
public class OrderServiceTests
{
private readonly IOrderRepository _orderRepository;
private readonly INotificationService _notificationService;
private readonly OrderService _sut;
public OrderServiceTests()
{
_orderRepository = Substitute.For<IOrderRepository>();
_notificationService = Substitute.For<INotificationService>();
_sut = new OrderService(_orderRepository, _notificationService);
}
[Fact]
public async Task CreateOrderAsync_ValidRequest_PersistsAndNotifies()
{
// Arrange
var request = new CreateOrderRequest(
CustomerId: Guid.NewGuid(),
Items: new[] { new OrderItemDto("SKU-001", 2, 29.99m) });
// Act
var order = await _sut.CreateOrderAsync(request);
// Assert
await _orderRepository.Received(1).AddAsync(Arg.Any<Order>());
await _notificationService.Received(1)
.SendOrderConfirmationAsync(Arg.Is<Order>(o => o.Id == order.Id));
}
[Fact]
public async Task CreateOrderAsync_EmptyItems_ThrowsDomainException()
{
// Arrange
var request = new CreateOrderRequest(
CustomerId: Guid.NewGuid(),
Items: Array.Empty<OrderItemDto>());
// Act & Assert
await Assert.ThrowsAsync<DomainException>(
() => _sut.CreateOrderAsync(request));
}
}Os testes verificam comportamento, não implementação. O _sut (system under test) interage com mocks. As asserções verificam que os métodos corretos foram chamados com os argumentos corretos.
Perguntas de Entrevista sobre Clean Architecture C#
As entrevistas técnicas avaliam a compreensão da Clean Architecture em múltiplos níveis. Essas perguntas aparecem em entrevistas .NET de nível sênior.
Os candidatos frequentemente confundem Clean Architecture com arquitetura N-tier. A diferença chave: na Clean Architecture, as dependências apontam para dentro, em direção ao Domain. Na N-tier, cada camada depende da que está abaixo, fazendo o Domain depender de Infrastructure.
P: Como a Clean Architecture difere da arquitetura em camadas tradicional?
A arquitetura em camadas tradicional tem cada camada dependendo da que está abaixo: Presentation depende de Business Logic, que depende de Data Access. Clean Architecture inverte isso: o Domain não tem dependências, Application depende do Domain, e Infrastructure depende de ambos. Esta inversão significa que a tecnologia de banco de dados pode mudar sem tocar nas regras de negócio.
P: Quando você escolheria não usar Clean Architecture?
Clean Architecture adiciona indireção. Para aplicações fortemente CRUD sem regras de negócio complexas, a sobrecarga excede o benefício. Uma API simples que faz proxy direto para tabelas de banco de dados não precisa de quatro camadas. O valor emerge quando a complexidade da lógica de negócio justifica a separação.
P: Como você lida com preocupações transversais como logging e caching?
Dois padrões funcionam bem: o padrão decorator e middleware. Um decorator de cache envolve a interface do repositório, implementando a mesma interface enquanto adiciona lógica de cache. Logging tipicamente usa middleware ou um interceptor de DI que envolve as chamadas de serviço sem poluir a lógica de negócio.
public class CachingOrderRepository : IOrderRepository
{
private readonly IOrderRepository _inner;
private readonly IDistributedCache _cache;
public CachingOrderRepository(
IOrderRepository inner,
IDistributedCache cache)
{
_inner = inner;
_cache = cache;
}
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
{
var cacheKey = $"order:{id}";
var cached = await _cache.GetStringAsync(cacheKey, ct);
if (cached is not null)
return JsonSerializer.Deserialize<Order>(cached);
var order = await _inner.GetByIdAsync(id, ct);
if (order is not null)
{
await _cache.SetStringAsync(
cacheKey,
JsonSerializer.Serialize(order),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
},
ct);
}
return order;
}
// Other methods delegate to _inner
}P: Como você estrutura a validação na Clean Architecture?
A validação acontece em dois níveis. A validação de domínio (invariantes) pertence às entidades: um pedido não pode ter zero itens. A validação de aplicação (validação de entrada) pertence aos handlers de comandos ou validadores: a requisição deve incluir um ID de cliente válido. FluentValidation se integra bem para validação de entrada, enquanto a validação de domínio permanece em construtores e métodos das entidades.
Organização do Código e Convenções de Nomenclatura
A estrutura do projeto comunica a arquitetura. O template padrão de Clean Architecture organiza projetos por camada.
src/
├── MyApp.Domain/
│ ├── Entities/
│ ├── ValueObjects/
│ ├── Events/
│ └── Exceptions/
├── MyApp.Application/
│ ├── Common/
│ │ ├── Interfaces/
│ │ └── Behaviors/
│ ├── Orders/
│ │ ├── Commands/
│ │ ├── Queries/
│ │ └── EventHandlers/
│ └── Customers/
├── MyApp.Infrastructure/
│ ├── Persistence/
│ ├── Services/
│ └── Configuration/
└── MyApp.WebApi/
├── Controllers/
├── Filters/
└── Middleware/Os cortes verticais (Orders, Customers) agrupam casos de uso relacionados. Esta organização escala melhor que cortes horizontais (Commands, Queries) conforme a aplicação cresce.
Considerações de Performance na Clean Architecture
A abstração tem um custo. Cada chamada de interface adiciona indireção. Estas práticas minimizam a sobrecarga enquanto preservam a testabilidade.
Usar records para DTOs: Records geram implementações eficientes de Equals e GetHashCode. São imutáveis por padrão, prevenindo mutações acidentais.
public record OrderDto(
Guid Id,
Guid CustomerId,
IReadOnlyList<OrderItemDto> Items,
decimal Total,
DateTime CreatedAt);
public record OrderItemDto(
string Sku,
int Quantity,
decimal UnitPrice);Evitar sobre-abstração: Nem toda classe precisa de uma interface. Abstrair dependências externas (banco de dados, clientes HTTP, sistema de arquivos). Serviços de domínio internos que têm uma única implementação raramente precisam de interfaces.
Perfilar antes de otimizar: A sobrecarga das camadas de Clean Architecture é tipicamente insignificante comparada com operações de I/O. Uma consulta de banco de dados levando 50ms ofusca os microssegundos gastos em dispatch de métodos.
Aplicando Princípios Clean Code a Métodos C#
Clean Code foca na legibilidade no nível de métodos e classes. Estas práticas se aplicam independentemente do padrão arquitetural.
Métodos fazem uma única coisa: Um método chamado ProcessOrderAndSendEmail viola SRP. Dividi-lo em ProcessOrder e SendOrderConfirmation.
Nomes significativos: CalculateOrderTotal comunica intenção. DoCalculation não. Nomes de variáveis seguem a mesma regra: customerOrders ao invés de list.
Métodos pequenos: Se um método excede 20 linhas, provavelmente faz demais. Extrair métodos auxiliares com nomes descritivos.
// Before: Long method doing multiple things
public decimal CalculateInvoiceTotal(Invoice invoice)
{
decimal subtotal = 0;
foreach (var item in invoice.Items)
{
subtotal += item.Quantity * item.UnitPrice;
}
decimal discount = 0;
if (invoice.Customer.IsPreferred)
{
discount = subtotal * 0.1m;
}
decimal tax = (subtotal - discount) * 0.2m;
return subtotal - discount + tax;
}
// After: Small methods with single responsibilities
public decimal CalculateInvoiceTotal(Invoice invoice)
{
var subtotal = CalculateSubtotal(invoice.Items);
var discount = CalculateDiscount(subtotal, invoice.Customer);
var tax = CalculateTax(subtotal - discount);
return subtotal - discount + tax;
}
private decimal CalculateSubtotal(IEnumerable<InvoiceItem> items)
=> items.Sum(item => item.Quantity * item.UnitPrice);
private decimal CalculateDiscount(decimal subtotal, Customer customer)
=> customer.IsPreferred ? subtotal * 0.1m : 0;
private decimal CalculateTax(decimal taxableAmount)
=> taxableAmount * 0.2m;A versão refatorada se lê como um resumo da lógica de negócio. Cada método auxiliar pode ser testado independentemente.
Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Pontos-Chave para Clean Code Architecture em C#
- A regra de dependência: as dependências do código apontam para dentro, de Infrastructure em direção ao Domain, nunca para fora
- Os princípios SOLID guiam o design de classes: responsabilidade única, aberto para extensão, substituição de Liskov, segregação de interfaces, inversão de dependências
- Quatro camadas separam as preocupações: Domain (entidades), Application (casos de uso), Infrastructure (sistemas externos), Presentation (API/UI)
- A injeção de dependência conecta as camadas na raiz de composição, tipicamente em
Program.cs - O padrão Repository abstrai o acesso a dados por trás de interfaces centradas no domínio que retornam entidades, não DTOs
- Os testes unitários verificam comportamento usando mocks de interfaces, validando que os métodos corretos recebem os argumentos corretos
- Clean Code no nível de métodos: métodos pequenos, nomes significativos, responsabilidades únicas
- O custo de performance da abstração é tipicamente insignificante comparado com operações de I/O; perfilar antes de otimizar
- As perguntas de entrevista avaliam a compreensão da regra de dependência, trade-offs e padrões práticos como decorators para preocupações transversais
Você saberia encontrar o bug em .NET?
Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Escrito por
Anthony Fillion-MailletFundador da SharpSkill
Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.
Atualizado em 24 de agosto de 2026
Tags
Compartilhar
Artigos relacionados

LINQ Avançado em C# em 2026: Operadores, Performance e Perguntas de Entrevista
Dominar operadores LINQ avançados, otimizar a performance de consultas e se preparar para entrevistas técnicas C# com exemplos práticos.

Clean Architecture com .NET: Guia Prático
Domine a Clean Architecture em .NET com C#. Aprenda os princípios SOLID, a separação de camadas e os padrões de implementação para aplicações sustentáveis.

Perguntas de Entrevista C# e .NET: Guia Completo 2026
As 25 perguntas mais comuns em entrevistas de C# e .NET. LINQ, async/await, injeção de dependência, Entity Framework e boas práticas com respostas detalhadas.