Capítulo 35, Entrevistas
Perguntas de entrevista
As perguntas que mais aparecem em entrevistas sobre FastAPI e APIs, com a resposta que eu daria, a explicação por trás dela e o capítulo onde cada assunto é tratado.
Como usar este capítulo
Uma resposta curta mostra que você decorou, e uma resposta com o porquê mostra que você entendeu. Em cada pergunta, eu dou as duas coisas. Tente responder em voz alta antes de ler. As perguntas seguem a ordem do curso.
Fundamentos de API
O que é uma API e o que é uma rota? Uma API é um contrato entre quem pede e quem responde: que endereços existem, com quais verbos, com quais dados, e o que volta. Uma rota é um caminho (/usuarios) mais um verbo (GET), ligados a uma função que roda quando a requisição chega. (Capítulos 1 e 3.)
Qual a diferença entre GET, POST, PUT, PATCH e DELETE? O GET lê e não altera nada. O POST cria. O PUT substitui um recurso inteiro. O PATCH altera só parte dele. O DELETE remove. Um detalhe que impressiona: GET, PUT e DELETE são idempotentes (repetir a mesma requisição deixa o servidor no mesmo estado), e o POST normalmente não é (repetir cria outro recurso). (Capítulos 3 e 8.)
Qual a diferença entre parâmetro de caminho, de consulta e corpo? O caminho identifica o recurso (/usuarios/7). A consulta filtra ou ajusta (?ativo=true). O corpo carrega os dados que serão criados ou alterados. No FastAPI, o que está em {} na rota é caminho, um tipo simples fora dela é consulta, e um modelo do Pydantic é corpo. (Capítulos 4, 5 e 9.)
Por que o FastAPI devolve JSON por padrão? Porque o objetivo de uma API é trocar dados entre programas, e o JSON é leve, legível e entendido por praticamente toda linguagem. O FastAPI converte dicionários, listas e modelos para JSON automaticamente. (Capítulo 3.)
O que é o Swagger UI e de onde vem? É a página /docs, gerada a partir do documento OpenAPI que o FastAPI monta sozinho com as suas rotas e os seus tipos. Ela lista as rotas e deixa você testá-las. A /redoc mostra o mesmo documento de outra forma. (Capítulos 1 e 2.)
Por que usar FastAPI e não Flask ou Django? Não existe um "melhor". O FastAPI foi desenhado para APIs, com validação e documentação automáticas a partir dos tipos e suporte assíncrono desde o início. O Django é excelente para aplicações completas (painel administrativo, ORM, autenticação). O Flask é pequeno e flexível. Desconfie de quem responde "porque é o mais rápido": o desempenho depende muito mais do banco e do seu código do que do framework. (Capítulo 1.)
Pydantic, validação e erros
O que é o Pydantic e por que um modelo em vez de um dicionário? É a biblioteca que valida e converte dados a partir de anotações de tipo. Um dicionário aceita qualquer coisa, e você escreve cada checagem à mão. O modelo declara a estrutura uma vez e ganha validação, conversão, mensagens de erro e documentação. (Capítulos 6 e 7.)
O Pydantic recusa campos que o modelo não declara? Por padrão, não: ele os ignora em silêncio. Para recusá-los, configure extra="forbid", o que ajuda a pegar erros de digitação do cliente. (Capítulo 7.)
Para que serve o response_model? Para controlar o que sai: ele filtra o retorno da função pelo modelo, o que impede vazar campos sensíveis (como a senha), e valida a saída. Por isso eu separo os modelos de entrada, de saída e do banco. (Capítulo 10.)
Quando usar 200, 201 e 204? E 400, 401, 403, 404, 409 e 422? 200 para leitura ou alteração bem-sucedida, 201 quando algo foi criado, 204 quando deu certo e não há corpo (um DELETE). 400 para uma requisição inválida por regra de negócio, 401 quando o cliente não está autenticado, 403 quando está autenticado mas não tem permissão, 404 quando o recurso não existe, 409 para conflito com o estado atual (e-mail repetido) e 422 quando os dados não passam na validação. (Capítulo 11.)
Por que nunca devolver um erro com status 200? Porque o cliente olha o status primeiro. Uma resposta 200 com {"error": ...} diz "deu certo" e o código que consome a API segue como se tivesse dado. (Capítulo 11.)
Qual a diferença entre HTTPException, uma exceção própria e um tratador global? O HTTPException interrompe a rota e devolve um erro HTTP direto. Uma exceção própria expressa um erro do seu domínio, sem se preocupar com HTTP. O tratador global (@app.exception_handler) traduz uma exceção em uma resposta em um lugar só, o que evita repetir código e garante um formato de erro único. (Capítulo 12.)
Dependências e middleware
O que é injeção de dependências no FastAPI? É declarar, com Depends, uma função que a rota pede e o FastAPI executa e entrega. Serve para reaproveitar autenticação, paginação e acesso ao banco, sem copiar código. A mesma dependência usada duas vezes na mesma requisição roda uma vez. (Capítulo 13.)
Para que serve o yield em uma dependência? Para abrir um recurso antes da rota e fechá-lo depois, mesmo se ocorrer um erro: o padrão da sessão do banco. (Capítulos 13 e 16.)
Qual a diferença entre middleware e dependência? O middleware atravessa toda requisição, na ida e na volta, e enxerga a resposta: serve para tempo, identificador de requisição e compressão. A dependência vale só para as rotas que a pedem e recebe parâmetros validados: serve para autenticação e banco. (Capítulo 14.)
Em que ordem os middlewares executam? O último registrado fica por fora: é o primeiro a ver a requisição e o último a ver a resposta. (Capítulo 14.)
Banco de dados
O que é um ORM, e SQL puro ou ORM? É uma camada que mapeia classes Python para tabelas, e gera o SQL por você. SQL puro serve para scripts pequenos, e o ORM protege contra injeção de SQL e permite trocar de banco, o que o torna a escolha para uma API que vai crescer. (Capítulos 15 e 16.)
O que é injeção de SQL e como evitá-la? É quando um dado do usuário, montado dentro de uma string de SQL, muda a consulta (x' OR '1'='1 devolve todos os registros). Evita-se com parâmetros (? no sqlite3) ou com um ORM, e nunca com f-string ou concatenação. (Capítulo 15.)
Por que uma sessão por requisição? Qual a diferença entre commit e refresh? Uma sessão compartilhada entre requisições mistura transações e não é segura entre threads. O commit grava as alterações no banco. O refresh recarrega o objeto com o que está no banco, o que importa quando o banco gera valores (a data de criação). Por padrão, o commit expira os objetos, e acessá-los com a sessão já fechada dá DetachedInstanceError, motivo pelo qual se usa expire_on_commit=False ou refresh. (Capítulos 16 e 17.)
Paginação por página ou por cursor? Por página (OFFSET) permite pular para qualquer página, mas o custo cresce com a profundidade e, se os dados mudam entre duas requisições, itens são pulados ou repetidos. Por cursor ("depois do item X") tem custo constante e é estável, mas só avança. Em qualquer caso, a consulta precisa de ORDER BY. (Capítulo 27.)
O que é uma migração e por que não usar só o create_all? O create_all cria tabelas que não existem, e não altera as existentes. Uma migração (Alembic) versiona a evolução do esquema, e permite acrescentar uma coluna a uma tabela com dados reais. (Capítulo 34.)
Assincronia
Qual a diferença entre def e async def em uma rota? A rota def roda em uma pool de threads, e a async def roda no laço de eventos. A regra é combinar com a biblioteca: bibliotecas síncronas pedem def, e bibliotecas assíncronas pedem async def com await. (Capítulo 18.)
O que acontece se eu chamar time.sleep (ou uma biblioteca síncrona) dentro de uma rota async def? O laço de eventos para, e todas as outras requisições esperam. Eu medi: cinco chamadas de 0,2 segundo levaram cerca de 1 segundo, contra 0,2 segundo com await asyncio.sleep. A saída é asyncio.to_thread ou uma rota def. (Capítulo 18.)
Segurança
Como guardar senhas? Como hash lento com sal (Argon2, por exemplo), e nunca em texto puro nem com hashes rápidos como SHA-256. O sal faz a mesma senha gerar hashes diferentes, e a lentidão torna caro testar bilhões de senhas. (Capítulo 19.)
O que é um JWT? Ele é criptografado? É um token com três partes (cabeçalho, corpo e assinatura). Não é criptografado: qualquer pessoa lê o corpo decodificando Base64. A assinatura garante que o conteúdo não foi alterado. Por isso nada sensível vai nele. (Capítulo 19.)
Por que um token precisa de expiração, e como revogá-lo? Para limitar o estrago de um token vazado. Um JWT é sem estado, então o servidor não consegue cancelá-lo antes da expiração: por isso a validade é curta, e sessões longas usam um refresh token guardado no servidor, que pode ser revogado. (Capítulo 20.)
Por que o decode exige algorithms=[...]? Para impedir o ataque do algoritmo none, em que o atacante fabrica um token sem assinatura. Com a lista fixa, o servidor o recusa. (Capítulo 19.)
O que é CORS? É uma proteção do servidor? É uma regra do navegador que decide se o JavaScript de uma origem pode ler a resposta de outra. Não protege o servidor: curl e outros servidores o ignoram. E allow_origins=["*"] com credenciais é perigoso: o Starlette devolve a origem de quem perguntou, o que libera qualquer site com credenciais. (Capítulo 22.)
Como fazer upload com segurança? Nunca use o nome que o cliente mandou para decidir onde gravar (um ../ sai da pasta). Gere o nome no servidor, valide a extensão contra uma lista, limite o tamanho durante a leitura e não confie no content-type. (Capítulo 21.)
Como funciona o rate limiting, e o que pode dar errado? O balde de fichas, a janela fixa e a deslizante limitam as requisições por cliente em um período, e devolvem 429 com Retry-After. Atrás de um proxy, o IP visto é o do proxy, e com vários processos cada um conta o seu limite: para um limite global, o contador precisa de um armazenamento compartilhado. (Capítulo 29.)
Testes, configuração e produção
Como testar uma rota que usa banco e autenticação? Com o TestClient e app.dependency_overrides, trocando a sessão por um banco de teste e o usuário por um fixo. Cada teste deve começar com o banco limpo, senão a ordem de execução muda o resultado. (Capítulo 24.)
Como tratar configuração e segredos? Fora do código, em variáveis de ambiente (ou um .env que não vai para o Git), lidas por uma classe tipada (pydantic-settings) que recusa iniciar se faltar algo. (Capítulo 23.)
Como funciona o cache e qual a parte difícil? Guarda-se uma resposta por um tempo (TTL), e a parte difícil é saber quando ela deixa de valer. Cuidados: o cache em memória é por processo, dados por usuário precisam do usuário na chave, e o ETag permite ao cliente revalidar com um 304. (Capítulo 28.)
Como colocar uma API no ar? Dependências travadas, segredos no ambiente, uma rota de saúde, logs na saída padrão, fastapi run (sem recarga automática) na porta da plataforma, e vários processos conforme os núcleos, lembrando que o estado não é compartilhado entre eles. (Capítulo 30.)
Uma pergunta que sempre vem no fim
"Qual a decisão de que você mais se arrependeu em uma API?" Não há resposta certa, e é onde a entrevista vira conversa. A minha, com frequência: não ter limitado e ordenado as listas desde o primeiro dia. Uma rota que devolve "tudo" funciona com 50 registros e derruba tudo com 500 mil.