Arquiteturas síncronas em sistemas distribuídos modernos, comunicação.

Renato Rosa Guimarães
Devspoint
Published in
7 min readDec 15, 2021

Olá pessoal, tudo bem? Estamos de volta para falarmos de arquiteturas síncronas, se você não viu o artigo anterior (que faz uma introdução sobre o tema), sugiro dar uma conferida aqui. Hoje a abordagem é específica para entendermos como os processos (ou serviços) se comunicam e o que precisamos observar de detalhes quando estamos implementando uma jornada que utiliza um modelo síncrono.

Um pouco de história

Bom, falar de comunicação parece fácil (e é, na verdade), mas quando estamos falando de sistemas distribuídos, as coisas podem ficar um pouco nebulosas. Desde os primórdios dos sistemas distribuídos, comunicação sempre foi tratada como um processo habilitador para que peças independentes pudessem se relacionar através de um meio seguro usando um dialeto comum. Parece simples de entender, mas quando colocamos uma lupa sobre o assunto, percebemos que existem diversas peculiaridades.

Num passado não tão distante (< 30 anos), os sistemas distribuídos eram construídos e publicados em uma mesma instância de hardware (não havia um modelo de cloud ou computação distribuída como conhecemos hoje), cada aplicação era executada em um processo diferente e, portanto, tinha um "isolamento" de recursos (gerenciado pelo kernel do sistema operacional) que garantia a sua 'independência' de execução.

representação simples de uma interação de um sistema distribuído em um único hardware.
representação simples de uma arquitetura distribuída dentro do mesmo hardware

Parece bizarro, mas essa era uma forma de construir um sistema distribuído: Colocar todas as aplicações em uma peça de hardware única, pois, na prática, a aplicação tinha um comportamento de um sistema distribuído, mas a realidade era diferente: se o hardware falhasse, todas as aplicações iam para o beleléu.

Olhando dessa maneira, não se via vantagem absoluta em usar esse modelo que não fosse o deployment modularizado, o que garantia uma velocidade de desenvolvimento maior (era mais fácil distribuir tarefas de desenvolvimento sem impactar o todo da aplicação). E, como tudo estava contido dentro do mesmo hardware, a latência de comunicação tendia a zero.

processo de desenvolvimento “paralelo”

Bom, a tecnologia e os processos foram se modernizando, novas práticas foram surgindo e hoje temos um vasto leque de opções para criarmos aplicações que tem um viés de serem realmente distribuídas. Para abordarmos de maneira bem prática o tema de comunicação, vamos começar pela famigerada sigla IPC, ou, interprocess communication (comunicação inter-processos, em tradução livre).

O modelo

A comunicação interprocessos pavimenta uma longa lista de modelos de interação entre as aplicações, porém, como estamos falando de arquitetura síncrona, o único modelo suportado aqui é o famoso "Request-Response".

O modelo "Request-Response" mimetiza muito do comportamento humano, por exemplo, quando vamos iniciar uma conversa, o interlocutor e o ouvinte devem estar 'presentes' (figurativamente falando) no mesmo ambiente ao mesmo tempo para que a comunicação aconteça. Há um fator muito importante nesta "equação" também, o idioma utilizado. Se o interlocutor e o ouvinte falam idiomas diferentes, muito provavelmente essa comunicação não acontecerá como o esperado (ou não acontecerá de fato). Numa abstração sistêmica, as premissas são as mesmas, as aplicações devem estar 'presentes' (em quesito de contexto) e se comunicar através de um idioma comum (mediado por um protocolo, por ex.).

modelo de interação via comunicação

Apesar do óbvio estar sendo dito aqui, é importante mentalizar este cenário para podermos visualizar com mais clareza alguns aspectos que costumamos negar durante a construção de um produto/serviço digital.

Contexto e considerações

Vamos começar pelo contexto, toda jornada de um produto, normalmente exige uma série de execuções de funcionalidades de domínios técnicos distintos. Se considerarmos o uso de técnicas de modelagem de software modernas, conseguimos fazer os agrupamentos dos serviços com maior afinidade (de requisitos!) em domínios específicos, e, para que esses domínios se relacionem, normalmente construimos canais de comunicação utilizando um (ou mais) protocolo em comum (se estivermos falando de REST HTTP, o HTTP seria o protocolo aqui). O contexto é criado quando iniciamos uma transação em um domínio, e esta transação se propaga para outros domínios, e, todo contexto tem um prazo de vida (normalmente utiliza-se o TTL (ou time-to-live)).

diagrama de jornada ‘super’ simplificado

Transações em arquiteturas síncronas normalmente tem um contexto ágil de execução, pois as aplicações costumam trabalhar em um modelo de atendimento 'best-effort', garantindo que todos os estímulos que estejam recebendo sejam atendidos no prazo mais curto possível (dado todas as limitações de infraestrutura impostas).

O contexto de execução é naturalmente influenciado pela carga de dados que está transportando, isto é, para contextos "ágeis", precisamos trafegar um volume menor de informação em poucos 'saltos' interprocessos.

"Tá, mas no que isso se traduz?". Bom, acho que a analogia mais apropriada para isso é a seguinte:

"Imagine que você quer conversar com um amigo seu, mas sua conversa é sobre um tema o qual ele não está familiarizado, naturalmente, para que a conversa flua de maneira apropriada, você vai precisar contextualiza-lo sobre o assunto. Se você conta uma história muito longa, o seu amigo vai levar um tempo até "digerir" todas as informações e te dar uma opinião ou conselho, e, da mesma forma que uma história longa pode afetar o tempo de "digestão", uma história mais resumida pode trazer mais agilidade, porém, a contextualização vai ser mais fragmentada, o que influencia também na velocidade no qual a opinião vai ser entregue para o interlocutor"

representação de tipos de contexto

Trazendo para o técnico, podemos utilizar a analogia acima como base, interpretando que o interlocutor é um serviço (ou microsserviço) e o ouvinte é outro serviço. Para estabelecerem a comunicação formal (ou dialeto), a linguagem comum que podemos utilizar é padrão HTTP, por exemplo. E a mensagem trafegada é o payload, que pode ter vários formatos, sendo um dos mais populares, o JSON.

"Dããã, então não vai ter o que fazer, né?". Isso não é inteiramente verdade! A ideia que gira por trás de uma comunicação eficiente é o poder da síntese. Sintetizar um assunto é você trazer de forma concisa as referências necessárias para que você possa tomar uma decisão, assumindo que o ouvinte conheça algumas premissas. Para sistemas distribuídos, a história é a mesma: Você passa contratos (ou estruturas de dados, entidades e etc) de maneira objetiva entre os serviços (ou processos) esperando que as respostas sejam devolvidas de maneira rápida.

Quais são as considerações importantes que devemos pensar em contextos de jornada? É difícil dizer com precisão absoluta, mas temos os seguintes pontos a serem considerados:

  • Tamanho dos contratos (estruturas mais enxutas podem trazer mais velocidade em processos de serialização e 'desserialização' das mensagens);
  • Simultaneidade no atendimento de requisições (aqui é mais difícil especular, mas, com um pool de threads maior, garante mais 'superfície de contato' para o processamento das chamadas, porém, cada chamada pode ter um tamanho variável de tamanho e isso pode impactar na saúde do serviço que está servindo as requisições, muito cuidado!);
  • Linearidade no comportamento do processo (ser elástico não deve ser uma característica apenas de infraestrutura, mas também de aplicação, jornadas complexas podem sofrer com rajadas (ou burst traffic) por alguma campanha de negócio, e, para que as aplicações tenham o menor desvio padrão possível no tempo de processamento, devemos criar uma maneira de gerenciar volumes substanciais de requisições);
  • Objetividade nas jornadas (por último e não menos importante, as jornadas precisam ser objetivas naquilo que se propõem, devemos evitar a dispersão do contexto em chamadas paralelas que não irão agregar valor para o cliente final (transações mais demoradas)).

A "malha" e a plataforma

Antes de irmos para uma breve conclusão, acho que é importante citar aqui que um produto digital, no final das contas, é uma malha de serviços/processos interligada intimamente por um objetivo comum, e, sabendo disso, é importante nos atentarmos aos sinais de interferência que esta malha possa estar sofrendo.

Isso se resolve (parcialmente, mas com boa cobertura) com métricas de infraestrutura e de aplicação, que nos vão dar insumos sobre tempos de chamada, utilização de hardware do serviço, processamento dos payloads e etc. Em arquiteturas síncronas, quaisquer perturbações na "malha", podem impactar severamente na operação do produto, isto é, se você possui 'orquestradores', muito provavelmente eles serão os serviços impactados negativamente mediante as falhas na jornada (e muito provavelmente serão serviços que consumirão mais recursos (containeres maiores ou maior quantidade de réplicas).

É importante fazer um balanço geral das capacidades de infraestrutura que a sua plataforma suporta para você ter um direcionador de dimensões de unidades de serviço você pode ter (quantos containeres, volumes, instâncias e etc). Lembre-se: existe um limite teórico de "concorrência(ou paralelismo)" que você consegue atingir, na dúvida, consulte a Lei de Amdahl (principalmente para aplicações cpu-bound).

Finalmente nos finalmentes

Se você leu até aqui, muito obrigado! Foi uma jornada bem legal escrever esse conteúdo. Vocês notarão que faltam muitos aspectos a serem cobertos na narrativa, mas a proposta aqui é ser um texto mais 'guiado' para uma parte prática de entendimento.

A comunicação é (muito provavelmente) a parte mais importante de uma jornada feita com sistemas distribuídos em uma arquitetura síncrona, pois é ela que vai determinar a quantidade de tempo que uma transação vai levar para ser concluída (caso o produto seja bem construído, é claro).

Há um grande consenso comunitário na área de tecnologia de que as transações que usam arquitetura síncrona estão para 'acabar', há modelos mais eficientes (near-realtime, por ex.) que cobrem todas as questões onde o modelo síncrono falha e, ainda há outros benefícios diretos e indiretos para o desenvolvimento (como o uso de técnicas reativas, programação funcional eficiente e etc), porém, ainda temos (o mundo tem, na verdade) um legado incrivelmente grande de arquiteturas síncronas, algumas bem feitas, outras nem tanto, mas que ainda são passíveis de melhorar (principalmente no quesito de comunicação) e estão disponíveis em ambientes produtivos.

No próximo artigo, pretendo entrar nos modelos processuais e falar mais sobre técnicas de resiliência nestas arquiteturas. Espero vocês lá! Obrigado!

--

--

Renato Rosa Guimarães
Devspoint

Solutions Architect, container enthusiast and sometimes a golang hobbist