Como estruturar subdomínios em arquiteturas modernas: do monolito aos microsserviços

Imagine que você está desenvolvendo um aplicativo empresarial de missão crítica. Nesse cenário, entregar mudanças de forma rápida, frequente e confiável deixou de ser diferencial e virou obrigação. Métricas como as da DORA mostram que velocidade e estabilidade caminham juntas, e é por isso que muitas organizações adotam equipes pequenas, multifuncionais e com baixo acoplamento, seguindo conceitos como Team Topologies e práticas DevOps, incluindo implantação contínua.

Dentro desse modelo, surge uma pergunta central de arquitetura: como organizar os subdomínios do sistema em componentes que possam ser implantados e evoluídos sem virar um gargalo?

O papel dos subdomínios na arquitetura

Cada equipe costuma ser responsável por um ou mais subdomínios, que representam capacidades de negócio específicas. Um subdomínio encapsula regras de negócio, entidades (ou agregados, no contexto de DDD) e adaptadores que fazem a ponte com o mundo externo. Em aplicações Java, por exemplo, isso normalmente se traduz em conjuntos de classes organizadas em pacotes, compiladas em arquivos JAR.

Esses subdomínios implementam o comportamento do sistema por meio de operações que podem ser disparadas por requisições síncronas ou assíncronas, eventos de outros serviços ou até pela passagem do tempo. O desafio está em decidir como agrupar esses subdomínios em componentes executáveis.

O dilema da organização: forças em conflito

Essa decisão não é trivial porque envolve forças que puxam a arquitetura em direções opostas. De um lado, existe a necessidade de componentes simples, autonomia das equipes, pipelines rápidos, liberdade tecnológica e segregação por características como segurança e escalabilidade. Do outro, entram preocupações como manter interações simples e eficientes, evitar excesso de chamadas em rede, preferir transações ACID sempre que possível e reduzir o acoplamento tanto em tempo de execução quanto em tempo de desenvolvimento.

Essas forças explicam por que não existe uma arquitetura perfeita, apenas escolhas conscientes com trade-offs claros.

A solução: serviços independentes e fracamente acoplados

Uma abordagem comum é estruturar a aplicação como um conjunto de serviços independentes, cada um responsável por um ou mais subdomínios. Cada subdomínio pertence a um único serviço, com exceção de bibliotecas compartilhadas, usadas por múltiplos times. O serviço passa a ser “propriedade” da equipe que cuida daqueles subdomínios, reforçando autonomia e responsabilidade ponta a ponta.

Normalmente, um API Gateway funciona como ponto de entrada da aplicação. Algumas operações ficam restritas a um único serviço, enquanto outras exigem colaboração entre vários. Para isso, entram em cena padrões de colaboração entre serviços.

Para garantir independência real, cada serviço costuma ter seu próprio repositório de código e pipeline de CI/CD, responsável por compilar, testar e implantar sem depender de outros times.

Exemplo prático: um e-commerce moderno

Em um aplicativo fictício de comércio eletrônico, é comum separar responsabilidades em serviços distintos: interface do usuário, verificação de crédito, controle de estoque e envio de pedidos. Cada um evolui no seu ritmo, escala conforme a demanda e pode até usar tecnologias diferentes, desde que respeite os contratos de comunicação.

Benefícios e custos da abordagem

Entre os principais benefícios estão serviços menores e mais fáceis de manter, maior autonomia das equipes, pipelines de implantação mais rápidos, liberdade para usar múltiplas tecnologias e melhor adequação a requisitos específicos de escalabilidade, disponibilidade e segurança.

Por outro lado, surgem desafios. Operações distribuídas podem se tornar complexas e difíceis de depurar. A comunicação em rede pode gerar ineficiências. Transações frequentemente precisam abrir mão do modelo ACID em favor de consistência eventual, já que cada serviço mantém seu próprio banco de dados. Além disso, existe sempre o risco de acoplamento excessivo, seja em tempo de execução ou no design.

Assemblage: usando forças para decidir melhor

Para lidar com essas decisões, entra o conceito de Assemblage, um processo de definição de arquitetura que usa justamente essas forças “em conflito” para agrupar subdomínios de forma consciente. O objetivo é chegar a uma arquitetura que pode ser monolítica ou baseada em microsserviços, mas que faça sentido para o contexto do negócio e da organização.

Essas forças também influenciam diretamente o desenho das operações distribuídas, que são um dos pontos mais delicados em arquiteturas de microsserviços.

Como implementar operações distribuídas

Quando uma operação envolve vários serviços, cada um com seu próprio banco de dados, a solução passa pelo uso de padrões consolidados. Entre os mais comuns estão Saga, para comandos distribuídos baseados em transações locais, réplica do lado do comando, composição de API e CQRS para consultas distribuídas.

Esses padrões geralmente dependem de mensagens assíncronas e do uso de técnicas como Transaction Outbox, garantindo consistência entre dados persistidos e eventos emitidos.

Padrões que orbitam os microsserviços

Além dos padrões de colaboração, existem muitos outros que completam o ecossistema: banco de dados por serviço, API Gateway, descoberta de serviços, testes de contrato, circuit breaker, observabilidade, rastreamento distribuído, métricas, logs e estratégias de implantação. Todos eles atacam problemas específicos que surgem quando o sistema cresce.

Casos reais de sucesso

Empresas como Netflix, Amazon e eBay são exemplos clássicos de evolução de arquiteturas monolíticas para modelos orientados a serviços. A Netflix lida com bilhões de chamadas diárias distribuídas entre centenas de serviços. A Amazon construiu um ecossistema com centenas de serviços reutilizados por diferentes aplicações. O eBay segmentou sua lógica de negócio em aplicações independentes, combinando diferentes estratégias de escalabilidade.

Esses casos mostram que microsserviços não são uma bala de prata, mas uma resposta arquitetural a sistemas grandes, complexos e em constante evolução. A chave está em entender o contexto, as forças envolvidas e escolher conscientemente como estruturar seus subdomínios.