| Bellacosa Mainframe e os mistérios dos microsserviços |
☕ Um Café no Bellacosa Mainframe
🕵️ CHARLIE CHAN E O MISTÉRIO DOS MICROSSERVIÇOS QUE NÃO ERAM MICROSSERVIÇOS
Distributed Monolith, APIs, bancos compartilhados, chamadas síncronas, observabilidade, consistência, Sagas e o estranho caso do COBOL acusado por um crime cometido pela arquitetura distribuída.
Sob a tutela de Charlie Chan
Há crimes em que o culpado deixa impressões digitais.
Há outros em que desaparece sem deixar vestígios.
E existem aqueles especialmente interessantes nos quais todas as evidências apontam para o suspeito errado.
Era exatamente esse o caso.
Naquela manhã, Charlie Chan foi chamado para investigar um incidente gravíssimo na fictícia Companhia Internacional de Pagamentos Oriental & Ocidental.
A empresa havia realizado uma enorme modernização.
Durante décadas, seu sistema central funcionara em IBM Z. COBOL, CICS e Db2 processavam clientes, contas, pagamentos e transações. A arquitetura certamente possuía limitações e décadas de evolução acumulada, mas havia uma característica difícil de ignorar:
funcionava.
Então chegou a transformação digital.
Consultorias apareceram.
PowerPoints apareceram.
Post-its apareceram.
Kubernetes apareceu.
Containers apareceram.
APIs apareceram.
Kafka apareceu.
E, naturalmente, apareceram microserviços.
Muitos microserviços.
Centenas deles.
O antigo sistema, considerado monolítico demais para o século XXI, foi cercado por uma maravilhosa arquitetura distribuída.
Até que determinada transação passou a levar quase dois segundos.
Imediatamente alguém encontrou o culpado:
COBOL.
Charlie Chan entrou silenciosamente na sala de guerra.
Na parede havia dashboards, diagramas, traces e gráficos coloridos.
Um arquiteto apontou para o mainframe.
— O legado está atrasando nossa transformação.
Charlie Chan observou o enorme desenho da arquitetura.
Mobile.
API Gateway.
Authentication Service.
Customer Service.
Order Service.
Inventory Service.
Pricing Service.
Payment Service.
Kafka.
Integration Service.
z/OS Connect.
CICS.
COBOL.
Db2.
Depois perguntou:
— Quanto tempo COBOL levou?
Silêncio.
Alguém consultou o monitor.
— Aproximadamente 38 milissegundos.
Chan sorriu discretamente.
Havia encontrado sua primeira pista.
🕵️ CAPÍTULO 1 — O morto tinha muitos suspeitos
Para entender o crime, precisamos entender o que microserviços deveriam proporcionar.
A ideia não é simplesmente transformar:
PROGRAMA A
PROGRAMA B
PROGRAMA Cem:
SERVICE A
SERVICE B
SERVICE Ce colocar HTTP entre eles.
Uma arquitetura de microserviços procura estabelecer fronteiras significativas, normalmente relacionadas às capacidades ou domínios do negócio.
Imagine uma loja.
Podemos ter:
CUSTOMER
ORDER
PAYMENT
INVENTORY
SHIPPINGCada domínio possui responsabilidades relativamente claras.
PAYMENT, por exemplo, deveria poder evoluir sem exigir que cinco outras equipes reconstruíssem simultaneamente seus sistemas.
Esse é um dos objetivos fundamentais:
reduzir acoplamento.
Charlie Chan escreveu três palavras no quadro:
DESIGN
RUNTIME
DATAO jovem programador COBOL perguntou:
— O que significa isso?
Chan apontou para a primeira.
Acoplamento de design
Se modificar A exige modificar B, existe dependência de design.
A mudou
↓
B precisa mudar
↓
C também precisa mudarDepois apontou para a segunda.
Acoplamento de runtime
Se A somente funciona enquanto B estiver disponível naquele exato instante:
A → Bhá dependência operacional.
Finalmente:
Acoplamento de dados
Se A precisa conhecer as estruturas internas de dados pertencentes a B:
SERVICE A
|
└────> TABLE_Btemos acoplamento de dados.
Charlie Chan circulou as três palavras.
Quando uma arquitetura apresenta simultaneamente forte dependência de design, runtime e dados, podemos ter algo muito interessante:
um monólito distribuído.
🕸️ CAPÍTULO 2 — O Distributed Monolith entra na sala
O primeiro suspeito da investigação era elegante.
Vestia containers.
Carregava YAML.
Falava Kubernetes fluentemente.
Apresentava-se como microservices.
Mas seus documentos contavam outra história.
Havia:
Customer Service
Order Service
Payment Service
Inventory Service
Shipping ServiceParecia perfeito.
Charlie Chan perguntou:
— Podemos alterar Payment Service e colocá-lo em produção sozinho?
O arquiteto respondeu:
— Tecnicamente sim...
Charlie Chan conhecia aquela palavra.
Tecnicamente.
Quase sempre alguma coisa interessante aparece depois dela.
— Mas?
— Precisamos coordenar com Order, Customer e Billing.
— Podemos implantar Customer independentemente?
— Normalmente fazemos release conjunto.
— E Inventory?
— Também.
Chan virou-se para o programador.
— Cinco casas com cinco portas não significam cinco famílias independentes se todos precisam sair juntos quando um morador troca a geladeira.
Era exatamente isso.
Fisicamente:
A B C D EArquiteturalmente:
A → B → C → D → EVocê não eliminou o monólito.
Você colocou uma rede dentro dele.
E essa distinção é extraordinariamente importante.
Um monólito tradicional pode possuir chamadas locais extremamente rápidas.
Um distributed monolith mantém parte do acoplamento do monólito e acrescenta:
DNS, TCP, TLS, serialização, desserialização, proxies, gateways, service discovery, containers, balanceadores, retries, timeouts e falhas parciais.
O velho:
CALL 'PAGAMENTO'vira conceitualmente:
COBOL CALL
↓
“Você está aqui.”Enquanto:
REST CALLpode significar:
Aplicação
↓
DNS
↓
Network
↓
TLS
↓
Gateway
↓
Load Balancer
↓
Container
↓
Framework
↓
JSON parser
↓
Business LogicO CALL deixou de ser uma porta.
Virou uma viagem.
🧩 CAPÍTULO 3 — O caso dos microserviços pequenos demais
O segundo suspeito parecia inocente.
Chamava-se Excessive Microservices.
A investigação encontrou:
CustomerNameService
CustomerAddressService
CustomerPhoneService
CustomerEmailService
CustomerPreferenceService
CustomerStatusServiceCharlie Chan perguntou:
— Por que existem seis serviços?
Responderam:
— Porque microserviços precisam ser pequenos.
Chan permaneceu alguns segundos em silêncio.
Esse é um dos maiores equívocos causados pelo próprio nome microservice.
O “micro” não significa:
menor quantidade possível de linhas de código.
Nem:
transforme cada função em servidor.
O limite deveria fazer sentido arquitetural e comercial.
Se cada pequena operação vira serviço, surge uma explosão de infraestrutura.
Cada serviço pode exigir:
repositório
pipeline
build
container
configuração
secrets
logs
monitoramento
alertas
testes
versionamento
documentação
ownershipMultiplique isso por 300.
A empresa pretendia diminuir complexidade.
Acabou industrializando-a.
Charlie Chan escreveu:
“Dividir problema não necessariamente divide complexidade.”
📞 CAPÍTULO 4 — As testemunhas conversavam demais
A terceira pista apareceu no distributed tracing.
Uma simples compra fazia:
Gateway
↓
Customer
↓
Address
↓
Inventory
↓
Pricing
↓
Discount
↓
Fraud
↓
Payment
↓
ShippingNove serviços para responder uma única solicitação.
Isso é Service Chatter.
Serviços excessivamente conversadores transformam pequenas operações em longas sequências de chamadas remotas.
Imagine que cada chamada leve apenas 30 ms.
Parece pouco.
Mas adicionamos:
latência de rede
processamento
autenticação
serialização
consultas
filas
TLS
proxiesE há algo ainda pior:
tail latency.
Um serviço pode normalmente responder em 20 ms, mas ocasionalmente levar 500 ms.
Se uma transação depende sequencialmente de muitos serviços, basta um deles espirrar para a transação inteira pegar gripe.
⛓️ CAPÍTULO 5 — A corrente síncrona
Charlie Chan encontrou então:
A → B → C → D → E → FCada serviço esperava pelo seguinte.
Se F falhasse:
A → B → C → D → E → F
XE esperariam:
E
D
C
B
Aaté os respectivos timeouts.
Mas alguém havia pensado na solução:
RETRY!
Agora:
F falhou
↓
retry
↓
retry
↓
retryEnquanto centenas de transações faziam exatamente a mesma coisa.
O sistema tentava resolver uma sobrecarga...
produzindo mais carga.
Essa é uma das armadilhas mais deliciosamente perversas dos sistemas distribuídos.
Retries são úteis.
Retries indiscriminados são multiplicadores de desastre.
Entram então padrões como:
Timeout
Circuit Breaker
Bulkhead
Backoff
Rate Limiting
Load Shedding
Async MessagingO Circuit Breaker é particularmente interessante.
Se determinado serviço está claramente falhando, por que continuar chamando-o milhares de vezes?
É semelhante ao disjuntor elétrico.
Detectamos:
FAIL
FAIL
FAIL
FAILe abrimos temporariamente o circuito:
A ─────X────→ Bevitando desperdiçar recursos e potencialmente agravando o incidente.
🗄️ CAPÍTULO 6 — Todos juravam ter seu banco. Mas havia apenas um
Charlie Chan então entrou no Db2.
Encontrou algo curioso:
Customer Service ─┐
Order Service ────┤
Payment Service ──┼── DATABASE
Shipping Service ─┤
Inventory Service ┘— Quem é proprietário desta tabela?
Silêncio.
— Todos.
Chan sorriu.
— Quando todos são proprietários, talvez ninguém seja.
Esse é o problema do Common Shared Database.
O problema não é simplesmente utilizar o mesmo servidor Db2.
É perfeitamente possível organizar:
Db2
│
├── CUSTOMER schema
├── ORDER schema
├── PAYMENT schema
└── SHIPPING schemaO ponto fundamental é ownership.
Payment não deveria simplesmente vasculhar tabelas internas de Customer porque encontrou um SELECT conveniente.
Caso contrário:
ALTER TABLE CUSTOMER...pode quebrar:
Customer
Payment
Order
Shipping
FraudAgora todos precisam coordenar mudanças.
Voltamos ao primeiro suspeito.
Distributed Monolith.
Charlie Chan percebeu que os suspeitos não eram independentes.
Eram cúmplices.
🔢 CAPÍTULO 7 — O JSON que mudou de aparência
Outra equipe resolveu melhorar uma API.
Antes:
{
"customerId": 317,
"name": "Charlie Chan"
}Depois:
{
"customer": {
"id": 317,
"fullName": "Charlie Chan"
}
}Muito mais elegante.
Deploy realizado.
Dezessete consumidores quebraram.
Bem-vindo ao Missing API Version Control.
Para um programador COBOL isso deveria soar assustadoramente familiar.
Imagine:
01 CUSTOMER-RECORD.
05 CUSTOMER-ID PIC 9(09).
05 CUSTOMER-NAME PIC X(40).Alguém modifica irresponsavelmente o COPYBOOK.
Dezenas de programas dependem daquela estrutura.
Temos o mesmo princípio.
COPYBOOK é contrato.
OpenAPI é contrato.
JSON é contrato.
Protobuf é contrato.
Mensagem MQ é contrato.
Evento Kafka é contrato.
Modernização não aboliu contratos.
Apenas lhes deu novos nomes.
🔭 CAPÍTULO 8 — O crime aconteceu, mas ninguém sabia onde
Então veio a pergunta mais simples de toda investigação:
— Onde a transação ficou lenta?
Todos olharam os dashboards.
Havia:
gateway.log
customer.log
payment.log
order.log
inventory.log
shipping.log
kafka.log
integration.logCada equipe possuía seu próprio pedaço da história.
Ninguém possuía o filme inteiro.
Esse é o Limited Observability.
Em sistemas distribuídos precisamos correlacionar eventos.
Imagine:
TRACE-ID = CHAN-0317Então conseguimos reconstruir:
03:17:00.001 Gateway
03:17:00.020 Authentication
03:17:00.051 Customer
03:17:00.094 Order
03:17:00.141 Inventory
03:17:00.209 Payment
03:17:01.710 Payment TIMEOUTAh...
03:17.
Quem acompanha o Bellacosa Mainframe sabe que alguma coisa sempre acontece às 03:17. 😉
Agora temos evidência.
Observabilidade moderna costuma trabalhar com três elementos extremamente importantes:
LOGS
METRICS
TRACESEu acrescentaria contexto operacional:
EVENTS
DEPLOYMENTS
CONFIG CHANGES
SECURITY EVENTSPorque descobrir que a latência começou às 03:17 é interessante.
Descobrir que:
03:16:48 deployment PAYMENT V7.4
03:17:00 latency spikeé muito mais interessante.
💾 CAPÍTULO 9 — O COMMIT desaparecido
Charlie Chan voltou-se então para o programador COBOL.
— Mostre uma transação tradicional.
Ele escreveu:
BEGIN
UPDATE ACCOUNT
INSERT PAYMENT
UPDATE ORDER
COMMITFalhou?
ROLLBACKElegante.
Mas agora cada domínio possui seus próprios dados:
Order DB
Inventory DB
Payment DB
Shipping DBA transação torna-se:
Create Order
↓
Reserve Inventory
↓
Charge Payment
↓
Schedule ShippingSuponha:
Create Order OK
Reserve Inventory OK
Charge Payment FAILO que acontece com o estoque?
Precisamos desfazer semanticamente a operação anterior:
Release Inventory
Cancel OrderEntramos no universo das Sagas e transações compensatórias.
E aqui existe uma diferença filosófica enorme.
No modelo transacional tradicional pensamos frequentemente:
ANTES
↓
TRANSAÇÃO
↓
DEPOISNo distribuído podemos precisar reconhecer estados intermediários:
CREATED
↓
INVENTORY_RESERVED
↓
PAYMENT_PENDING
↓
PAYMENT_FAILED
↓
COMPENSATING
↓
CANCELLEDEsses estados não são acidentes.
Fazem parte do desenho do negócio.
⚙️ CAPÍTULO 10 — A configuração escondida no código
Chan encontrou outra evidência:
PAYMENT_HOST=production.company.localDentro da aplicação.
Para mudar o endereço:
alterar código
↓
build
↓
teste
↓
deployTemos Embedded Configuration.
Configuração operacional deveria normalmente estar separada do código.
Mas Charlie Chan encontrou outra armadilha.
Externalizar tudo também não resolve magicamente o problema.
Se alguém consegue alterar:
PAYMENT_TIMEOUT=3000para:
PAYMENT_TIMEOUT=30000sem auditoria, acabamos criando outro mistério.
Configuração precisa de:
versionamento
controle de acesso
auditoria
validação
rollbackImagine durante uma investigação descobrir:
“Ninguém alterou o programa.”
Verdade.
Alteraram apenas a configuração.
O comportamento mudou completamente.
Essa diferença seria especialmente interessante em uma investigação de Red Team.
🧱 CAPÍTULO 11 — Um morreu e levou todos consigo
Restava Weak Fault Isolation.
Payment caiu.
Order começou a esperar.
Checkout esperou Order.
Gateway esperou Checkout.
Threads foram ocupadas.
Connections acabaram.
Retries começaram.
CPU subiu.
Memória subiu.
Filas cresceram.
E finalmente:
PAYMENT
X
↓
ORDER
X
↓
CHECKOUT
X
↓
SYSTEM
XCharlie Chan perguntou:
— Por que chamam isso de serviços independentes?
Ninguém respondeu.
Fault isolation deveria permitir algo mais próximo de:
Payment X
Catalog OK
Search OK
Customer OK
Order DEGRADEDTalvez o cliente não consiga pagar naquele momento.
Isso não significa necessariamente que ele também precise perder acesso ao catálogo.
Essa é a essência de graceful degradation.
🏛️ CAPÍTULO 12 — O suspeito que ninguém interrogou: o monólito modular
Havia outra pessoa na sala.
Ninguém havia interrogado.
Chamava-se:
Modular Monolith.
Durante anos, “monolith” tornou-se quase uma palavra ofensiva em determinados círculos tecnológicos.
Mas Charlie Chan não investigava palavras.
Investigava evidências.
Considere:
┌───────────────────────────────┐
│ APPLICATION │
│ │
│ CUSTOMER │ ORDER │ PAYMENT │
│──────────┼───────┼────────────│
│ INVENTORY│ SHIPPING │
│ │
└───────────────────────────────┘Um único deployable não significa necessariamente uma massa desorganizada.
Podemos possuir:
módulos
interfaces
encapsulamento
bounded contexts
ownership
testes
regras arquiteturaissem obrigatoriamente adicionar:
latência de rede
service discovery
distributed tracing
eventual consistency
orquestração
centenas de pipelines
falhas parciaisE aqui está uma excelente estratégia de engenharia:
comece estabelecendo boas fronteiras.
Depois, se uma fronteira realmente precisar virar serviço independente, extraia-a.
Não precisamos distribuir primeiro para descobrir o domínio depois.
🖥️ CAPÍTULO 13 — Finalmente interrogam o COBOL
Depois de horas de investigação, Charlie Chan voltou ao suspeito original.
COBOL.
O relatório dizia:
Tempo total da transação: 1.843 msParecia terrível.
Então Chan pediu a decomposição.
API Gateway 74 ms
Authentication 91 ms
Customer Service 63 ms
Order Service 112 ms
Inventory 127 ms
Pricing 86 ms
Payment 742 ms
Integration 164 ms
z/OS Connect 49 ms
CICS 31 ms
COBOL 38 ms
Db2 41 ms
demais esperas 225 ms
--------------------------------
TOTAL 1.843 msTodos olharam para o quadro.
COBOL havia sido acusado de produzir uma transação de quase dois segundos.
Sua participação direta era minúscula.
Charlie Chan finalmente declarou:
O homem mais velho da sala não necessariamente cometeu o crime apenas porque estava presente quando aconteceu.
O mainframe não estava necessariamente lento.
Haviam construído uma rodovia cheia de pedágios para chegar até ele.
☕ CAPÍTULO 14 — O que um programador COBOL iniciante deveria aprender disso?
Muito.
Especialmente que os conceitos modernos não são tão alienígenas quanto parecem.
Veja:
| Mundo tradicional | Mundo distribuído |
|---|---|
| COPYBOOK | API Schema |
| CALL | Service Call |
| COMMAREA | Payload |
| MQ Message | Event/Message |
| COMMIT | Local Transaction |
| ROLLBACK | Compensação/Saga |
| Return Code | HTTP Status/Error |
| SMF/monitoramento | Telemetry |
| Correlation ID | Trace ID |
| Timeout | Timeout |
| RACF | Identity/Authorization |
| JCL/PROC/Parms | Deployment/configuration |
As implementações mudam profundamente.
Os problemas fundamentais, nem tanto.
Ainda perguntamos:
Quem chama quem?
Quem possui os dados?
Quem pode alterar?
Quem depende dessa interface?
O que acontece se falhar?
Como recuperamos?
Como sabemos o que aconteceu?Essas perguntas atravessam gerações tecnológicas.
🕵️ CAPÍTULO 15 — Charlie Chan monta o quadro do crime
Chan finalmente ligou todos os fios.
DISTRIBUTED MONOLITH
│
┌─────────────┼─────────────┐
↓ ↓ ↓
DESIGN RUNTIME DATA
COUPLING COUPLING COUPLING
│ │ │
↓ ↓ ↓
API versions chatter shared DB
deployments sync chains consistency
contracts retries transactions
│ │ │
└─────────────┼─────────────┘
↓
COMPLEXIDADE
↓
OBSERVABILIDADE
↓
FAULT ISOLATIONFinalmente tudo fazia sentido.
Os dez anti-patterns da figura original não eram dez crimes isolados.
Eram manifestações de algumas forças fundamentais.
Acoplamento.
Distribuição.
Complexidade.
Quando dividimos uma aplicação fisicamente sem reduzir suficientemente o acoplamento lógico, obtemos o pior dos dois mundos.
Carregamos problemas do monólito...
e adicionamos problemas de sistemas distribuídos.
🔎 CAPÍTULO 16 — O checklist Charlie Chan
Antes de criar CustomerService, PaymentService ou WhateverService, Charlie Chan faria perguntas.
Não começaria perguntando:
Qual framework vamos utilizar?
Perguntaria:
Existe uma fronteira real de domínio?
Depois:
Esse componente precisa evoluir independentemente?
Precisa ser implantado independentemente?
Possui necessidades próprias de escala?
Precisa de isolamento operacional?
Existe ownership claramente definido?
Se ficar indisponível, o restante do sistema consegue continuar funcionando?
Quem possui seus dados?
Quantas chamadas serão necessárias para completar uma operação?
Como rastrearemos uma transação entre os componentes?
Como trataremos consistência?
Como evoluiremos contratos?
E existe uma pergunta especialmente incômoda:
Que problema concreto seria impossível — ou significativamente pior — resolver mantendo este componente dentro de um monólito modular?
Se ninguém souber responder...
Charlie Chan já encontrou uma pista.
🧠 EPÍLOGO — O mistério nunca foi COBOL versus microservices
Ao final da investigação, o jovem programador perguntou:
— Mestre Chan, então microserviços são ruins?
Charlie Chan sorriu.
Essa seria uma conclusão fácil demais.
Microservices podem ser extraordinários quando suas características resolvem problemas reais.
Grandes organizações podem precisar de autonomia entre equipes.
Partes específicas podem possuir escalabilidade completamente diferente.
Certos domínios podem precisar de ciclos de release independentes.
Alguns componentes podem exigir isolamento de falhas.
Tecnologias diferentes podem ser apropriadas para problemas diferentes.
Há excelentes motivos para distribuir sistemas.
O erro está em confundir:
DISTRIBUIÇÃOcom:
MODERNIZAÇÃOou:
QUANTIDADE DE SERVIÇOScom:
QUALIDADE ARQUITETURALUma aplicação não se torna moderna porque ganhou 200 containers.
Assim como um sistema COBOL não se torna ruim simplesmente porque nasceu há quarenta anos.
Arquitetura precisa ser julgada pelas propriedades que entrega.
Charlie Chan fechou seu caderno.
Na última página havia apenas uma frase:
“Quando vinte microserviços precisam concordar para mudar uma linha, procure novamente. Monólito pode estar usando vinte disfarces.”
Ele caminhou em direção à porta.
Então parou.
Voltou ao terminal.
Abriu o trace do incidente mais uma vez.
Havia uma informação que ninguém percebera.
TRACE-ID: CHAN-0317
TIMESTAMP: 03:17:00Chan sorriu.
O programador COBOL arregalou os olhos.
— 03:17 outra vez?
Charlie Chan colocou o chapéu.
— Alguns mistérios pertencem à arquitetura.
E saiu.
No Bellacosa Mainframe, entretanto, todos já sabiam:
se aconteceu às 03:17, o incidente ainda não terminou. ☕🕵️
☕ MORAL DO CASO
A grande lição para quem está começando em COBOL — ou estudando arquitetura moderna — é surpreendentemente simples:
não modernizamos um sistema trocando nomes antigos por nomes modernos.
Trocar:
PROGRAMpor:
SERVICEnão elimina acoplamento.
Trocar:
COPYBOOKpor:
JSONnão elimina contratos.
Trocar:
CALLpor:
RESTnão elimina dependências.
Trocar:
COMMITpor:
EVENTSnão elimina consistência.
E trocar:
MONOLITHpor:
MICROSERVICESnão produz automaticamente uma boa arquitetura.
Às vezes produz algo muito mais curioso:
MONÓLITO
+
REDE
+
200 CONTAINERS
+
KAFKA
+
KUBERNETES
=
DISTRIBUTED MONOLITHE talvez essa seja a pista mais importante deixada por Charlie Chan:
tecnologia muda. Os nomes mudam. As ferramentas mudam. Mas dependência, acoplamento, responsabilidade, falha, dados e complexidade continuam sentados na mesma sala de interrogatório.
O bom arquiteto não pergunta primeiro:
“Como transformamos isto em microserviços?”
Pergunta:
“Qual problema estamos tentando resolver?”
Somente depois escolhe a arquitetura.
Porque, como descobriríamos naquela madrugada no Bellacosa Mainframe, às vezes o velho COBOL acusado de todos os crimes estava tranquilamente processando sua transação em 38 milissegundos.
Os outros 1.805 milissegundos estavam ocupados sendo modernos. ☕🕵️♂️💻
Sem comentários:
Enviar um comentário