Skip to content

ADR-002: Estratégia Contracts-First

Status: Aceito

Data: 2024-11-02

Decisores: Aron Cardoso

Contexto

Com arquitetura monorepo, módulos precisam interagir de forma desacoplada mesmo estando no mesmo repositório. Precisávamos definir como garantir que interfaces permaneçam estáveis enquanto implementações evoluem.

Forças em jogo:

  • Estabilidade de APIs: Evitar breaking changes frequentes
  • Evolução independente: Módulos devem poder mudar internamente
  • Testabilidade: Contratos facilitam mocks e testes
  • Documentação viva: Interfaces são documentação executável

Alternativas Consideradas

Opção 1: Implementation-First

  • Prós:
    • Desenvolvimento mais rápido inicialmente
    • Menos overhead de planejamento
    • Flexibilidade para experimentar
  • Contras:
    • Interfaces instáveis (mudanças frequentes)
    • Dificulta testes unitários
    • Acoplamento entre módulos
    • Breaking changes inesperados

Opção 2: Contracts-First (Escolhida)

  • Prós:
    • Interfaces estáveis e versionadas
    • Facilita TDD (Test-Driven Development)
    • Documentação clara de dependências
    • Previne acoplamento acidental
  • Contras:
    • Planejamento inicial maior
    • Overhead de manter contratos e implementações
    • Pode retardar prototipagem rápida

Decisão

Adotamos Contracts-First com as seguintes práticas:

  1. Definição prévia de interfaces:

    • Toda nova feature começa com definição de contratos
    • Contratos revisados antes de implementação
    • Versionamento semântico rigoroso
  2. Pacote packages/contracts contém:

    • Interfaces PHP (*Interface)
    • DTOs imutáveis
    • Eventos globais
    • Enums compartilhados
  3. Evolução controlada:

    • Breaking changes apenas em major versions
    • @deprecated com pelo menos uma minor version de antecedência
    • Changelog detalhado de contratos
  4. Implementações desacopladas:

    • Módulos dependem apenas de contratos
    • Nenhuma dependência direta entre módulos de domínio
    • Comunicação via eventos do contrato

Consequências

Positivas

  • Previsibilidade: Módulos sabem exatamente o que esperar de dependências
  • Facilita testes: Mocks baseados em interfaces são simples
  • Evolução segura: Breaking changes são explícitos e versionados
  • Documentação clara: Interfaces documentam comportamento esperado
  • Permite TDD: Testes podem ser escritos antes da implementação

Negativas

  • Planejamento prévio: Requer pensar na interface antes de implementar
  • Rigidez inicial: Mudanças em contratos têm alto custo
  • Overhead de manutenção: Duas bases de código (contratos + implementação)
  • Pode ser excessivo: Para features muito simples, pode ser overkill

Neutras

  • Necessita disciplina: Time precisa seguir processo religiosamente
  • Requer versionamento cuidadoso: SemVer estrito é essencial

Notas de Implementação

Estrutura de Contratos

text
packages/contracts/src/
├─ Payments/
│  ├─ PaymentGatewayInterface.php
│  ├─ TaxCalculatorInterface.php
│  └─ DTOs/
│     └─ PaymentDTO.php
├─ People/
│  ├─ PersonRepositoryInterface.php
│  └─ Events/
│     ├─ PersonCreated.php
│     └─ PersonUpdated.php
└─ Communications/
   └─ NotificationChannelInterface.php

Padrões de Naming

  • Interfaces: *Interface suffix
  • DTOs: *DTO suffix ou nome descritivo
  • Eventos: Past tense (PersonCreated, não PersonCreate)

Processo de Mudança

  1. Propor mudança via issue ou discussão
  2. Discutir impacto em módulos dependentes
  3. Implementar com @deprecated se breaking
  4. Atualizar documentação de compatibilidade
  5. Módulos atualizam no mesmo commit (vantagem do monorepo)

Referências

Documentação privada do ecossistema Filament Core.