Architecture
Arquitetura de software na era da AI
Como conceitos com décadas de existência, como DDD e testes como documentação, podem devolver controle sobre produtos construídos com agents de AI.
By Vinícius Amélio

Na era de LLM's a produção de código e desenvolvimento de produtos digitais se popularizou de tal forma que qualquer pessoa com um pouco de tempo, e familiaridade com tecnologia, consegue desenvolver aplicações de diferentes complexidades e tamanhos. O que antes poderia levar alguns dias, semanas e até meses, hoje pode ser construído em alguns minutos/horas à depender do quão objetivo você consegue ser em contextualizar seus agents. Isso pode ser positivo à medida que cada vez mais MVP's, protótipos e hand-off's passam a ocorrer em tempo recorde. Teve uma ideia? Utilize seu Claude Code, Codex, Copilot, ou (adicione aqui seu coding agent favorito) e veja em tempo real suas ideias ganharem vida, valide-a com usuários em tempo recorde, e Voilà: Você está pronto para fazer dinheiro, alcançar seus KPI's, bater a meta de MAU da sua aplicação, mas e depois?
Conforme o tempo passa, aquele código inicialmente desenvolvido, com pouca ou nenhuma supervisão, passa a apresentar os mesmos sintomas de um MVP desenvolvido na década passada numa startup que, na melhor das hipóteses, deu muito certo e expandiu cada uma das features necessárias para os usuários frenéticamente. Esses sintomas que outrora eram comumente sentidos, e sofridos, por desenvolvedores humanos agora passam a ser sentidos por humanos e também agents. Você, Dev/PM/Product Designer/(Bote aqui seu cargo chique em inglês), começa a notar que qualquer mudança relativamente pequena no projeto, mas que já necessite se locomover entre arquivos para entender o que acontece, passa a encher a janela de contexto do seu Agent de maneira acelerada, e o pior: Antes de tocar em sequer uma linha de código a compactação de contexto já está quase sendo utilizada de maneira automática. Você tenta repassar todo o código gerado, entendendo arquivo à arquivo (que você mentiu pra si mesmo que revisou antes de fazer cada commit) e finalmente percebe o erro que foi ter delegado as decisões arquiteturais e de design de software para o agent, mas quem pode o culpar? O produto demandava prazo curto, você o fez dentro desse curto prazo, mas agora é a hora de limpar a casa.
O débito cognitivo nos times
Lendo tudo isso que disse na introdução pode dar à entender que eu estou sugerindo que usar AI nos projetos, particularmente os corporativos, é um erro, e não é o caso pois tenho até amigos que usam (óbviamente é uma piada). A questão toda é o "como" isso é feito.
Veja bem, antes era muito comum times possuirem débito técnico à medida que novas features e vertentes eram desenvolvidas para seus produtos. Débito técnico nada mais é do que o famoso cenário "Não fizemos da maneira ideal pelo motivo X, mas um dia vamos ajustar". Hoje nós nos deparemos com outro tipo de débito, o cognitivo, que é bem mais prejudicial para a saúde do seu software. Débito cognitivo é como o técnico, que possuíamos antes, mas com uma grande diferença: "Meu agent gerou esse código, um dia eu entendo melhor isso aqui que eu estou entregando". Pronto, com isso você tem a fórmula do caos implantada na sua codebase e, além disso, na sua própria compreensão de tecnologia e ownership.
Como conceitos com mais de duas décadas de existência podem te dar de volta o controle sobre seu produto?
Domain Driven Design (DDD) foi introduzido inicialmente num livro entitulado "Domain-Driven Design: Tackling Complexity in the Heart of Software." escrito por Eric Evans, em 2003. Em um resumo, muito bem resumido e extraindo apenas o necessário para minha tese, define alguns conceitos core como:
- Linguagem ubíqua: Um vocabulário comum e compartilhado tanto por desenvolvedores como pelo time de negócios.
- Contextos delimitados: Limites bem definidos ao redor de modelos de domínio para gerenciar complexidade.
- Entidades e objetos de valor (value objects): Peças fundamentais usadas para classificar e modelar objetos baseados em identidade e mutabilidade.
Eu sei que muitos desenvolvedores torcem o nariz só de ouvir coisas como DDD, Clean Code e Clean Architecture. Diria que tem ocorrido ainda mais hoje em dia com as LLM's coisas como "É over engineering", "Muita complexidade pra pouco resultado", dentre outras reclamações. Acho muitas delas pertinentes até certo ponto, à depender do tamanho do projeto, tecnologias utilizadas, tamanho do time, senioridade do time, e mais alguns outros pontos que não se fazem relevantes neste tópico.
Ainda sim, gostaria de deixar claro que a ideia não é pedir para sua IA sair aplicando DDD criando contextos, aggregates, aplicando CQRS nos seus endpoints e nem nada do tipo. A ideia é estruturar suas aplicações de tal maneira que tanto você e seu time quanto os agents consigam ler a codebase de maneira simples, sem encher a janela de contexto e sem fazer sobrecarga cognitiva com quem tenta ler o código por conta própria.
Veja, você talvez até já aplique alguns desses conceitos no seu dia a dia: Você pode muito bem utilizar os termos que o time de produto/negócios utiliza para nomear os componentes da sua codebase, e facilitar o entendimento quando te pedem algo na daily; Você talvez já faça uso de módulos ou algo como vertical slices, onde cada feature tem sua pasta dentro do projeto e que delimita os assuntos abordados dentro daquela separação virtual de protudo; Você talvez já tenha estruturas que representem operações e atores de negócio.
Um exemplo de todos esses conceitos que citei foi a concepção do Physikos, meu app para gestão de treinos de musculação. Vamos imaginar que ele possui alguns contextos diferentes como: workout, analytics, dashboard e settings.
Cada um desses módulos tem uma atribuição ligada diretamente à aplicação e sua representação de negócio: Workout cuida de tudo que envolver a gestão dos treinos, analytics cuida de tudo aquilo que visa fornecer comparações dos insumos desses treinos em formato gráfico, dashboard é responsável por mostrar métricas de treinos e medidas corporais. Cada um desses módulos possui suas próprias features, com seus próprios repositories, services, eventos, componentes e testes. Cada um desses módulos é um pacote diferente na minha codebase, de tal forma que cada alteração que preciso realizar, basta eu adicionar a pasta desse módulo no contexto do meu prompt e pronto, o agent sabe que, muito provavelmente, todo o trecho de código que ela precisa entender para fazer uma alteração referente à o que estou pedindo está contida naquela pasta, meu time e eu sabemos que se eu preciso mexer em algo no log dos treinos eu preciso alterar algo naquela pasta, e por fim, eu sei que os testes daquilo que vou alterar também estão naquela parte do projeto.
Na prática, a estrutura de pastas fica mais ou menos assim, com cada contexto isolado no seu próprio módulo e cada feature carregando suas próprias camadas:
src/
├── workout/
│ ├── features/
│ │ ├── log-session/
│ │ │ ├── data/
│ │ │ │ └── workout-repository.ts
│ │ │ ├── domain/
│ │ │ │ └── workout-session.ts
│ │ │ ├── services/
│ │ │ │ └── log-session-service.ts
│ │ │ ├── events/
│ │ │ │ └── session-logged-event.ts
│ │ │ ├── components/
│ │ │ │ └── log-session-form.tsx
│ │ │ └── log-session.test.ts
│ │ └── edit-plan/
│ │ └── ... (mesmas camadas: data, domain, services, events, components, testes)
│ └── index.ts
├── analytics/
│ └── features/
│ └── ... (mesmo padrão de camadas)
├── dashboard/
│ └── features/
│ └── ... (mesmo padrão de camadas)
└── settings/
└── features/
└── ... (mesmo padrão de camadas)
Repare que não existe uma pasta genérica utils ou services compartilhada entre módulos guardando lógica de negócio: cada contexto é auto-contido, e o que é realmente transversal (formatação, clients http, etc) fica isolado num pacote shared à parte, sem regra de negócio nenhuma.
Dessa forma o agent não precisa vasculhar o projeto todo, enchendo sua janela de contexto atoa, meu time não precisa sair lendo um emaranhado de arquivos pra entender o que vai ser alterado, eu economizo tokens, tempo e sanidade mental.
Testes como documentação
Da mesma forma que hoje existe uma facilidade absurda para gerar código produtivo, também temos essa mesma facilidade para gerar testes, sejam eles quais forem: unidade, integração, componente, e2e, golden, comportamental, (adicione aqui seu tipo de teste favorito).
Todos eles podem ser gerados pela IA igualmente, e são uma boa forma adicionar guardrails aos seus agents ao mesmo tempo que você garante qualidade e documentação. Veja bem, numa sessão de código muito longa os agents tendem a se tornar preguiçosos, ao "travarem" com alguma especificação que não foi muito bem abordada no seu documento de specs ou prompt, eles podem frequentemente dar um jeitinho de simplesmente pularem aquela etapa por estarem presos por muito tempo tentando fazer algo funcionar da maneira errada. Quando você especifica que determinados testes sejam escritos, e eles compõe sua DoD (Definition of done) sua AI tende a ter mais facilidade em resolver problemas mais complexos por saber qual o resultado final esperado, mas mais do que isso, essa definição para considerar a tarefa pronta evita aquele chato ciclo repetitivo de você ler o output final e ver que seu agent deu a task como terminada mas ela não atende à tudo que era necessário.
Está fazendo uma feature nova? Adicione testes de comportamentos padronizados que todas as features possuem: Estado vazio, estado de erro, laoding, estado de sucesso, variantes. Se for no server-side você pode subir um container com o banco utilizado no projeto, ou fazer um adapter in-memory/sqlite que valide erro de comunicação com o DB, erro vindo da API de terceiros, input inválido, timeout, ativação de fallbacks, quebra de circuítos, população de uma DLQ, regra de acesso ao cache, etc.
Está corrigindo um bug? Faça uso do TDD, peça que o agente inicie a correção pelos testes, ele vai iniciar escrevendo o comportamento correto e só então adaptar o código exatamente à aquele cenário, sem alucinar com hipóteses que não condizem com o bug em si.
Ao final, você terá minimamente uma fonte catalogada de como seu software funciona e o que é esperado dele.