Pular para o conteúdo principal

Monorepos Orientados a Domínio

Arquitetura de Monorepos Orientada a Domínio

A escalabilidade de ecossistemas de software corporativos frequentemente esbarra em dois gargalos principais: a complexidade cognitiva do domínio de negócios e a fricção imposta pela infraestrutura de gestão de código. À medida que as organizações crescem, microsserviços distribuídos em múltiplos repositórios (multirepos) ou estruturas monolíticas sem fronteiras claras tendem a acumular débitos técnicos insustentáveis.

A convergência entre o Domain-Driven Design (DDD)—especificamente o padrão estratégico de Contextos Delimitados (Bounded Contexts)—e as arquiteturas de Monorepositórios (Monorepos) oferece uma solução elegante. Ao mapear limites lógicos de negócios diretamente a limites físicos de pastas, estabelecemos um modelo de governança automatizado que protege o reaproveitamento de código enquanto garante a evolução independente. Este documento detalha como o tupynambalucas.dev materializa o design estratégico orientado a domínios em seu monorepo.


1. Fundamentos Estratégicos do Domain-Driven Design (DDD)

O DDD alinha o modelo de código interno de um sistema de software com as áreas de negócios do mundo real que ele suporta. Ele rejeita a falácia de um modelo canônico único e unificado para todo o sistema. Tentar forçar uma única definição de termos como "Cliente" ou "Ativo" (Asset) em diferentes áreas de uma organização gera códigos inchados e frágeis.

1.1 Polissemia de Conceitos

O fenômeno da polissemia (onde um único termo assume significados diferentes dependendo do contexto) é o principal catalisador para a adoção de Bounded Contexts. Forçar uma única classe ou entidade a atender múltiplos interesses de negócios viola o Princípio da Responsabilidade Única e causa acoplamentos severos.

Em nosso monorepo, o concept de um "Ativo" (Asset) possui definições completamente diferentes dependendo do contexto:

  • Contexto Studio (studio/): Um ativo é um arquivo de origem vetorial bruto (Illustrator .ai, Blender .blend, PSD) ou uma imagem rasterizada compilada de alta resolução armazenada no Cloudflare R2.
  • Contexto Renderer (renderer/): Um ativo é um recurso de fonte variável dinâmico, ciente do tema e codificado em base64, ou um cartão SVG compilado dinamicamente para contornar sandboxes de iframes.
  • Contexto Hub (hub/): Um ativo é uma URL estática e pública armazenada em cache por CDN que serve conteúdo do portfólio para o navegador do usuário.

Em vez de criar uma definição global e inchada de Asset, cada contexto mantém sua própria representação especializada, completamente isolada dos demais.

1.2 O Contexto Delimitado (Bounded Context)

Um Contexto Delimitado define a fronteira explícita dentro da qual um modelo de domínio é válido. Dentro desta fronteira, nossa Linguagem Ubíqua (Ubiquitous Language) possui significados precisos e indiscutíveis. No tupynambalucas.dev:

  • A terminologia de domínio está totalmente alinhada a esquemas técnicos, variáveis e especificações.
  • Os desenvolvedores têm propriedade e autonomia total sobre o ciclo de vida de seus contextos.
  • Contextos externos estão totalmente protegidos contra alterações de implementações internas.

2. Mapeamentos Estratégicos no Monorepo

Categorizamos nossos contextos utilizando as classificações táticas do DDD para priorizar investimentos técnicos e decisões arquiteturais:

ContextoClassificação EstratégicaAbordagem Arquitetural
Renderer (renderer/)Core Domain (Domínio Central)Pipeline proprietário de compilação de documentos e geração de gráficos com injeção de fontes em base64 sem dependências externas.
Hub (hub/)Core Domain (Domínio Central)Aplicativo web do portfólio, backend Fastify API e pacotes de contratos com validações rígidas de schemas Zod.
Cortex (cortex/)Supporting Domain (Suporte)Ingress gateway unificado de IA, índices de memória vetorial persistente, adaptadores MCP e terminais de agentes.
Studio (studio/)Supporting Domain (Suporte)Vault central de identidade visual da marca, biblioteca de tokens de design e sincronizador CLI de buckets R2.
Platform (platform/)Generic Domain (Genérico)Coletor de monitoramento OpenTelemetry e servidores de cache Turborepo.

3. Organização Física dos Espaços de Trabalho (PNPM Workspaces)

While DDD dita o agrupamento lógico de conceitos, o monorepo define a topologia física. Utilizamos pnpm workspaces para orquestrar dependências, estabelecendo uma fonte única da verdade para os pacotes ao mesmo tempo que garantimos uma separação lógica estrita.

3.1 Topologia de Pastas

O layout de diretórios reflete diretamente o mapa de Contextos Delimitados:

tupynambalucas/
├── hub/ # Contexto Hub (Portfólio Web & API)
│ ├── services/web/ # Aplicativo Web (React)
│ ├── services/api/ # Backend API (Fastify)
│ └── packages/core/ # Contratos de validação de dados (Zod)
├── cortex/ # Contexto Cortex (AI Ingress, Memória, MCP, Agentes)
├── renderer/ # Contexto Renderer (Pipeline de Documentos e Ativos)
├── studio/ # Contexto Studio (Sistema de Design e Ativos Master)
│ └── assets/ # Tokens CSS, SVG Logos e Sincronizador CLI
├── platform/ # Contexto Platform (OTel, Cache)
├── tools/ # Contexto Tools (Automação do GitHub CLI)
└── docs/ # Contexto Docs Hub (Docusaurus)

We completamente rejeitamos o antipadrão de um diretório utils/ global e genérico na raiz do projeto. Pastas deste tipo invariavelmente degeneram em depósitos sistêmicos de código inútil, disparando builds globais desnecessários e violando limites lógicos. Qualquer utilitário deve permanecer encapsulado em seu contexto de origem.

3.2 Isolamento de Dependências de Workspaces

Os workspaces declaram dependências internas explicitamente usando o protocolo workspace:*. Isso impede a resolução de pacotes internos por meio de registros públicos e força a vinculação física local de symlinks. Sob o pnpm, essa arquitetura de vinculação rígida rejeita dependências fantasmas, garantindo que um pacote só possa importar o que está explicitamente declarado em seu próprio package.json local.


4. Mapeamento e Integração de Contextos (Context Mapping)

Bounded Contexts não operam em isolamento absoluto; eles devem se comunicar por meio de padrões de relacionamento explícitos.

4.1 Shared Kernel (Núcleo Partilhado)

O padrão de Shared Kernel é utilizado quando os contextos compartilham uma parte estável de esquemas de domínio ou variáveis de design. Em nosso sistema, o pacote @tupynambalucas-studio/design atua como nosso Shared Kernel. Tanto o @tupynambalucas-hub/web quanto o @tupynambalucas/renderer importam os tokens de estilo CSS e ícones SVG diretamente deste pacote.

Qualquer alteração nesses tokens aciona validações imediatas em tempo de compilação em ambos os contextos downstream consumidores, garantindo consistência visual sistêmica.

4.2 Camada Anticorrupção (Anti-Corruption Layer - ACL)

Quando um contexto limpo precisa consumir serviços externos complexos ou brutos, introduzimos uma Camada Anticorrupção (ACL) para traduzir as entradas externas na linguagem de domínio limpa do consumidor.

Nosso serviço AgentGateway (sob o diretório /cortex/gateway) atua como uma ACL a nível de rede. Containers de agentes de IA downstream no diretório cortex/agents executam comandos complexos e não padronizados de servidores MCP. Em vez de acoplar os agentes diretamente aos endpoints brutos dos servidores, eles se comunicam através do proxy gateway de entrada unificado, que traduz e roteia os payloads de forma limpa.


5. Estruturação e Imposição Arquitetural

Impomos modularidade estrutural dinamicamente para evitar importações cruzadas ilícitas e manter a integridade arquitetônica do monorepo.

5.1 Limites Verticais vs. Horizontais

  • Limites Verticais: Ditados pelos diretórios raiz dos workspaces (ex: o código em hub/ não pode realizar importações diretas e profundas de arquivos do sistema de arquivos sob o diretório renderer/).
  • Limites Horizontais: Camadas arquiteturais internas. Por exemplo, o aplicativo web React @tupynambalucas-hub/web (camada de UI/Feature) é totalmente desacoplado do pacote @tupynambalucas-hub/core (camada de domínio/contratos), forçando o fluxo de dependências estritamente para baixo.

5.2 Imposição de Fronteiras Automatizada

Se deixados apenas à mercê da disciplina social das equipes, as fronteiras arquiteturais eventualmente se degradam sob pressões de lançamento. Impomos esses limites de forma programática por meio de:

  • Regras Estritas de Linting: Configuração de verificadores de fronteiras de importação para bloquear importações cruzadas não autorizadas entre domínios.
  • Análise Topológica de Gráficos (Dependency Cruising): Auditoria contínua de todo o grafo de importações, bloqueando dependências circulares e removendo códigos mortos.
  • Importações Apenas de Tipos (Type-only imports): Garantia de acoplamento limpo por meio do consumo dinâmico apenas de contratos de tipos, em vez de importar classes de implementação física de outros serviços.

6. Padrões de Layout de Contêineres: Flat Services vs. Layered Domains

Quando um Contexto Delimitado (Bounded Context) inclui unidades de implantação em contêineres via Docker, o layout dos diretórios deve ser estruturado para corresponder ao seu alinhamento arquitetural:

6.1 Layout de Flat Services (Serviços Planos)

Um layout horizontal onde todos os microsserviços são posicionados dentro de um diretório plano /services/.

  • Quando Usar: Aplicado a workspaces focados em produtos específicos (como /hub e studio/design) que representam pilhas tradicionais de aplicações web (Frontend Client, Backend API, Banco de Dados, Cache).
  • Justificativa: Os serviços funcionam como componentes equivalentes de uma única aplicação acoplada. Adicionar camadas estruturais adicionais aumentaria a complexidade sem trazer benefícios práticos.

6.2 Layout de Layered Domain (Domínios em Camadas)

Um layout vertical onde os serviços são agrupados em diretórios irmãos representando diferentes planos arquiteturais (como gateway/ para roteamento, mcp/ para barramento de dados, e agents/ para contêineres de execução) na raiz do espaço de trabalho.

  • Quando Usar: Aplicado a workspaces focados em infraestrutura (como /cortex) que contêm componentes heterogêneos com ciclos de vida, permissões de sistema e necessidades de deploy independentes.
  • Justificativa: Evidencia a hierarquia estrutural de integrações complexas. Permite inicializar partes específicas (como o roteador de rede e o plano de dados) de forma independente das camadas de controle (como contêineres de execução de IAs).