Contract First ou Morra Tentando: A Única Maneira Sensata de Projetar APIs

Este post foi originalmente escrito em inglês. A tradução pode não refletir 100% das ideias originais do autor.

Preciso desabafar. Algo que tem apodrecido na minha alma desde a primeira vez que entrei em um projeto em meio ao desenvolvimento e fiz a pergunta fatídica: “Onde está a documentação da API?”

A resposta, invariavelmente, era uma das seguintes:

  1. “Confira a coleção do Postman.” (Tradução: um cemitério de 200 requisições, metade das quais desatualizadas, com nomes como GET users FINAL v2 (cópia))
  2. “É só olhar o código.” (Tradução: faça engenharia reversa do nosso espaguete e boa sorte)
  3. “Vamos documentar depois.” (Tradução: nunca vamos documentar)

Eu Desisto!

Isso, meus amigos, é a consequência do design de API Code First. E estou aqui para dizer que é uma doença. Uma doença contagiosa, que mata projetos e se espalha pelas equipes como um vírus em um escritório mal ventilado.

A Cena do Crime: Code First

Deixe-me pintar um quadro. Você é um desenvolvedor backend. Recebe um ticket: “Criar endpoint para buscar transações do usuário.” Você abre sua IDE, cria um Controller, conecta um Service, retorna algum DTO que inventou na hora e faz push para develop. Pronto. Envie.

Enquanto isso, o desenvolvedor frontend está esperando. Ele pergunta: “Como é a resposta?” Você diz: “É só chamar e ver.” Ele chama. Vê um campo chamado txn_dt. Ele pergunta: “Isso é um timestamp? Uma string? Qual fuso horário?” Você dá de ombros. “É o que o LocalDateTime do Java serializa por padrão.”

Parabéns. Você acabou de criar um desastre de comunicação embrulhado em um sanduíche de dívida técnica.

O desenvolvedor mobile, que entrou na call cinco minutos atrasado, agora precisa implementar o mesmo endpoint. Ele olha para a “documentação” (a coleção do Postman, lembra?) e encontra uma requisição de três sprints atrás que retorna uma estrutura completamente diferente porque alguém refatorou o DTO e esqueceu de atualizar qualquer coisa.

Isso é Code First. Você escreve o código, e o contrato é o que o código acaba produzindo. A especificação da API é uma reflexão tardia, um efeito colateral, um acidente.

A Salvação: Contract First

Agora imagine um universo paralelo onde a sanidade prevalece.

Antes de qualquer um escrever uma única linha de Java, Go ou qualquer veneno que você prefira, a equipe se senta e define o contrato. Este contrato é um arquivo .yaml ou .json escrito em OpenAPI (antigo Swagger). Ele especifica:

  • Cada endpoint.
  • Cada método HTTP.
  • Cada parâmetro de requisição e seu tipo.
  • Cada corpo de resposta e sua estrutura.
  • Cada código de erro possível e o que significa.

Este arquivo se torna a fonte única da verdade. O backend gera stubs de servidor a partir dele. O frontend gera SDKs de cliente a partir dele. A equipe mobile gera seus modelos a partir dele. Todos trabalham contra a mesma especificação, sincronizados como uma orquestra bem regida.

openapi: 3.0.3
info:
  title: Transaction API
  version: 1.0.0
paths:
  /users/{userId}/transactions:
    get:
      summary: Fetch user transactions
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: A list of transactions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Transaction'
        '404':
          description: User not found
components:
  schemas:
    Transaction:
      type: object
      properties:
        id:
          type: string
          format: uuid
        amount:
          type: integer
          description: Amount in cents
        currency:
          type: string
          example: "EUR"
        timestamp:
          type: string
          format: date-time
      required:
        - id
        - amount
        - currency
        - timestamp

Olhe só. Lindo. Explícito. Sem ambiguidade. O timestamp é um date-time. O amount está em centavos (sem pesadelos de ponto flutuante). O userId é um UUID, não “qualquer string que você quiser enviar.”

Se o backend desviar deste contrato, o código gerado não compilará. Se o frontend esperar um campo diferente, o cliente gerado vai gritar com eles. O contrato impõe disciplina.

“Mas Escrever YAML é Chato”

Sim. É. Sabe o que mais é chato? Passar quatro horas em uma videochamada depurando por que o app Android está crashando porque alguém no backend decidiu renomear user_id para userId sem contar para ninguém.

Sabe o que é chato? Reescrever testes de integração porque a estrutura da resposta mudou e ninguém atualizou os mocks.

Sabe o que é esmagadoramente chato? Ser o coitado que entra em um projeto de dois anos e tem que entender 150 endpoints lendo arquivos Controller espalhados por 47 pacotes sem documentação alguma.

Escrever o contrato antecipadamente é um investimento. É como escovar os dentes: tedioso no momento, mas evita um canal mais tarde.

As Ferramentas: Edição Java

Como prometi exemplos de código, deixe-me mostrar como isso funciona no ecossistema Spring. Usamos um plugin Maven chamado openapi-generator-maven-plugin. Você aponta para o seu contrato, e ele gera as interfaces para você.

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>7.2.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/api/openapi.yaml</inputSpec>
                <generatorName>spring</generatorName>
                <apiPackage>me.doismiu.api.generated</apiPackage>
                <modelPackage>me.doismiu.api.generated.model</modelPackage>
                <configOptions>
                    <interfaceOnly>true</interfaceOnly>
                    <useSpringBoot3>true</useSpringBoot3>
                    <useTags>true</useTags>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

Execute mvn compile, e de repente você tem uma interface Java que se parece com isso:

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
@Tag(name = "Transactions")
public interface TransactionsApi {

    @Operation(summary = "Fetch user transactions")
    @GetMapping(value = "/users/{userId}/transactions", produces = "application/json")
    ResponseEntity<List<Transaction>> getUserTransactions(
        @PathVariable("userId") UUID userId
    );
}

Seu Controller simplesmente implementa esta interface:

@RestController
public class TransactionsController implements TransactionsApi {

    private final TransactionService service;

    public TransactionsController(TransactionService service) {
        this.service = service;
    }

    @Override
    public ResponseEntity<List<Transaction>> getUserTransactions(UUID userId) {
        return ResponseEntity.ok(service.findByUser(userId));
    }
}

Se você tentar mudar o tipo de retorno para algo que não corresponda ao contrato, o compilador vai te dar um tapa. Se você tentar adicionar um parâmetro que não está na especificação, o compilador vai te dar um tapa mais forte. O contrato é a lei, e o código deve obedecer.

O Problema Cultural

Aqui está a verdade desconfortável: Contract First não é um desafio técnico. É um cultural.

Desenvolvedores resistem porque parece “trabalho extra.” Gerentes de produto resistem porque “atrasa a entrega.” Todo mundo quer se mover rápido e quebrar coisas, exceto que as coisas que eles quebram são a sanidade de seus colegas e a estabilidade do sistema.

Contract First força você a pensar antes de codificar. Força você a ter conversas sobre tipos de dados, casos de borda e tratamento de erros antes que se tornem incidentes de produção. Força disciplina em uma indústria que adora “se mover rápido.”

E é exatamente por isso que a maioria das equipes se recusa a adotá-lo. Disciplina é difícil. Caos é fácil. Até que não é.

Quando NÃO Usar Contract First

Serei justo. Há cenários onde Code First faz sentido:

  • Protótipos: Você está explorando, não sabe como a API vai ficar ainda e precisa iterar rápido. Tudo bem. Mas no momento em que for para produção, você escreve aquele contrato.
  • Microserviços internos com gRPC: Se você já está usando Protobuf (como eu desabafei anteriormente), o arquivo .proto é seu contrato. Você já está fazendo Contract First sem chamar assim.
  • Projetos solo: Se você é o único desenvolvedor, frontend e backend, pode manter o contrato na sua cabeça. Mas no momento em que outra pessoa entrar, escreva-o.

Para todo o resto, especialmente equipes maiores que uma pessoa, especialmente projetos que devem durar mais que um hackathon, Contract First é não negociável.

Conclusão

Já vi projetos morrerem porque ninguém sabia o que a API deveria fazer. Já vi desenvolvedores desistirem porque passaram mais tempo fazendo engenharia reversa de endpoints não documentados do que realmente construindo funcionalidades. Já vi quedas de produção causadas por “mudanças menores” que quebraram todos os clientes porque não havia contrato para validar.

Contract First não é uma metodologia. É uma estratégia de sobrevivência. É a diferença entre construir em solo sólido e construir em areia movediça de olhos vendados.

Escreva o contrato. Gere o código. Durma à noite.

Ou não. Continue vivendo no caos. Continue depurando coleções do Postman às 2 da manhã. Continue explicando para a equipe mobile por que o campo agora se chama transactionDate em vez de txn_dt pela terceira vez este mês.

Você decide.

Tchau!