Conectando a API ao Banco de Dados

Conectando a API ao Banco de Dados

A API ganha persistência real e, com ela, um conceito que estrutura aplicações profissionais: a injeção de dependência. A aula mostra como registrar o acesso ao banco num lugar central e recebê-lo nas rotas, e por que esse padrão vale muito além do desenvolvimento de APIs.
Linguagem C#

• • 11 min de leitura

Na aula anterior, construímos uma API REST, mas com uma limitação séria: os dados viviam numa lista em memória que desaparecia a cada reinício do programa. Agora unimos as duas metades desta fase — a web da aula passada e a persistência das aulas de banco de dados — conectando a API a um banco real com o Entity Framework Core. E, ao fazê-lo, encontraremos um conceito que é um dos mais importantes do desenvolvimento profissional moderno, e que o ASP.NET Core torna natural: a injeção de dependência. Ela responde a uma pergunta que se insinuava: como as rotas da nossa API obtêm acesso ao banco de dados de forma organizada? A resposta — e o princípio de projeto por trás dela — vai muito além de APIs, e é uma marca do código bem estruturado. Esta aula une o que você já sabe e adiciona uma peça valiosa.

A pergunta: quem cria o acesso ao banco?

Nossas rotas precisarão de um contexto do EF Core (o LojaContext da Extensão #02) para conversar com o banco. A abordagem ingênua seria cada rota criar o seu próprio:

// Abordagem ingênua e problemática: cada rota cria seu próprio contexto.
app.MapGet("/produtos", async () =>
{
    using var db = new LojaContext();   // a rota conhece e cria o acesso ao banco
    return await db.Produtos.ToListAsync();
});

Isso funcionaria, mas traz problemas. A rota fica amarrada à criação concreta do contexto — se a forma de configurá-lo mudar (uma nova string de conexão, uma configuração diferente), você teria de alterar todas as rotas. Há duplicação — cada rota repete a criação. E testar a rota isoladamente fica difícil, pois ela sempre criará um banco real. O princípio violado é aquele que vimos na Extensão #03: dependa de abstrações, não de criações concretas espalhadas pelo código. A injeção de dependência resolve isso de forma elegante.

A ideia da injeção de dependência

A injeção de dependência inverte quem cria o quê. Em vez de cada parte do programa criar as coisas de que precisa, ela as recebe prontas, de fora, de um "provedor central" que sabe construí-las. Você registra, num único lugar, como cada coisa é criada; e, onde precisar dela, apenas a declara como necessária — e o sistema a entrega. A analogia: em vez de cada funcionário de uma oficina fabricar sua própria ferramenta, existe um almoxarifado central que entrega a ferramenta certa a quem a pede. Isso centraliza a configuração (muda-se num lugar só), elimina a duplicação, e facilita testes (pode-se entregar uma versão de teste da dependência). O ASP.NET Core tem esse "almoxarifado" embutido, e usá-lo é natural.

Passo 1: registrar o acesso ao banco

Primeiro, preparamos o projeto: adicionamos as bibliotecas do EF Core (como na Extensão #02) e registramos o contexto no "almoxarifado central". No Program.cs, antes do builder.Build():

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// REGISTRA o LojaContext no sistema de injeção, dizendo COMO criá-lo.
// Agora o ASP.NET Core sabe fabricar um LojaContext quando alguém precisar.
builder.Services.AddDbContext<LojaContext>(opcoes =>
    opcoes.UseNpgsql("Host=localhost;Database=loja_db;Username=curso;Password=senha123"));

var app = builder.Build();

O builder.Services.AddDbContext<LojaContext>(...) é o registro: ele ensina o sistema a criar um LojaContext já configurado com a string de conexão. A configuração da conexão agora vive num único lugar — não mais espalhada pelas rotas. (Com esse registro, o LojaContext recebe a configuração de fora, então não precisa mais do método OnConfiguring que tinha na Extensão #02 — um detalhe que o registro cuida.)

Passo 2: receber o acesso nas rotas por injeção

Agora a parte elegante: as rotas simplesmente declaram que precisam de um LojaContext, como um parâmetro, e o ASP.NET Core o injeta automaticamente — cria um, entrega à rota, e o libera ao final. As rotas não criam nada; apenas recebem o que pedem:

// A rota DECLARA que precisa de um LojaContext (o parâmetro 'db'); o sistema o INJETA.
app.MapGet("/produtos", async (LojaContext db) =>
    await db.Produtos.ToListAsync());   // consulta o banco (LINQ + assincronia)

app.MapGet("/produtos/{id}", async (int id, LojaContext db) =>
{
    var produto = await db.Produtos.FindAsync(id);
    return produto != null ? Results.Ok(produto) : Results.NotFound();
});

app.MapPost("/produtos", async (Produto novo, LojaContext db) =>
{
    db.Produtos.Add(novo);
    await db.SaveChangesAsync();        // persiste no banco (fase de concorrência)
    return Results.Created($"/produtos/{novo.Id}", novo);
});

app.MapDelete("/produtos/{id}", async (int id, LojaContext db) =>
{
    var produto = await db.Produtos.FindAsync(id);
    if (produto == null) return Results.NotFound();
    db.Produtos.Remove(produto);
    await db.SaveChangesAsync();
    return Results.NoContent();
});

app.Run();

Observe o parâmetro LojaContext db em cada rota. Você não escreve new LojaContext() em lugar nenhum — o ASP.NET Core vê que a rota precisa de um, consulta o registro (onde você o configurou), cria uma instância pronta, injeta-a, e a libera depois. A rota fica limpa, focada só na sua lógica, e a criação do acesso ao banco é responsabilidade do sistema. Compare com a versão ingênua do início: sumiu o using var db = new..., e com ele a amarração e a duplicação. E note como tudo se conecta: o LINQ e o SaveChangesAsync da fase de bancos, a assincronia (async/await) da fase de concorrência, os códigos de resposta e o tratamento de nulo — todos reaparecem, agora numa API que persiste de verdade.

A injeção de dependência além das APIs

Encerro com a perspectiva maior, pois é o que torna este conceito valioso. A injeção de dependência não é um truque do ASP.NET Core; é um princípio de projeto fundamental, usado em praticamente todo sistema profissional. Em qualquer programa não trivial, as partes dependem umas das outras — um serviço depende de um acesso a dados, que depende de uma configuração. Fazer cada parte criar suas próprias dependências gera um emaranhado rígido, difícil de mudar e de testar. A injeção de dependência desata esse nó: cada parte declara o que precisa, e um sistema central monta tudo. Isso concretiza a "dependência de abstrações" que vimos na Extensão #03 e a testabilidade da fase de testes (você pode injetar uma versão falsa de uma dependência nos testes). É por isso que a injeção de dependência é onipresente no C# profissional e além — e agora você não só sabe usá-la, como entende por que ela existe.

A API deixou de guardar dados em memória e passou a conversar com um banco de verdade. E, no caminho, apareceu um conceito que estrutura aplicações profissionais inteiras: em vez de cada parte do código criar por conta própria aquilo de que precisa, as dependências são registradas num lugar central e entregues a quem as declara.

A injeção de dependência resolve de uma vez o controle do tempo de vida dos recursos, a facilidade de substituir uma implementação por outra e a testabilidade do código. Ela não é um recurso de APIs nem do ASP.NET Core — é um padrão de projeto de aplicação, e reconhecê-lo aqui ajuda a identificá-lo em praticamente qualquer sistema bem estruturado.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Pegue a API da aula anterior, adicione as bibliotecas do EF Core, registre o LojaContext com AddDbContext e a string de conexão do seu PostgreSQL, e aplique as migrações. Confirme que o banco está pronto para a API.

Ver resposta

✓ Resposta: Após adicionar as bibliotecas do EF Core e do Npgsql, registra-se no Program.cs: builder.Services.AddDbContext<LojaContext>(o => o.UseNpgsql("Host=localhost;Database=loja_db;Username=curso;Password=senha123"));. Com o LojaContext e a classe Produto definidos, dotnet ef migrations add ApiInicial e dotnet ef database update criam a tabela. O banco fica pronto para a API usar.

Exercício 2

Converta as quatro rotas para receberem o LojaContext por injeção (o parâmetro LojaContext db) e persistirem no banco com ToListAsync, FindAsync e SaveChangesAsync. Teste criando um produto via POST e confirmando, após reiniciar a API, que ele ainda está lá (persistência real).

Ver resposta

✓ Resposta: Cada rota passa a declarar LojaContext db como parâmetro e a usar o banco: o GET da lista com await db.Produtos.ToListAsync(), o GET por id com FindAsync, o POST com Add + SaveChangesAsync, o DELETE com Remove + SaveChangesAsync. Criando um produto via POST e reiniciando a API, um GET /produtos seguinte ainda o mostra — confirmando que agora os dados vivem no banco, não em memória volátil, e persistem entre execuções.

Exercício 3

Compare a rota MapGet("/produtos", ...) na versão ingênua (que fazia new LojaContext()) com a versão por injeção. Liste dois problemas concretos que a injeção de dependência resolve.

Ver resposta

✓ Resposta: A versão injetada resolve, entre outros: (a) a amarração à criação concreta — na versão ingênua, cada rota chamava new LojaContext() e conhecia a forma de construí-lo; se a configuração (como a string de conexão) mudasse, todas as rotas teriam de mudar. Com injeção, a configuração vive só no registro, num único lugar, e alterá-la afeta um ponto só. (b) A duplicação — a versão ingênua repetia a criação do contexto em cada rota; com injeção, a rota apenas declara que precisa de um contexto, sem repetir a criação, deixando o código mais limpo e a responsabilidade de criar centralizada no sistema.

Exercício 4

Explique, com suas palavras e a analogia do almoxarifado, o que é a injeção de dependência. Quem cria as dependências na abordagem ingênua, e quem as cria com injeção?

Ver resposta

✓ Resposta: A injeção de dependência é um mecanismo em que as partes de um programa, em vez de criarem as coisas de que precisam, as recebem prontas de um "provedor central". Na analogia do almoxarifado: em vez de cada funcionário fabricar sua própria ferramenta, existe um almoxarifado central que entrega a ferramenta certa a quem a pede — o funcionário só precisa dizer o que precisa. Na abordagem ingênua, cada parte (cada rota) cria suas próprias dependências (cada funcionário fabrica sua ferramenta), gerando duplicação e amarração. Com injeção, o sistema central (o almoxarifado, o ASP.NET Core) cria as dependências e as entrega a quem as declara necessárias (o almoxarifado fornece a ferramenta pronta), e a parte apenas recebe o que pediu.

Exercício 5

Sem código: a aula afirma que a injeção de dependência facilita os testes. Com base no que você aprendeu na fase de testes, explique por que receber uma dependência de fora (em vez de criá-la internamente) torna uma rota ou um método mais fácil de testar.

Ver resposta

✓ Resposta: Receber uma dependência de fora torna o código mais fácil de testar porque, como a rota ou o método não cria a dependência internamente, é possível, num teste, entregar-lhe uma versão controlada dessa dependência em vez da real. Por exemplo, em vez de a rota sempre usar o banco de dados real (lento e que exigiria um banco disponível), o teste poderia fornecer um acesso a dados falso ou em memória, preparado para o teste. Isso permite verificar a lógica da rota isoladamente, de forma rápida e previsível, sem depender de um banco real no ar. Se a rota criasse o próprio contexto internamente (a abordagem ingênua), ela sempre usaria o banco real, e não haveria como substituí-lo por uma versão de teste — tornando o teste lento, frágil e dependente de infraestrutura externa. Receber a dependência de fora dá ao teste o controle necessário para isolar o que está sendo verificado.

Comentários

Mais em Linguagem C#

Projeto Capstone: Reunindo Tudo
Projeto Capstone: Reunindo Tudo

O projeto que integra o curso inteiro: um gerenciador de tarefas completo, com…

Operadores e Expressões: Fazendo Contas e Combinando Valores
Operadores e Expressões: Fazendo Contas e Combinando Valores

Como fazer contas e combinar valores em C#: os operadores aritméticos, o resto…

Laços de Repetição: while e for
Laços de Repetição: while e for

Os dois laços fundamentais do C#: o while, que repete enquanto uma condição…