ADR-0027 Princípios de engenharia
Padrão da empresa para a forma do código — KISS primeiro, YAGNI depois, SOLID onde o código tem uma razão real para mudar.
- Decisão
- KISS, YAGNI, SOLID, por esta ordemConstruir a app
- Estado
- Aceite
- Data
- Âmbito
- Predefinição da empresa para todas as novas apps
Ligações
Apoia-se em
Contexto
Seção intitulada “Contexto”Os ADRs 0001–0025 escolhem as ferramentas, e o ADR-0026 diz onde vivem os ficheiros. Nenhum diz quanta estrutura deve ter o código dentro deles, pelo que isso volta a ser decidido em cada revisão, por gosto.
- Muitos produtos, uma equipa: quem muda de produto deve encontrar as mesmas formas — onde vive uma regra, onde se vão buscar os dados, onde vale a pena ter uma interface
- Os princípios já cá estão, caso a caso: os hooks são donos dos dados e do estado e os componentes renderizam (ADR-0018); uma só fronteira de adapter para os fornecedores de modelos, passada como parâmetro (ADR-0025); os plugins do Better Auth são opt-in por produto (ADR-0009); Zustand só quando aparece mesmo estado partilhado entre árvores (ADR-0012); um registo partilhado de componentes só quando dois produtos duplicam um componente (ADR-0011)
- Estrutura a mais é barata de escrever e cara de manter. As ferramentas de código com IA escrevem depressa e tendem a produzir mais camadas, opções e wrappers do que a tarefa pede; quem revê precisa de uma regra para onde apontar
O que está em aberto: que princípios nomeamos e qual ganha quando discordam.
Opções consideradas
Seção intitulada “Opções consideradas”1. Sem princípios escritos
Seção intitulada “1. Sem princípios escritos”Prós: nada para manter; cada equipa usa o seu próprio critério. Contras: a mesma discussão em cada pull request, com um resultado diferente por revisor; nada para entregar a quem chega ou a uma ferramenta de IA.
2. Uma arquitetura obrigatória (Clean / Hexagonal)
Seção intitulada “2. Uma arquitetura obrigatória (Clean / Hexagonal)”Camadas fixas em todo o lado: entidades, casos de uso, repositórios, ports e adapters.
Prós
- Todas as bases de código têm as mesmas pastas e as mesmas fronteiras
- As regras de negócio testam-se sem infraestrutura
Contras
- Uma interface com uma só implementação, para cada classe — cerimónia que pesa mais do que o CRUD que a maioria das nossas apps é
- Contraria escolhas que já fizemos: o Drizzle é SQL-first e usado diretamente (ADR-0008), e os testes de integração correm em Postgres e Valkey reais (ADR-0014), pelo que uma camada de repositórios que existe para ser simulada não traz nada
- Mais ficheiros para ler até encontrar a linha que faz o trabalho
3. Princípios curtos e conhecidos, com ordem de prioridade
Seção intitulada “3. Princípios curtos e conhecidos, com ordem de prioridade”KISS, YAGNI e SOLID, com uma regra para os conflitos.
Prós
- Nomes conhecidos: ensinados em todo o lado, pesquisáveis e entendidos pelas ferramentas de IA sem explicação
- Curtos o suficiente para lembrar e para citar num comentário de revisão
- A ordem resolve o conflito habitual — o SOLID a pedir uma abstração que o YAGNI diz ainda não ser precisa
Contras
- “Simples” é um juízo, não uma medida; quem faz cumprir é a revisão de código, não um linter
- O SOLID foi escrito para código orientado a objetos com classes e precisa de ser traduzido para funções, hooks e componentes
- Menos uniforme do que uma arquitetura obrigatória: dois produtos podem ter aspetos diferentes
Decisão
Seção intitulada “Decisão”KISS, depois YAGNI, depois SOLID — aplicados por esta ordem — são o padrão da empresa para todo o código novo.
Em concreto:
- KISS — o desenho mais simples que resolve o problema por inteiro. Preferir, por ordem: o que a base de código já tem, a plataforma ou a biblioteca padrão, uma dependência que estes ADRs já escolheram e só depois código novo. Legível ganha a engenhoso; uma linha que é preciso decifrar não é simples
- YAGNI — construir para o requisito que temos. Nenhuma opção, flag de configuração, wrapper ou ponto de extensão até existir um segundo caso real. Extrair uma abstração partilhada quando o segundo caso chega, não antes. Apagar o código que não é usado em vez de o guardar “para depois” — o git lembra-se
- SOLID — onde o código tem uma razão real para mudar ou uma segunda implementação real, nunca como estrutura especulativa
- Backend:
- Responsabilidade única: um módulo ou função é dono de uma só razão para mudar. Uma server function ou um handler de rota da API (ADR-0036) faz parse, autoriza e chama uma função da funcionalidade; a validação é Zod (ADR-0005), as regras de negócio são funções simples em
server/services/(ADR-0026), a persistência é Drizzle (ADR-0008) - Aberto/fechado: acrescentar comportamento acrescentando um caso (um handler, uma estratégia, uma linha de tabela) em vez de editar ramos que funcionam — quando existe uma segunda variante
- Substituição de Liskov: todas as implementações de uma interface cumprem o mesmo contrato: entradas, saídas, erros, efeitos secundários. Nenhuma implementação que lança “not supported”
- Segregação de interfaces: as interfaces são pequenas e desenhadas por quem as chama. Dividir um serviço quando quem o chama usa metades diferentes
- Inversão de dependências: a lógica de negócio recebe o que atravessa uma fronteira real — o
dbdo Drizzle, o cliente Valkey, clientes de APIs de terceiros, o relógio — como parâmetros, para que um teste possa passar os seus. O código interno puro importa código concreto diretamente
- Responsabilidade única: um módulo ou função é dono de uma só razão para mudar. Uma server function ou um handler de rota da API (ADR-0036) faz parse, autoriza e chama uma função da funcionalidade; a validação é Zod (ADR-0005), as regras de negócio são funções simples em
- UI:
- Responsabilidade única: um componente renderiza ou orquestra. O carregamento de dados e a lógica de estado vivem em hooks (ADR-0012); os componentes apresentacionais mantêm-se puros
- Aberto/fechado: estender por composição —
children, slots, propsrender, variantescva(ADR-0011) — e não duplicando um componente ou acumulando flags booleanas - Substituição de Liskov: um wrapper ou uma variante aceita e reencaminha as props do componente base (
ref,className, atributos ARIA, handlers de eventos) e comporta-se da mesma forma - Segregação de interfaces: passar os campos de que o componente precisa, não o objeto de domínio inteiro
- Inversão de dependências: os componentes recebem clientes de API, stores e serviços por props, contexto ou hooks, nunca importando um singleton concreto no fundo da árvore
- Quando discordam, ganha o desenho mais simples, e o pull request regista o compromisso numa linha
- O que a simplicidade nunca remove: a validação nas fronteiras do sistema (ADR-0005), a autenticação e a autorização (ADR-0009), os limites de pedidos (ADR-0010), o tratamento de erros que evita perda de dados, a acessibilidade e os testes do ADR-0014
- Cumprido na revisão de código. Perante qualquer abstração, opção ou dependência nova, quem revê faz uma pergunta: que requisito atual precisa disto? Sem resposta, não há merge
Os fatores decisivos: nomes que qualquer programador e ferramenta de IA já conhece, uma lista curta o suficiente para citar numa revisão e uma ordem explícita que impede o SOLID de se tornar um motivo para acrescentar camadas. Aceitamos que “simples” continue a ser um juízo feito na revisão.
Consequências
Seção intitulada “Consequências”Mais fácil:
- As discussões em revisão têm um vocabulário comum e uma regra de desempate
- Menos código para ler, testar e atualizar: as abstrações aparecem quando um segundo caso as justifica
- As mesmas formas em todos os produtos — handlers finos, regras em módulos simples, hooks donos do estado, componentes que renderizam
- Os testes precisam de poucos mocks: as dependências chegam como parâmetros e a base de dados é a real (ADR-0014)
Mais difícil:
- Exige critério; dois revisores podem continuar a discordar sobre o que é “o mais simples”
- Extrair uma abstração tarde obriga a voltar a mexer na primeira cópia — um custo pequeno e conhecido
- Manter a linha perante código gerado que chega com camadas que ninguém pediu
Do que abdicamos: da uniformidade de uma arquitetura obrigatória e do DRY como regra própria. Aceite — a duplicação é mais barata do que a abstração errada, e o YAGNI já diz quando extrair.

