Monolito tem sido uma palavra com pesada conotação negativa de há uns tempos para cá. Especialmente com o aparecimento da Arquitetura em Micro Serviços. A arquitetura de micro serviços parte dos conceitos de Service Oriented Architecture (SOA) e promove o conceito de separa física de serviços em várias máquinas interligadas por rede.
Algumas empresas, como a Netflix, popularizaram esta configuração de arquitetura e rapidamente ela foi se expandindo todos os tipos de sistema, mesmo os não baseados em streaming. Eventualmente chegou nos sistemas corporativos de transação e registro. Antes, os sistema de transação e registro viram sua evolução de sistemas cliente-servidor para sistemas cliente-servidor de aplicação-servidor de banco de dados, especialmente nos nichos Java e .NET.
Então alguém teve a ideia de transformar sistema de transação e registro em sistemas baseados em micro serviços. Impulsionada pelo novo brinquedo da vez: a nuvem.
A justificação da migração de um modelo arquitetural para outro é baseadas nas várias vantagens que um sistema de micro serviços deve oferece:
Modularidade – cada micro serviço é independente dos outros. Como cada micro serviço só atende uma função especifica isso torna o sistema mais simples de entender, desenvolver e manter.
Escalabilidade – cada micro serviço pode ter várias instâncias rodando que podem ser ligadas e desligadas usando as tecnologias de nuvem, permitindo que apenas as instância necessárias à carga demandada sejam usadas. Quando há mais carga novas instâncias são colocadas em funcionamento para distribuir a carga, e desligadas quando não são mais necessárias. Isto é possível porque, normalmente, os micro serviços são stateless e não importa realmente em que instância chegou o request.
Desenvolvimento Autônomo – cada micro serviço pode ser criado por um team diferente que pode decidir qual tecnologia usar e como construir o serviço de forma autônoma modificando-o e fazendo novos deploys à sua vontade.
Integração – cada micro serviço pode ser construido com a tecnologia que for necessário. Isto permite criar micro serviços em um parque heterogêneo de tecnologias e permitir que todos se comuniquem entre si.
Contudo, hoje em dia depois de mais de 10 anos com este padrão de configuração de arquitetura, começam-se a ver algumas falhas. Falhas que sempre estiveram lá, mas que foram ignoradas pelo hype ou porque as empresas de nuvem juraram a pés juntos que não existiam.
A primeira e mais óbvia falha é que micro serviços precisam se comunicar e para isso precisam de uma rede entre eles. A rede não é apenas necessária para se comunicarem , mas para se acharem uns aos outros. Logo precisamos de orquestradores – como o Zookepper e outros – para manter ordem e governança sobre os micro serviços. Por outro lado, sendo que estão distribuídos e são autônomos, precisamos saber se estão ligados, desligados, com problemas, se precisamos mais instâncias, etc. Enfim, as mesmas preocupações que tínhamos um sistema cliente-servidor, mas potencializado pela rede de interconexões entre os micro serviços. Em cima disso tudo, a própria rede é um problema.
Sabemos que a rede não é fiável, nem segura, nem permanente e os serviços podem não se conseguir comunicar no momento que é necessário. Na presença de rede, o design muda de mecanismos síncronos e rápidos de comunicação para mecanismos assíncronos com várias tentativas e erro de entregar os dados necessários. Os micro serviços não são realmente autônomos porque precisam se conectar a uma rede segura de comunicação e a outros serviços que mediam a comunicação; tal como sistemas de mensageria. Por outro lado, podem precisar de Repetidores e Circuit-Breakers que adicionam complexidade no processo de comunicação, e em última análise não garantem a entrega dos dados.
Com a rede e a extrema distribuição de tarefas entre os serviços nasce o problema da consistência. Transações distribuídas são muito mais complexas que as monolíticas e exigem muito mais esforço e orquestração entre os serviços. Isso quando funcionam. O micro serviço não é assim tão autônomo se precisa respeitar um protocolo comum a todos os micro serviços de um processo. Novos conceitos surgem – como sagas – mas são nada menos que soluções ad hoc para um problema já conhecido e resolvido no monolito.
O teorema CAP nos ensina que perante um modelo de micro serviços é impossível manter, simultaneamente, a consistência e a disponibilidade. Como micro serviços são desenhados para maximizar a disponibilidade ( com suporte da nuvem subjacente) é a consistência que falha , levando a novos conceitos como Consistência Eventual que significa que, em algum ponto do futuro, o sistema estará em um estado consistência. Eventualmente, no futuro, estará. Só não se sabe quando.
Esta moda levou alguns bancos – antes bastiões da consistência – a adotar estratégias de micro serviços com consistência eventual. Ou seja, eventualmente, aquela transferência vai cair na sua conta. O gera de vez em quando um panico da pessoa que viu o dinheiro sair de uma conta, mas não chegar na outra, não porque há um processo burocrático, mas porque o software simplesmente vai demorar um pouco a mostrar as coisas nos lugares certos.
Claro que nada disto teria sido possível sem a nuvem. Na nuvem a empresa tem que alugar espaço em disco, espaço em memória, espaço no banco de dados e tempo na CPU… o que outrora era um caso de uma compra única, agora é um pagamento constante e continuo. É claro que a nuvem quer que seus sistemas sejam o mais complexos possíveis, porque assim mais coisas serão cobradas. Esta conversa engana muita gente há muito tempo, mas vemos agora um movimento de saída da nuvem e volta aos data-centers. Afinal não há nada vendor-specific que impeça de criar sua própria nuvem no seu próprio data center, ou num data center alugado – tudo é feito com ferramentas open-source. E ao mesmo tempo que isto acontece, é colocado em causa a razão para tanta separação para começo de conversa. O que pareciam pontos positivos são afinal pontos negativos no muito longo prazo.
Desenvolvimento Autônomo – parece bom que cada equipe crie seu software com a sua própria tecnologia e stack, mas no que isso resulta é em uma torre de babel. Um parque com várias tecnologias, implica várias pessoas com skills diferentes que não podem ser intercambiadas. Se tem um parque com 10 serviços na tecnologia X só precisa de pessoas que saibam X. Mas se cada serviço tem uma tecnologia diferente, precisa de 10 pessoas diferentes – ou, precisa de pessoas que têm mais skills o que implica em mais custo na mesma. Por outro lado, sem controle e governança o team pode ter escolhido tecnologias de nicho que hoje não têm mais evolução e portanto não atraem desenvolvedores dificultando a contratação e aumento o custo, o que torna o serviços em legado que é duro e custoso de manter. Demasiada escolha, não é escolha alguma e acabamos num caos ou com tecnologias obsoletas.
Integração – Softwares de integração sempre existiram e podem ser feitos sem muito problema, não significando que todo o eco sistema tem que ser uma miríade de serviços de integração interconectados. Quando precisarmos realizar uma integração, ou com um sistema legado ou com um sistema externos , podemos continuar fazendo isso sem que o sistema principal tenha que ser pulverizado em pequenos softwares. Muitas vezes as integrações até são simples e podem facilmente ser realizadas dentro do próprio sistema principal – claro está, se devidamente isoladas a contento. Não precisamos de rede para isolar os sistemas, temos padrões de design para isso.
Escalabilidade – A nuvem, o data center ou seja qual for a plataforma que usa para rodar seu sistema, não lhe importa se seu software faz uma só coisa ou vinte ou cem. Ter múltiplas cópias do mesmo software rodando sempre fez parte do quotidiano. Apenas alguns cuidados precisam ser respeitados como não haver estado de sessão guardado nas instâncias, por exemplo. A escalabilidade não é exclusiva dos micro serviços.
Modularidade – Construir um software de forma modal tem que ver com a correta e eficaz separação de responsabilidades e identificação de contextos de domínio. Isto é assim independentemente se há uma rede conectando os domínios ou não. Porque os team de software, em sua larga maioria, são muito maus a cumprir as regras de isolamento e modularização alguém achou que a solução era forçar essa separação lógica impondo uma separação física. E sim, funciona – em parte – mas nem sempre. A separação física não força uma correta analise e compreensão do domínio e das responsabilidade o que leva muitas vezes a ter que migrar a responsabilidade entre micro serviços. O isolamento físico não resolve nada, só torna as coisas mais complexas quando se descobre que a analise foi errada.
Enfim, tudo o que é possível com micro serviços é possível sem eles. Aliás é até mais fácil porque não precisamos de rede entre as partes para que funcione.
Micro serviços têm o seu lugar ao sol, mas isso não significa que todos os sistemas do mundo devem ser construidos com essas configuração. Deve ser feito assim como com tudo em software: quando for estritamente necessário.
Além do hype da nuvem e dos vendors forçando a ideia de que quanto mais distribuído melhor, uma das razões da adoção de micro serviços foi a má qualidade do sistemas monolíticos que existiam. Sistemas mal desenhados, mal feitos, mal programados e sem a devida separação lógica de responsabilidades: o famoso, código espaguete. Esta má fama dos servidores de aplicação da época, aliada a uma nova geração de ferramentas – a nuvem – e a uma nova geração de desenvolvedores que não aprenderam engenharia de software direito, resultaram em uma adoção massiva de uma configuração de arquitetura desnecessariamente complexa para a maioria dos sistemas, especialmente sistemas de transação e registro como sejam sistemas de banco ou qualquer ERP.
O termo monolito vem desse conceito que estava tudo já colado que era impossível separar as partes. É tudo uma peça só, tal como um monolito.
Quando separamos as responsabilidades corretamente e isolamentos uma parte da outra temos um sistema modular. Este é o sistema que sempre foi o objetivo, mas que as pessoas não conseguiam construir por falta de conhecimento teórico e prático. O monolito modular nada mais é que um software construido em conformidade com tudo o que sabemos que são boas práticas em engenharia de software. Cada módulo atua da mesma forma que um micro serviço – isolado dos outros – mas não há rede entre eles, transações são muito mais simples de trabalhar e se for mesmo necessário podemos extrair o módulo para um software à parte.
Existem alguns casos onde ter sistema heterogêneos pode realmente ser uma necessidade, mas é mais comum que não seja necessário. Um sistema homogêneo com uma só tecnologia é muito mais barato de construir e manter – desde que, é claro – tenha escolhido a tecnologia certa. Com um monolito modular construido com a tecnologia errada você comete um erro. Mas com N micro serviços construidos com a tecnologia errada, você comete N erros. Na prática migrar N serviços é tão complexo quanto migrar um monolito porque na prática, os micro serviços não são realmente desacoplados e o que foi construido é um código espaguete distribuído.
Cada vez mais ouvimos falar de monolitos modulares e isso não é por acaso. Eles são o software como sempre foi intencionado que o software fosse e servem de base para qualquer outra configuração, inclusive micro serviços.
Um último adendo em relação à Lei de Conway que – simplificadamente – estabelece que uma organização criar os seus sistemas como reflexo da sua própria organização. Esta é a razão porque parece natural que em um empresa com N departamentos tenhamos N softwares – N micro serviços – cada um isolado do outro, mas comunicando com o outro , tal e qual as pessoas dos departamentos. Parece sensato que cada departamento tenha seu próprio team e sua própria tecnologia porque assim podem mudar o software mais rapidamente e mantê-lo a par com as necessidades mais atuais. Contudo, isto é contrário à realidade de que esses departamentos formam uma empresa só, e que as regras devem ser comuns à empresa e estabelecidas em conversação. Como as pessoas dos departamento não sabem conversar para chegar num domínio comum, ficam satisfeitas em espelhar no software a mesma organização. Só que o software também precisa comunicar e se não é claro como os departamento comunicam, não vai ser claro como fazer o software. Por isso, mesmo em um arquitetura de micro serviços é preciso ajustar as responsabilidades e movê-las de um serviço para outro: porque não era claro quem era responsável pelo quê. Mas, em um ambiente heterogêneo esta movimentação é várias ordens de grandeza mais complexa.
A origem do problema é compartimentação artificial da empresa e um segundo problema é espelhar isso no software. Conforme o tempo passa e as modas passam a compartimentação muda, mas mudar os micro serviços é muito mais custoso e caro que mudar um monolito modular, que é modular, mas é apenas um só: tal como uma empresa.