O ASP.NET Core se tornou o framework preferido de muitos desenvolvedores devido à sua flexibilidade, desempenho e amplo ecossistema. Seja para criar microsserviços, aplicações web ou soluções corporativas, entender as melhores práticas para o design de APIs REST pode diminuir significativamente a manutenção e aumentar o desempenho do seu projeto. Neste artigo, mostrarei as técnicas e dicas que uso para criar APIs escaláveis com facilidade.
Melhores práticas da API RESTful
Aderir aos princípios REST
REST (Representational State Transfer) é mais do que um estilo de programação, é um conjunto de princípios arquitetônicos que levam a APIs escaláveis e sustentáveis.
Abaixo estão os quais considero mais importantes “ilustrando” com exemplos de código.
Api Sem Estado
Cada solicitação deve incluir todas as informações necessárias para executar. Por exemplo, usar tokens para autenticação evita manter sessões do lado do servidor.
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetOrder(int id)
{
var order = _orderService.GetOrderById(id);
return order != null ? Ok(order) : NotFound();
}
}
URIs baseados em recursos
Use URLs claras e lógicas para representar recursos. Por exemplo:
// URL: /api/products
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
var products = _productService.GetAllProducts();
return Ok(products);
}
}
Uso de métodos HTTP padrão
Implemente operações CRUD usando os verbos HTTP apropriados:
// GET, POST, PUT, DELETE methods in the same controller
[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetCustomer(int id)
{
/* ... */
}
[HttpPost]
public IActionResult CreateCustomer([FromBody] Customer customer)
{
/* ... */
}
[HttpPut("{id}")]
public IActionResult UpdateCustomer(int id, [FromBody] Customer customer)
{
/* ... */
}
[HttpDelete("{id}")]
public IActionResult DeleteCustomer(int id)
{
/* ... */
}
}
Implementar controle de versão
À medida que sua API evolui, o controle de versão impede incompatibilidad eentre alterações significativas. O ASP.NET Core oferece suporte a diversas estratégias de controle de versão. You can read more at API Versioning with ASP.NET and Swagger UI.
Controle de versão do Path de URL
// URL: /api/v1/products
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts() { /* ... */ }
}
Controle de versão pela string de consulta
// URL: /api/products?api-version=1.0
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ApiVersionReader = new QueryStringApiVersionReader("api-version");
});
Controle de versão de cabeçalho
// Use a custom header, e.g., "X-API-Version"
services.AddApiVersioning(options =>
{
options.ApiVersionReader = new HeaderApiVersionReader("X-API-Version");
});
Garantir a segurança
Segurança É OBRIGATÓRIA para qualquer API! O ASP.NET Core oferece diversos recursos de segurança integrados, como:
Autenticação e Autorização
// Configure JWT authentication in Startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
Validação de dados
public class Product
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[Range(0.01, double.MaxValue)]
public decimal Price { get; set; }
}
Limitação de taxa
Considere soluções de middleware como AspNetCoreRateLimit para evitar abusos.
// In Startup.cs - Configure rate limiting options
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
services.AddInMemoryRateLimiting();
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
Otimização e Desempenho
O desempenho é fundamental para uma boa experiência do usuário. Práticas essenciais incluem cache, programação assíncrona (muito importante) e acesso eficiente aos dados.
Cache
Este é um exemplo simples usando um cache de memória, mas você pode usar um mecanismo mais sofisticado como o Redis.
// Using in-memory caching
public class ProductsController : ControllerBase
{
private readonly IMemoryCache _cache;
public ProductsController(IMemoryCache cache) => _cache = cache;
[HttpGet]
public IActionResult GetProducts()
{
var cacheKey = "productList";
if (!_cache.TryGetValue(cacheKey, out List<Product> products))
{
products = _productService.GetAllProducts();
var cacheEntryOptions = new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(5));
_cache.Set(cacheKey, products, cacheEntryOptions);
}
return Ok(products);
}
}
Programação Assíncrona
Isso é OBRIGATÓRIO para cenários de alta carga porque libera o thread para o pool de threads enquanto aguarda qualquer E/S, como acesso ao banco de dados.
[HttpGet("{id}")]
public async Task<IActionResult> GetProductAsync(int id)
{
var product = await _productService.GetProductByIdAsync(id);
return product != null ? Ok(product) : NotFound();
}
Acesso eficiente a dados
O segredo aqui é usar AsNoTracking ao consultar dados que você não atualizará ou excluirá. Este método informa ao Microsoft Entity Framework (EF) para interromper o monitoramento da entidade.
// Using Entity Framework Core with async queries
public async Task<List<Product>> GetAllProductsAsync()
{
return await _context.Products.AsNoTracking().ToListAsync();
}
Integração de middleware no ASP.NET Core
O middleware é essencial para o pipeline de processamento de solicitações do ASP.NET Core. Veja abaixo exemplos de middleware personalizado e integração de middleware de terceiros.
Construindo Middleware Personalizado
Um middleware personalizado pode ser usado para lidar com exceções, registros e outras ações. Por exemplo, um manipulador de exceções global.
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception occurred.");
context.Response.StatusCode = 500;
await context.Response.WriteAsync("An unexpected error occurred. Please try again later.");
}
}
}
Registrando o middleware no Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<GlobalExceptionMiddleware>();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Integração de middleware de terceiros
O ASP.NET Core tem um rico ecossistema de middleware para tarefas como logs, CORS e compactação de resposta.
Registro de eventos (log) com Serilog
Abaixo tem um exemplo simples do uso do Serilog. Veja as postagns Enhance logging in .NET Core Web API e Set up Datalust Seq in a Docker Container para entender o Serilog com mais profundidade.
// In Program.cs
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
hostBuilder.UseSerilog();
Configuração CORS
// In Startup.cs, configure CORS policy
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.WithOrigins("http://example.com")
.AllowAnyHeader()
.AllowAnyMethod());
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors("AllowSpecificOrigin");
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
Compressão de Resposta
// In Startup.cs, add response compression
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
Usando a injeção de dependência (DI)
O contêiner DI integrado do ASP.NET Core simplifica o gerenciamento de serviços, melhora a capacidade de testes e promove uma separação clara das responsabilidades das classes.
Por que usar a injeção de dependência?
O DI ajuda a reduzir o acoplamento rígido injetando dependências em vez de codificá-las. Isso resulta em um código mais sustentável e testável.
Registro de Serviços
Registre serviços no método ConfigureServices com tempos de vida apropriados:
public void ConfigureServices(IServiceCollection services)
{
// Transient: New instance per use
services.AddTransient<INotificationService, EmailNotificationService>();
// Scoped: One instance per request
services.AddScoped<IProductService, ProductService>();
// Singleton: One instance for the entire application lifetime
services.AddSingleton<ILoggingService, LoggingService>();
services.AddControllers();
}
Injetando serviços em controladores
Após configurar o registro dos serviços, injete-os nos controladores por meio de injeção de construtor:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
private readonly ILoggingService _loggingService;
public ProductsController(IProductService productService, ILoggingService loggingService)
{
_productService = productService;
_loggingService = loggingService;
}
[HttpGet]
public async Task<IActionResult> GetProducts()
{
_loggingService.Log("Fetching all products.");
var products = await _productService.GetAllProductsAsync();
return Ok(products);
}
}
Testando com DI
Os testes unitários tornam-se mais fáceis quando as dependências podem ser simuladas (mocks). Por exemplo, usar o Framework Moq para testes
public class ProductsControllerTests
{
[Fact]
public async Task GetProducts_ReturnsOkResult_WithProductList()
{
// Arrange
var mockService = new Mock<IProductService>();
mockService.Setup(service => service.GetAllProductsAsync())
.ReturnsAsync(new List<Product> { new Product { Id = 1, Name = "Test Product", Price = 9.99M } });
var mockLogger = new Mock<ILoggingService>();
var controller = new ProductsController(mockService.Object, mockLogger.Object);
// Act
var result = await controller.GetProducts();
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var products = Assert.IsAssignableFrom<IEnumerable<Product>>(okResult.Value);
Assert.Single(products);
}
}
Práticas recomendadas adicionais para design de API escalável
Documentação e Testes
Documentação de API com Swagger
Integre o Swagger para gerar automaticamente documentação de API interativa.
// In Startup.cs - Configure Swagger services
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
Testes automatizados
Além dos testes unitários, considere os testes de integração usando o TestServer na memória:
public class IntegrationTests
{
[Fact]
public async Task GetProducts_EndpointReturnsSuccess()
{
// Arrange
var builder = WebApplication.CreateBuilder();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
var client = app.GetTestClient();
// Act
var response = await client.GetAsync("/api/products");
// Assert
response.EnsureSuccessStatusCode();
}
}
Monitoramento e Registro
O registro e o monitoramento robustos são cruciais para ambientes de produção
Usando o Application Insights
Leia mais sobre o Application Insights em learn.microsoft.com
// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration["ApplicationInsights:InstrumentationKey"]);
services.AddControllers();
}
Exemplo de Log estruturado
Isso é importante para serviços de log como SEQ
// In a service or middleware
_logger.LogInformation("User {UserId} performed action {Action} at {Time}", userId, action, DateTime.UtcNow);
Integração e Implantação Contínuas (CI/CD)
Automatize suas compilações e implantações com pipelines de CI/CD. Veja um exemplo de configuração YAML para GitHub Actions:
name: ASP.NET Core CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
Conclusão
Construir APIs RESTful robustas com ASP.NET Core requer atenção aos princípios de design, integração eficaz de middleware e o uso da injeção de dependências para um código modular e testável. Seguindo as melhores práticas para APIs REST discutidas, aderindo às convenções RESTful, integrando middleware de forma eficiente e utilizando DI, você pode criar APIs escaláveis, sustentáveis e de alto desempenho. Além disso, incorporar documentação, testes automatizados e monitoramento garante que sua API permaneça confiável em produção.
Estes exemplos de código expandidos fornecem insights práticos sobre como cada componente funciona no ecossistema ASP.NET Core. Seja iniciando um novo projeto ou aprimorando um existente, estas estratégias ajudarão você a criar APIs que evoluem com as necessidades do seu negócio, mantendo a excelência em desempenho e segurança.
Até mais!
