Lego Microservices
Microservices bem projetados são como peças de Lego: cada um cumpre sua função, mas juntos constroem algo grandioso.
Quando projetamos sistemas baseados em microserviços, um dos grandes desafios é garantir que cada parte do sistema seja independente, modular e bem encaixada. E sabe o que isso me lembra? Peças de Lego! 🧩
Agora, antes que você torça o nariz achando que estou inventando moda com “buzzwords”, deixa eu te tranquilizar: não estou propondo uma nova tendência ou mudando conceitos fundamentais da arquitetura. A ideia aqui é fazer um paralelo lúdico e visual entre as peças de Lego e a forma como devemos pensar em microserviços. Afinal, quem não gosta de uma boa analogia para deixar o tema mais leve e interessante, né? 😄
📖 No livro Building Microservices, Sam Newman explora profundamente como diferentes tipos de acoplamento podem surgir entre serviços e o impacto que isso tem no design e na operação de sistemas distribuídos. Ele reforça um princípio essencial: mantenha comportamentos relacionados juntos e separados daqueles que não estão relacionados. 🛠️ Essa abordagem ajuda a evitar o temido “efeito dominó” ao realizar mudanças, promovendo uma arquitetura mais resiliente e fácil de manter. 💡
🧐 Pense no seguinte: em um sistema bem projetado, ajustar um comportamento específico é como trocar uma peça de Lego. Se todos os comportamentos relacionados estão agrupados em um único serviço, a mudança pode ser feita de forma rápida e centralizada, com testes e implantação simplificados. Agora, se esses comportamentos estão espalhados por vários serviços, será necessário modificar, testar e lançar atualizações em diversas partes ao mesmo tempo. Isso torna o processo mais lento 🐢 e aumenta os riscos de comportamentos inconsistente ⚠️.
Nos próximos tópicos, vamos explorar como microserviços podem se comportar como peças de Lego bem encaixadas, prontos para construir sistemas flexíveis e escaláveis.
Como devemos encarar o Acoplamento?
A graça de brincar com Lego é a facilidade de construir, desmontar e reconstruir. Imagine que você montou um carro com portas, rodas e um teto removível. Se quiser trocar as rodas por outras maiores, é só destacar as peças que as conectam e encaixar as novas, sem precisar desmontar o resto do carro. Ou, se quiser mudar a cor do teto, basta tirar a peça atual e substituí-la por outra da cor desejada, deixando todo o restante intacto.
Essa simplicidade e modularidade são o que fazem do Lego um sucesso — cada peça tem um propósito claro e se integra sem comprometer o todo. 😄 Nos microserviços, buscamos essa mesma lógica: alterar uma “peça” ou comportamento sem precisar desmontar o sistema inteiro. 🛠️
Agora, imagine se o conceito do Lego fosse diferente. Para trocar uma única peça, você teria que desmontar metade da construção ou ajustar várias peças ao redor. 😬
Claro, nem todas as peças de Lego são feitas para serem facilmente desacopladas. Algumas edições especiais, como conjuntos temáticos de Star Wars ou Harry Potter, apresentam peças exclusivas e superpersonalizadas que muitas vezes só fazem sentido dentro daquele conjunto. Essas peças introduzem um “acoplamento necessário” para criar algo muito específico.
Porém, a ideia original do conceito Lego sempre foi a modularidade: evitar acoplamentos desnecessários e permitir que qualquer peça funcione em harmonia com outras. Esse é o espírito que queremos replicar em nossos sistemas: serviços que colaboram sem depender excessivamente uns dos outros, mas que, quando necessário, aceitem certo grau de dependência bem planejada para atender a requisitos especiais.
Mas afinal, o que significa “acoplamento”?
O termo acoplamento vem do latim copula, que significa “ligação” ou “união”. De lá, ele evoluiu para o francês antigo accoupler, que quer dizer algo como “juntar em parelhas”. Em português, usamos “acoplamento” para descrever a ação de conectar ou unir coisas, como define o Dicionário Priberam:
• Ação ou efeito de acoplar, ou seja, juntar ou ligar duas coisas ou sistemas.
Em tecnologia, especialmente no desenvolvimento de software, o significado fica um pouco mais específico. Acoplamento descreve o grau de dependência entre partes de um sistema. Quando os componentes estão muito interligados, dizemos que o sistema tem alto acoplamento — e isso, como você já deve ter sentido na prática, pode complicar bastante a manutenção e evolução de um projeto.
E em inglês?
Em inglês, o equivalente a “acoplamento” é “coupling”. Você provavelmente já viu essa palavra sendo usada em artigos técnicos ou documentações. Ela também carrega o mesmo sentido de “ligação” ou “interdependência”. Vamos ver como os dicionários explicam:
1. Oxford English Dictionary:
• “The action of linking or combining two or more things together.”
• No contexto de software: “The degree of interdependence between software modules or components.”
2. Merriam-Webster Dictionary:
• “The act of bringing or coming together.”
• Em tecnologia: “The linking of two systems or devices for operation together.”
Percebeu o padrão? Tanto em português quanto em inglês, o foco está na ideia de “ligar” ou “unir”. Mas, no nosso caso, a conversa vai além do dicionário, porque, para quem projeta sistemas, o impacto do acoplamento vai muito mais fundo.
Por que programadores e peças de Lego se preocupam tanto com isso?
Pensa comigo: no mundo do Lego, as peças são projetadas para se conectar sem criar dependências desnecessárias. Se você quiser trocar a cor do teto de uma casa que construiu, é só remover o bloco correspondente — não precisa desmontar tudo. Isso é o que chamamos de modularidade.
Agora, imagina se as peças fossem “coladas” umas às outras. Trocar uma porta exigiria que você desmontasse todo o prédio. Isso é alto acoplamento na prática — e é exatamente o tipo de problema que queremos evitar em sistemas de software.
Em uma arquiteturas de microserviços, o ideal é que cada “bloco de Lego” (ou serviço) seja independente. Isso quer dizer que você pode alterar ou substituir uma peça sem bagunçar o resto do sistema. Um verdadeiro espírito Lego: adicionar, remover ou reorganizar peças facilmente. 🛠️
Mas, quando o acoplamento é alto, adeus flexibilidade! 😔 Os serviços ficam tão conectados que parecem blocos de Lego colados com supercola. 💔 Qualquer ajuste pequeno obriga você a mexer em vários outros serviços, tornando a manutenção e a evolução do sistema uma missão quase impossível.
💡 É por isso que, em uma arquitetura desacoplada, cada serviço funciona como um bloco de Lego livre e maleável: fácil de encaixar, trocar ou reorganizar. Isso permite criar sistemas robustos e flexíveis, prontos para evoluir sem drama ou surpresas desagradáveis.
O Que é Coesão? 🤔
Pense nas peças de Lego novamente. 🧩 Quando todas as peças de um conjunto de Lego trabalham bem juntas para construir um objeto final, dizemos que o conjunto tem alta coesão — as peças estão bem focadas em fazer uma coisa e fazê-la bem. 🚗✨
No desenvolvimento de software, a coesão funciona da mesma maneira. Um serviço ou componente com alta coesão tem um propósito claro e focado: todas as suas partes trabalham juntas para cumprir uma responsabilidade específica. Assim como o conjunto de Lego que constrói um carro, um serviço coeso é aquele em que todas as suas funções estão alinhadas para fazer uma única tarefa ou resolver um único problema. 💡
Agora, se um serviço tentar fazer muitas coisas ao mesmo tempo — como processar pagamentos, gerenciar o estoque e enviar notificações — ele começa a perder o foco e se torna menos eficiente. Essa mistura de responsabilidades gera baixa coesão, assim como o conjunto de Lego que tenta construir um carro e um prédio ao mesmo tempo, acabando por não fazer nenhum dos dois direito. 😓
Trazendo Coesão para Microserviços 🌐
Em microserviços, um serviço é altamente coeso quando todas as suas funcionalidades estão relacionadas e trabalham juntas para um propósito comum. Por exemplo:
• Um Serviço de Pedidos em um sistema de e-commerce deve focar em tudo relacionado a pedidos: criar, atualizar, cancelar e listar pedidos. Todas essas funcionalidades pertencem ao domínio de pedidos e, portanto, devem estar agrupadas no mesmo serviço.
Se o Serviço de Pedidos começasse a incluir funcionalidades que pertencem a outros domínios, como processamento de pagamentos ou gerenciamento de inventário, ele perderia a coesão. As responsabilidades se tornariam difusas, tornando o serviço mais complexo, difícil de entender e de manter. ⚠️
Por Que a Coesão é Importante? 🌟
Alta coesão dentro de um serviço é desejável porque:
• ✅ Facilita a Manutenção: Quando todas as funcionalidades de um serviço são relacionadas, é mais fácil entender o que ele faz e como ele faz. Isso torna mais simples realizar mudanças e adicionar novas funcionalidades.
• ✅ Melhora a Compreensão: Desenvolvedores podem rapidamente entender o propósito do serviço e como ele contribui para o sistema como um todo.
• ✅ Reduz a Complexidade: Serviços coesos têm responsabilidades claras e limitadas, o que reduz a complexidade interna e facilita a resolução de problemas.
Quando os serviços são desenhados com alta coesão e baixo acoplamento, obtemos um sistema mais estável e fácil de evoluir:
Alta Coesão e Baixo Acoplamento 🔗
Quando as funcionalidades relacionadas estão agrupadas em um único serviço (alta coesão), qualquer mudança necessária pode ser feita dentro desse serviço sem impactar outros serviços. Isso mantém o acoplamento baixo, já que outros serviços não são afetados pelas mudanças internas.
A Lei de Constantine 📜
A Lei de Constantine, afirma que "uma estrutura será estável se a coesão for alta e o acoplamento for baixo". Isso destaca a importância de encontrar um equilíbrio entre coesão e acoplamento para criar uma estrutura estável e sustentável.
Evolução e Manutenção 🔄
Se um microserviço expõe um contrato que muda constantemente e se torna incompatível com versões anteriores, isso força os consumidores desse serviço a se adaptarem continuamente. Isso aumenta o acoplamento e dificulta a manutenção. Portanto, é crucial que os serviços sejam coesos internamente e que as interfaces expostas sejam estáveis e bem definidas para minimizar o impacto das mudanças. 🛡️
Vamos considerar um sistema de e-commerce com os seguintes serviços:
Serviço de Pedidos: Gerencia a criação e o processamento de pedidos.
Serviço de Inventário: Gerencia o estoque de produtos.
Serviço de Notificações: Envia notificações de confirmação de pedidos.
Se o Serviço de Pedidos e o Serviço de Inventário forem altamente coesos internamente e tiverem interfaces bem definidas, uma mudança na lógica de inventário (como a adição de um novo tipo de produto) pode ser feita sem impactar o Serviço de Pedidos.
Da mesma forma, se o Serviço de Notificações apenas escutar eventos de confirmação de pedidos sem depender diretamente dos detalhes de processamento de pedidos ou inventário, ele permanece desacoplado e pode ser alterado independentemente.
Mas construir tudo isso e entender as fronteiras não é uma tarefa simples. Entender o nível de acoplamento que cada serviço deve ter de outros também não é fácil. Por isso, vamos conversar mais detalhadamente sobre o acoplamento de domínio. 🧩
Acoplamento de Domínio 🚟
Quando Sam Newman define acoplamento de domínio, ele descreve algo que muitos de nós enfrentamos no dia a dia ao lidar com sistemas distribuídos:
“Um acoplamento de domínio descreve uma situação na qual um microserviço deve interagir com outro microserviço porque o primeiro microserviço precisa utilizar a funcionalidade que o outro microserviço oferece.” - Building Microservices by Sam Newman
Na prática, isso significa que um serviço não consegue operar sozinho porque depende de outro para validar, processar ou concluir seu trabalho. E embora, à primeira vista, pareça uma integração “normal” ou até necessária, a forma como isso acontece pode se tornar um problema enorme conforme o sistema cresce.
O Problema Que Isso Carrega no Dia a Dia
O acoplamento de domínio nem sempre é fácil de identificar no início. Na correria do dia a dia, é comum pensar:
• “Ah, é só consultar o outro serviço para validar isso.”
• “Faz sentido pegar a regra de lá, é rápido!”
Mas essas decisões vão se acumulando, e logo o que deveria ser um sistema modular começa a agir como um grande monólito distribuído.
Vamos imaginar o Serviço de Pedidos de um sistema de e-commerce. Ele deveria ser responsável apenas por gerenciar pedidos, certo? No entanto:
1. Para confirmar um pedido, ele consulta o Serviço de Inventário para verificar a disponibilidade dos produtos.
2. Depois, calcula os pontos de fidelidade do cliente, chamando o Serviço de Fidelidade.
3. Por fim, precisa enviar notificações ao cliente, envolvendo o Serviço de Notificações.
No curto prazo, parece que está tudo funcionando. Mas o que acontece quando um desses serviços muda ou fica indisponível? O Serviço de Pedidos, que deveria ser autônomo, agora depende profundamente de como outros domínios funcionam
Como Somos Afetados Por Isso?
Para quem trabalha na manutenção ou evolução de sistemas assim, o impacto é imediato:
• Clientes irritados: Dados errados ou falhas no fluxo criam desconforto, reclamações e podem até afetar a reputação da empresa.
• Pressão na equipe: Resolver problemas que surgem dessas dependências mal gerenciadas geralmente exige longas horas de investigação para identificar onde tudo começou a dar errado.
• Complexidade crescente: Com o tempo, essas dependências transformam o sistema em algo difícil de entender e perigoso de modificar.
Como Newman aponta, a capacidade de implantação independente depende de serviços fracamente acoplados. Se cada mudança em um serviço exige alterações em vários outros, o sistema perde resiliência, escalabilidade e flexibilidade — características fundamentais que buscamos em uma arquitetura de microserviços.
Mas isso não é algo natural? 🤔
Se você está lendo isso e pensando: “Mas espera aí, não é normal que os serviços conversem entre si? Afinal, estamos falando de microserviços em um sistema distribuído.” — você não está errado! 😊
A ideia de microserviços é justamente criar componentes independentes, cada um cuidando de um pedaço específico do sistema, mas que ainda assim precisam trabalhar juntos. A integração entre serviços é inevitável e faz parte da essência de arquiteturas distribuídas.
Então, qual é o problema? 🤷♂️
O problema não está na integração em si, mas na forma como ela acontece e no grau de dependência que criamos entre os domínios. É quando essas conexões deixam de ser simples colaborações e se transformam em dependências profundas, amarrando 🪢 serviços que deveriam ser independentes.
Pensa comigo: se você já trabalhou em um sistema de microserviços, provavelmente já viu ou viveu situações assim 👇🏼:
1. Um Serviço de Pedidos precisa verificar algo no Serviço de Inventário, e tudo bem.
2. Mas aí, o Serviço de Pedidos começa a fazer mais: calcula pontos de fidelidade, envia notificações, até consulta relatórios para validar algo.
De repente, o Serviço de Pedidos virou quase um centro de controle. E se qualquer um desses outros serviços falhar ou mudar a forma como funciona, o Serviço de Pedidos também será afetado. Essa dependência exagerada não é natural, é um sinal de que o sistema está começando a perder seus limites.
Integração ≠ Acoplamento
Aqui está o ponto principal: integração entre serviços é natural, mas acoplamento excessivo não deveria ser. É natural que serviços precisem se comunicar para cumprir suas funções. Por exemplo, o Serviço de Pedidos pode consultar o Serviço de Inventário para verificar a disponibilidade de um produto. Mas, o que não é natural é quando:
• Um serviço começa a carregar regras de outro domínio, como calcular pontos de fidelidade ou verificar condições de pagamento.
• As mudanças em um domínio impactam diretamente outros serviços, exigindo ajustes em cadeia.
Como o próprio Sam Newman explica, essa dependência não deveria ser tão profunda a ponto de comprometer a independência de cada serviço.
Acoplamento de Domínio ≠ Compartilhar Regras Internas
A interação entre microserviços, como Newman explica, refere-se à utilização de funcionalidades oferecidas por outro domínio — algo que, em um sistema bem projetado, é esperado. Porém, o problema começa quando essa interação ultrapassa a linha do “utilizar” e entra no território de conhecer ou incorporar regras de negócio que pertencem exclusivamente ao outro domínio.
Por exemplo:
• É natural que o Serviço de Pedidos utilize uma funcionalidade do Serviço de Inventário para verificar a disponibilidade de produtos.
• Mas não é natural que o Serviço de Pedidos conheça as regras de alocação de estoque ou tente replicá-las dentro do próprio código. Esse tipo de dependência cria um acoplamento muito mais profundo e perigoso.
Como Isso Realmente Infiltra no Nosso Dia a Dia?
Vamos entender como o acoplamento de domínio surge nos sistemas e, pior ainda, como ele começa a criar raízes que se espalham silenciosamente pela arquitetura. 🧩 Muitas vezes, tudo começa com as melhores intenções: “Ah, só precisamos integrar mais um serviço aqui, é rapidinho.” Mas, antes que você perceba, você está lutando com abstrações estranhas e gambiarras que nem fazem sentido. 😅
Já se pegou trabalhando em um sistema que parecia um quebra-cabeça sem fim? Talvez construindo um “gerenciador de notificações” que conecta várias APIs ou uma “orquestração” que tenta alinhar um monte de serviços que vivem quebrando? Pois bem, essas são consequências diretas de responsabilidades misturadas e regras de negócio no domínio errado.
O Início da Bagunça 🎢
Geralmente, a confusão começa pequena e faz sentido no início. Imagine que você está trabalhando no Serviço de Pedidos. Ele parece simples: gerencia a criação, atualização e listagem de pedidos. Mas aí, surgem novas demandas:
• “Precisamos verificar o estoque antes de confirmar o pedido!”
• “Seria ótimo calcular os pontos de fidelidade no Serviço de Pedidos.”
• “Ah, e não podemos esquecer de enviar notificações para o cliente.”
De repente, o Serviço de Pedidos está conectado a:
• O Serviço de Inventário, para verificar o estoque.
• O Serviço de Fidelidade, para calcular pontos.
• O Serviço de Notificações, para avisar o cliente.
O problema surge quando regras de negócio específicas começam a ser replicadas ou carregadas no domínio errado. Por exemplo:
• Se o Serviço de Pedidos começa a calcular pontos de fidelidade diretamente, ele passa a carregar lógica que pertence ao domínio de fidelidade.
• Ou, se ele gerencia notificações diretamente, algo que deveria estar no domínio de notificações, ele cria sobreposição de responsabilidades.
Essas decisões comprometem a coesão e aumentam o risco de inconsistências no sistema.
As Abstrações Bizarras Aparecem 🤨
Com responsabilidades dispersas, surgem “soluções” criativas que, embora funcionem no curto prazo, só mascaram o problema:
• Camadas genéricas que tentam centralizar múltiplos serviços, mas aumentam a complexidade.
• APIs de orquestração, que adicionam controle centralizado, mas criam novos pontos de falha.
• Métodos cheios de condições para tratar exceções específicas, tornando o código confuso e difícil de manter.
Essas “gambiarras elegantes” apenas empurram o problema para frente, enquanto raízes de alto acoplamento continuam crescendo e enfraquecendo o sistema como um todo.
Um Exemplo do Dia a Dia 👣
Imagine um Serviço de Relatórios Financeiros. Ele deveria ser simples: gerar PDFs com dados de vendas. Mas, com o tempo, surgem novas demandas:
• Adicionar informações de estoque.
• Mostrar pontos de fidelidade dos clientes.
• Exibir o status dos pedidos.
De repente, o Serviço de Relatórios precisa:
• Consultar APIs ou bancos de dados de três outros serviços.
• Resolver incompatibilidades de formatos de dados.
• Lidar com falhas e latências em tempo real.
Para “resolver”, criamos uma camada de agregação que junta todas essas informações. Parece sofisticado, mas apenas mascara o problema real: regras de negócio duplicadas e limites de domínio ignorados.
A Diferença Sutil Que Faz Toda a Diferença
No dia a dia, a linha entre “utilizar” uma funcionalidade de outro serviço e “conhecer demais” as regras internas de outro domínio pode ser muito tênue. É fácil pensar:
“Tudo bem, vou só replicar essa regra aqui para facilitar.”
Mas, como Sam Newman nos lembra:
"utilizar a funcionalidade que o outro microserviço oferece.”
Isso significa que interagir é esperado, mas replicar regras ou responsabilidades não é. Por exemplo:
• É saudável o Serviço de Pedidos consultar o Serviço de Fidelidade para calcular pontos.
• Mas é problemático se ele replicar a lógica de cálculo de pontos dentro de seu próprio domínio, gerando inconsistências caso a regra mude no domínio de fidelidade.
A mensagem é clara: integração é saudável, mas acoplamento excessivo não é. 😉
Refinando e Evitando a Teia de Dependências de Domínio 🕸️
Quando falamos sobre microserviços, um dos maiores desafios é evitar que a arquitetura se transforme em uma teia de dependências entre domínios. Essas dependências podem dificultar a evolução do sistema e criar um efeito cascata de problemas a cada mudança. Para manter os serviços independentes e desacoplados, precisamos adotar estratégias que equilibram boas práticas técnicas com um profundo entendimento do negócio.
Ocultar Informações: Compartilhe Apenas o Essencial 🃏
Sam Newman destaca que uma arquitetura robusta começa com a ideia de esconder informações desnecessárias. Pense nisso como um jogo de cartas: você só revela as cartas que são essenciais para a jogada. Quanto menos expuser, menos dependência criará.
👉 Como isso funciona na prática?
• Imagine o Serviço de Inventário em um sistema de e-commerce.
• O Serviço de Pedidos precisa saber apenas se o produto está disponível no estoque, e não detalhes como localização física ou lote de fabricação.
• Em vez de expor uma API que devolve todos os dados de inventário, o Serviço de Inventário pode responder com um simples “Sim” ou “Não” para a pergunta: “Este produto está disponível?”
Esse tipo de abordagem mantém a interface enxuta e clara, minimizando a necessidade de ajustes sempre que algo muda no Serviço de Inventário.
Enviar Apenas os Dados Necessários 📦
Além de ocultar informações, é importante garantir que os serviços troquem apenas o que é realmente essencial. Quanto menos dados você enviar, menor será a complexidade da integração.
👉 Exemplo prático:
Se o Serviço de Pedidos precisa verificar a disponibilidade de um produto, ele deve enviar apenas o ID do produto, e não todas as informações do pedido. Isso reduz a carga de comunicação e evita a transferência de dados irrelevantes.
🔑 Dica: Manter a comunicação enxuta também ajuda a melhorar a performance do sistema, especialmente em serviços que lidam com grandes volumes de requisições.
Interface Estável e Bem Definida 📑
Uma interface bem projetada é como um contrato: ela deve ser clara, estável e confiável. Isso permite que os serviços evoluam internamente sem causar impacto nos consumidores da API.
👉 Exemplo prático:
Se o Serviço de Inventário mudar a forma como gerencia o estoque internamente, isso não deve afetar o Serviço de Pedidos, desde que a API pública do Serviço de Inventário continue fornecendo a mesma funcionalidade.
🔑 Dica:
• Use ferramentas como OpenAPI para documentar e padronizar as interfaces.
• Sempre que possível, implemente versionamento de APIs para garantir compatibilidade com versões anteriores
Conexão entre Negócio e Domínios 🏢
Todas essas estratégias funcionam melhor quando são baseadas em um entendimento claro do negócio. Saber como os domínios interagem ajuda a evitar dependências desnecessárias. Por exemplo:
• O Serviço de Pedidos deve gerenciar apenas pedidos. Ele não deve lidar com detalhes de inventário ou lógica de fidelidade.
• O Serviço de Inventário deve gerenciar apenas estoque. Ele não precisa saber o status completo de um pedido.
Quando respeitamos os limites de domínio e projetamos as interfaces pensando no mínimo necessário, conseguimos construir uma arquitetura sustentável, escalável e resiliente.
A Perspectiva de Negócio e o Acoplamento de Domínio 💼
Microserviços não são sobre fragmentar sistemas aleatoriamente, mas sobre refletir os domínios dentro de uma organização. Essa relação é uma das principais razões para adotá-los: alinhar tecnologia e negócio para criar sistemas mais coesos, escaláveis e resilientes.
Mas aqui está o ponto crítico: se não entendermos as necessidades de cada área de negócio e como elas se relacionam, acabamos criando uma teia de dependências difícil de gerenciar. O resultado? O que deveria ser uma arquitetura modular e ágil vira um sistema tão complexo quanto um monolito. 😬
Quando construímos microserviços, eles representam diretamente os domínios da organização. Cada serviço deve encapsular um domínio específico e operar de forma independente, respeitando os limites desse domínio. Isso significa que, antes de começar a escrever qualquer código, precisamos ter uma visão clara de:
• Quais são os domínios existentes?
Por exemplo, no contexto de um e-commerce, temos domínios como Pedidos, Pagamentos, Inventário e Fidelidade. Cada um deles desempenha um papel específico e atende a diferentes áreas de negócio.
• Como esses domínios interagem?
Embora sejam independentes, os domínios frequentemente precisam se comunicar. Por exemplo, o domínio de Pedidos pode precisar consultar o domínio de Inventário para verificar a disponibilidade de um produto. Mas essa comunicação deve ser clara e bem definida, sem misturar responsabilidades.
👉 Quando ignoramos essas definições de domínio, caímos no problema do acoplamento de domínio. Isso acontece quando as responsabilidades de diferentes áreas se misturam dentro de um serviço, criando dependências difíceis de gerenciar.
Por Que Isso Importa? 🌟
Se não respeitarmos os limites de domínio e não refinarmos as dependências, corremos o risco de criar sistemas que:
• Misturam responsabilidades de diferentes áreas de negócio, tornando-os mais difíceis de entender e manter.
• São frágeis, porque qualquer mudança em um domínio impacta outros de forma inesperada.
• Perdem a flexibilidade, comprometendo a escalabilidade e a evolução do sistema.
Por outro lado, quando projetamos microserviços com base em domínios bem definidos e necessidades claras do negócio, conseguimos criar uma arquitetura que:
• Reflete a organização: Cada serviço é um espelho da área de negócio que representa.
• Facilita a colaboração: Times de negócio e de tecnologia conseguem trabalhar juntos de forma mais eficiente.
• É resiliente e escalável: Os serviços podem evoluir independentemente, sem comprometer o sistema como um todo.
Agora que já falamos sobre domínios e dependências… 🤔
Que tal aprofundarmos nas estratégias para evitar o acoplamento de domínio entre microserviços? Especialmente aquele acoplamento forte que transforma uma arquitetura planejada para ser flexível em um sistema frágil e difícil de evoluir.
Esse tipo de acoplamento ocorre quando um serviço depende diretamente de outro para funcionar, seja por meio de chamadas síncronas frequentes ou por compartilhar lógica de negócios e dados. Felizmente, existem estratégias testadas e amplamente utilizadas na indústria que ajudam a minimizar dependências indesejadas e melhorar a autonomia dos serviços. Vamos explorá-las?
Comunicação Assíncrona: A Chave Para Resiliência?
A comunicação síncrona, embora útil em muitos cenários, pode introduzir acoplamento temporal. Quando um serviço precisa esperar pela resposta de outro para continuar, cria-se um vínculo de tempo que pode impactar a resiliência e aumentar a complexidade, especialmente em fluxos críticos.
👉 Solução: Adote eventos assíncronos para comunicação entre serviços sempre que possível e quando fizer sentido no contexto do negócio. Essa abordagem permite reduzir o acoplamento temporal, aumentando a resiliência e a escalabilidade do sistema, sem comprometer os objetivos da organização.
Como funciona?
• Em vez de o Serviço A fazer uma chamada direta para o Serviço B, ele publica um evento em um broker de mensagens (como Kafka, RabbitMQ ou SNS).
• O Serviço B processa o evento no seu próprio tempo, sem que o Serviço A precise esperar.
Benefícios:
• Desacoplamento temporal: O Serviço A pode continuar funcionando, mesmo que o Serviço B esteja temporariamente indisponível.
• Escalabilidade: Cada serviço processa mensagens no seu próprio ritmo, evitando sobrecargas.
• Flexibilidade: Outros serviços podem se inscrever nos eventos sem que o Serviço A precise ser modificado.
Definir Limites Claros de Domínio (Bounded Contexts) ✂️
Como já discutimos, os microserviços devem refletir os domínios de negócios. Quando os limites de domínio não estão claros, é comum que responsabilidades de diferentes áreas se misturem, gerando dependências desnecessárias.
👉 Solução: Use o conceito de bounded contexts do Domain-Driven Design (DDD) para mapear e delimitar os domínios de negócio.
Como fazer isso na prática?
• Entenda o negócio: Identifique os processos principais e agrupe responsabilidades relacionadas dentro de um único serviço.
• Respeite os limites: Cada microserviço deve conter apenas os dados e lógica que pertencem ao seu domínio.
• Exponha contratos claros: Use APIs bem definidas para interações entre domínios, evitando que um serviço precise entender os detalhes internos do outro.
Expor APIs Simples e Estáveis 📑
Quando um serviço precisa interagir com outro, a interface de comunicação deve ser clara, estável e focada no essencial. APIs complexas, que expõem muitos detalhes internos, criam dependências difíceis de gerenciar.
👉 Solução: Exponha APIs que ofereçam contratos simples e estáveis entre os serviços.
Boas práticas:
• Use versionamento de APIs para garantir compatibilidade com versões anteriores.
• Compartilhe apenas o que é necessário: se um serviço precisa saber se um item está disponível, forneça uma resposta “Sim” ou “Não”, em vez de expor toda a lógica de inventário.
• Prefira APIs orientadas a recursos ou eventos, em vez de métodos altamente específicos.
Segregar Dados com o Princípio de Autonomia 🔐
Um dos maiores erros que levam ao acoplamento forte é o compartilhamento de bancos de dados entre microserviços. Quando dois serviços acessam diretamente o mesmo banco, eles se tornam dependentes da estrutura e lógica de dados um do outro.
👉 Solução: Cada microserviço deve ter seu próprio banco de dados.
Por que isso é importante?
• Garante que os serviços possam evoluir independentemente.
• Evita que mudanças em um domínio impactem outros.
• Melhora a segurança e a escalabilidade.
Se um serviço precisar de dados de outro, ele deve acessá-los por meio de APIs ou eventos, nunca diretamente no banco.
Orquestração ou Coreografia? Escolha Com Cuidado 🎭
Na comunicação entre serviços, existem duas abordagens principais:
• Orquestração: Um serviço central (orquestrador) coordena os fluxos e decide a ordem das interações.
• Coreografia: Cada serviço toma decisões autonomamente com base em eventos.
👉 Solução: Use coreografia para fluxos descentralizados e orquestração apenas quando houver necessidade de controle explícito.
Exemplo:
• Um pedido confirmado pode gerar eventos que os serviços de Inventário e Pagamento processam de forma independente (coreografia).
• Se houver uma lógica complexa que exige validação em múltiplos passos, um orquestrador pode ser usado para coordenar o fluxo (ex.: aprovação de crédito).
Dica: Considere coreografia sempre que possível para manter os serviços descentralizados e autônomos. Essa abordagem é ideal para cenários onde os serviços podem reagir a eventos sem a necessidade de um controlador central.
Por outro lado, prefira orquestração em situações onde um fluxo de trabalho mais rígido e coordenado é necessário, especialmente quando a ordem das operações é crítica ou quando é importante centralizar a lógica do processo.
Monitoramento e Observabilidade 🔍
Mesmo com as melhores práticas, dependências entre serviços sempre existirão. O segredo é garantir que essas interações sejam transparentes e rastreáveis.
👉 Solução: Implemente ferramentas de observabilidade para monitorar o comportamento dos serviços e suas dependências.
O que monitorar?
• Tracing distribuído: Identifique o fluxo de uma requisição entre os serviços (ferramentas como Jaeger ou Zipkin são úteis aqui).
• Métricas de desempenho: Meça o tempo de resposta e o volume de eventos processados.
• Logs centralizados: Garanta que os logs estejam integrados para facilitar a identificação de falhas.
Será Que Podemos Eliminar o Acoplamento de Domínio Entre Microserviços? 🤔
Essa é uma pergunta que muitos arquitetos e desenvolvedores se fazem ao projetar sistemas distribuídos. A ideia de microserviços muitas vezes é vendida como a “promessa de independência total”, mas, na prática, é possível eliminar completamente o acoplamento entre serviços? Ou será que precisamos aceitar um certo grau de dependência como inevitável?
Vamos explorar essa questão sob as perspectivas de Sam Newman e Chris Richardson, especialista em arquiteturas baseadas em microserviços e autor de Microservices Patterns.
O Que É Realmente Possível?
Na teoria, queremos que cada microserviço seja completamente independente, encapsulando seu domínio e comunicando-se com outros apenas por meio de contratos claros. No entanto, como Newman explica, não é possível eliminar todo o acoplamento em sistemas complexos.
“A verdadeira meta não é a ausência de acoplamento, mas sim desacoplar os serviços de forma que as mudanças sejam contidas dentro de seus próprios domínios.”
Por sua vez, Chris Richardson reforça que o objetivo não é eliminar o acoplamento, mas garantir que ele seja fraco e bem gerenciado:
“Sempre haverá dependências entre serviços, mas você pode projetá-las para minimizar o impacto negativo.”
Portanto, o objetivo não é acabar com o acoplamento, mas:
1. Tornar os serviços autônomos o suficiente para que a maioria das mudanças não se propague desnecessariamente.
2. Controlar o impacto do acoplamento ao estabelecer limites claros e boas práticas.
Aceitando Certo Grau de Acoplamento
Há situações onde certos tipos de acoplamento são inevitáveis, especialmente porque os microserviços representam os domínios de uma organização e, por natureza, os domínios de negócio muitas vezes dependem uns dos outros.
1. Acoplamento de Dados 🗂️
Se dois serviços precisam de informações compartilhadas, como um ID de cliente ou status de pedido, é natural que haja alguma forma de dependência. Porém, isso pode ser minimizado com práticas como:
• Expor apenas os dados estritamente necessários.
• Evitar o acesso direto a bancos de dados de outros serviços.
2. Acoplamento de Fluxos 🔄
Quando um processo de negócio envolve múltiplos serviços (por exemplo, um pedido que envolve pagamentos, inventário e notificações), é inevitável que eles estejam conectados de alguma forma. A diferença está em como essa conexão é projetada:
• Prefira eventos assíncronos para evitar dependências temporais.
• Mantenha cada serviço responsável apenas pela sua lógica de domínio.
3. Acoplamento Semântico 🧩
O acoplamento semântico ocorre quando microserviços precisam entender e compartilhar conceitos de negócio, como o que significa “pedido”, “cliente” ou “estoque”. Diferente do acoplamento técnico (como uma dependência de API), esse tipo de acoplamento está relacionado a como os serviços entendem os dados e os eventos do sistema.
Como Reduzir o Impacto do Acoplamento?
Tanto Newman quanto Richardson sugerem estratégias para controlar o impacto das dependências entre serviços:
1. Mantenha os Contratos Simples e Estáveis 📝
2. Use Comunicação Assíncrona Sempre Que Possível 📡
3. Respeite os Limites de Domínio ✂️
4. Monitore e Gerencie as Dependências 🔍
Então o que queremos? Lego Microservices! É apenas uma brincadeira, não estou inventando uma regra ou termo novo 😂, vou explicar melhor no próximo tópico.
Por Que Queremos “Microservices Lego”?
Você já parou para pensar por que eu comparei microserviços com peças de Lego no início do artigo? Será que é só pela praticidade de encaixar e montar, ou existe algo mais profundo nesse paralelo? Fazer o paralelo entre microserviços e Lego é legal porque ajuda a simplificar um conceito complexo com algo visual e intuitivo. Todo mundo consegue imaginar como peças de Lego funcionam: são modulares, fáceis de montar, desmontar e reutilizar. Isso traduz de forma clara os princípios que queremos em arquiteturas modernas
Lego não é só brinquedo — é uma metáfora poderosa. Com suas peças simples, modulares e reutilizáveis, o Lego representa exatamente o que queremos alcançar com microserviços: flexibilidade, independência e possibilidades infinitas de construção.
Comportamentos: Centralizando o Propósito
Vamos refletir juntos, caro leitor: uma peça de Lego assume mais de um comportamento? Ou será que ela contribui para o comportamento do todo, mantendo seu papel único e bem definido? 🧐
Pense comigo: quando você monta um carro de Lego, cada peça tem uma função específica. As rodas são feitas para rodar, as janelas para decorar e os blocos para sustentar. Nenhuma peça tenta “ser tudo”. Isso é o que torna o Lego tão eficiente: cada elemento faz o que precisa ser feito e nada além disso. É a centralização do propósito de cada peça que permite que elas trabalhem juntas para criar algo maior — sem sobreposição ou confusão. E é exatamente isso que queremos alcançar com nossos microserviços.
Agora, faça uma forcinha na sua memória, caro leitor. Já encontrou um comportamento ou uma regra de negócio espalhados por mais de um microserviço? Talvez uma lógica de cálculo que aparece no Serviço de Pedidos e também no Serviço de Pagamentos. Ou quem sabe uma regra de validação repetida em várias APIs.
O que aconteceu? 🤔
• Provavelmente, em algum momento, isso gerou inconsistências: uma parte do sistema aplicou a regra de forma diferente da outra.
• Talvez tenha causado rollbacks inesperados, porque um serviço aprovou uma operação que outro rejeitou.
• Ou pior, criou confusão entre os times, que ficaram discutindo de quem era a responsabilidade final por aquela lógica.
E o impacto nos clientes?
É possível que alguém tenha recebido informações incorretas, um pedido tenha sido processado de forma errada ou uma transação tenha falhado. Essas situações não apenas afetam a experiência do cliente, mas também criam gargalos operacionais, atrasos e retrabalho para corrigir problemas que poderiam ter sido evitados.
O Perigo dos Comportamentos Dispersos 🚨
Imagine que essa lógica duplicada sofreu uma alteração. Um time atualizou o cálculo no Serviço de Pedidos, mas esqueceu de sincronizar no Serviço de Pagamentos. O que acontece? Um cliente pode receber um desconto no carrinho, mas ser cobrado um valor diferente no checkout. Isso causa desconforto no cliente e, para você e sua equipe, resulta em:
• Rollbacks inesperados.
• Tickets de suporte.
• Reuniões intermináveis entre times tentando rastrear o problema.
• E, em casos extremos, impacto financeiro direto.
Tudo isso porque um comportamento, que deveria ser único e consistente, acabou espalhado pelo sistema.
O Perigo do Comportamento Espalhado
Quando uma regra de negócio ou lógica de comportamento é duplicada em vários serviços, o sistema perde consistência e torna-se mais difícil de manter. Considere:
• Alterações ficam arriscadas: Se você precisa mudar uma regra, terá que ajustá-la em todos os lugares onde ela foi replicada, aumentando as chances de erros.
• Testes se tornam complexos: Como garantir que a lógica funciona da mesma forma em todos os serviços?
• Times entram em conflito: Quem é o responsável por aquela regra? Onde ela deveria estar?
Assim como um conjunto de Lego mal montado, onde as peças não se encaixam direito, um sistema com comportamento espalhado se torna confuso, instável e difícil de evoluir.
Por Que o Lego É Tão Especial? 🥸
1. Peças Independentes:
Cada peça de Lego tem sua própria função. Você pode construir um carro, desmontá-lo e usar as mesmas peças para criar um avião. O importante é que uma peça nunca depende de outra para “funcionar”.
Nos microserviços, isso se traduz no conceito de baixo acoplamento. Um serviço deve fazer o seu trabalho de forma independente, sem precisar de ajustes em outros serviços toda vez que algo mudar.
2. Foco na Coesão:
As peças de Lego são feitas para trabalhar bem juntas, mas cada uma é projetada com um propósito claro: rodas para rodar, janelas para decorar, blocos para sustentar. Esse foco em coesão garante que cada peça faça apenas aquilo para o qual foi criada — nem mais, nem menos. É exatamente isso que queremos nos microserviços.
3. Reutilização sem Esforço:
Você já desmontou um Lego para criar algo novo? 🏗️ Isso é possível porque as peças seguem um padrão universal: encaixes simples, consistentes e previsíveis.
Microserviços também devem ser assim. Quando bem projetados, eles podem ser reutilizados ou adaptados sem grandes mudanças. Por exemplo, um Serviço de Autenticação pode ser usado em vários sistemas sem precisar ser reescrito.
4. Escalabilidade Modular:
Com Lego, você pode começar pequeno e expandir à medida que precisar — um prédio de três andares hoje, um arranha-céu amanhã. 🏙️ Nos microserviços, essa é a essência da escalabilidade. Se um serviço precisa lidar com mais tráfego, você pode escalá-lo independentemente, sem precisar mexer nos outros.
A Busca por Harmonia nos Comportamentos 🎼
O que realmente queremos é que cada microserviço contribua com um comportamento único para o sistema, assim como cada peça de Lego adiciona algo essencial à construção. Não queremos rodas duplicadas em lugares diferentes, nem blocos com funções ambíguas.
Quando os comportamentos são únicos e consistentes, temos:
• Previsibilidade: Sabemos exatamente onde uma lógica vive e qual microserviço é responsável por ela.
• Confiança entre os times: Cada equipe entende seus limites e responsabilidades, sem entrar em conflito com outros domínios.
• Harmonia no sistema: As peças — ou microserviços — se encaixam de forma natural, formando um todo que é maior do que a soma de suas partes.
O Que Podemos Aprender com o Lego?
1. Tenha uma Base Sólida:
Um bom conjunto de Lego começa com uma base estável. Nos microserviços, isso significa entender os domínios do negócio e projetar serviços que reflitam essas fronteiras claramente.
2. Encaixes Consistentes:
As peças de Lego funcionam porque seguem um padrão universal. Com microserviços, o equivalente são APIs bem definidas e estáveis, que garantem que os serviços possam interagir sem confusão.
3. Evite a “Supercola”:
Já tentou colar peças de Lego? 🤔 Elas perdem toda a mágica. O mesmo vale para microserviços: quando você cria dependências excessivas entre os serviços, eles deixam de ser modulares e flexíveis.
Microserviços Como Peças de Lego
Pensar em microserviços como peças de Lego nos ajuda a visualizar o que é essencial para sistemas distribuídos: modularidade, flexibilidade e colaboração harmônica. Assim como no Lego, cada peça deve cumprir seu papel específico, encaixando-se perfeitamente com as outras, sem criar dependências complicadas ou quebrar a estrutura ao menor movimento.
Quando projetamos microserviços, queremos evitar aquela “cola invisível” que transforma algo modular em algo confuso e frágil. O paralelo com Lego nos lembra que os melhores sistemas são aqueles em que cada bloco é reutilizável, bem definido e fácil de ajustar — sem precisar desmontar tudo no processo.
Se você chegou até aqui, obrigado por explorar esse conceito comigo! Até o próximo artigo! 😁