DEV Community

Cover image for Micro Serviços: 3 métodos de comunicação entre serviços
Eduardo Rabelo
Eduardo Rabelo

Posted on

Micro Serviços: 3 métodos de comunicação entre serviços

Créditos da Imagem

No mundo da arquitetura de micro serviço, criamos um aplicativo por meio de uma coleção de serviços. Cada serviço na coleção tende a atender aos seguintes critérios:

  • Fracamente acoplado
  • Manutenção e testável
  • Pode ser implantado independentemente

Cada serviço em uma arquitetura de micro serviço resolve um problema de negócios no aplicativo ou, pelo menos, suporta um. Uma única equipe é responsável por um ou mais serviços no aplicativo.

As arquiteturas de micro serviço podem desbloquear vários benefícios diferentes.

  • Geralmente são mais fáceis de construir e manter
  • Os serviços são organizados em torno de problemas de negócios
  • Eles aumentam a produtividade e a velocidade
  • Eles incentivam equipes autônomas e independentes

Esses benefícios são um dos principais motivos pelos quais os micro serviços estão aumentando em popularidade. Mas existem buracos que podem inviabilizar todos esses benefícios. Caindo neles, você obterá uma arquitetura que equivale a nada mais do que dívida técnica distribuída.

A comunicação entre micro serviços é um desses buracos que podem causar estragos se não forem considerados com antecedência.

O objetivo dessa arquitetura é criar serviços pouco acoplados, e a comunicação desempenha um papel fundamental nesse sentido. Neste artigo, vamos nos concentrar em três maneiras pelas quais os serviços podem se comunicar em uma arquitetura de micro serviço. Cada um, como veremos, traz seus próprios benefícios e vantagens.

Comunicação por HTTP

O líder absoluto ao escolher como os serviços se comunicarão entre si tende a ser HTTP. De fato, poderíamos argumentar que todos os canais de comunicação derivam desse. Mas, deixando isso de lado, as chamadas HTTP entre serviços são uma opção viável para a comunicação serviço a serviço.

Pode ser algo assim se tivermos dois serviços em nossa arquitetura. ServiceA pode processar uma solicitação e ligar ServiceB para obter outra informação.

function process(name: string): Promise<boolean> {
    /** lógica do ServiceA
        ....
        ....
    */
    /**
     * chama ServiceB para executar outra lógica
    */
    return fetch('https://service-b.com/api/endpoint')
        .then((response) => {
            if (!response.ok) {
                throw new Error(response.statusText)
            } else {
                return response.json().then(({saved}) => {
                    return saved
                })
            }
        })
}

O código é auto-explicativo e se encaixa na arquitetura de micro serviço. ServiceA possui uma parte da lógica de negócios. Ele executa seu código e depois chama ServiceB para executar outra parte da lógica de negócios. Nesse código, o primeiro serviço aguarda a conclusão do segundo serviço antes de retornar.

O que temos aqui são chamadas HTTP síncronas entre os dois serviços. Esse é um padrão de comunicação viável, mas cria um acoplamento entre os dois serviços que provavelmente não precisamos.

Outra opção no espectro HTTP é o HTTP assíncrono entre os dois serviços. Aqui está o que isso pode parecer:

function asyncProcess(name: string): Promise<string> {
    /** lógica do ServiceA
        ....
        ....
    */
    /**
     * chama ServiceB para executar outra lógica
    */
    return fetch('https://service-b.com/api/endpoint')
        .then((response) => {
            if (!response.ok) {
                throw new Error(response.statusText)
            } else {
                return response.json().then(({statusUrl}) => {
                    return statusUrl
                })
            }
        })
}

A mudança é sutil. Agora, ao invés do ServiceB retornar uma propriedade saved, ele está retornando a statusUrl. Isso significa que este serviço agora está recebendo a solicitação do primeiro serviço e retornando imediatamente uma URL. Este URL pode ser usado para verificar o andamento da solicitação.

Transformamos a comunicação entre os dois serviços de síncrona para assíncrona. Agora, o primeiro serviço não fica mais parado aguardando a conclusão do segundo serviço antes de retornar o trabalho.

Com essa abordagem, mantemos os serviços isolados um do outro e o acoplamento está solto.
A desvantagem é que ele cria solicitações HTTP extras no segundo serviço; agora ele será pesquisado de fora até que a solicitação seja concluída. Isso introduz complexidade também no cliente, pois agora ele deve verificar o andamento da solicitação.

Porém, a comunicação assíncrona permite que os serviços permaneçam fracamente acoplados um ao outro.

Comunicação por Mensagens

Outro padrão de comunicação que podemos aproveitar em uma arquitetura de micro serviço é a comunicação baseada em mensagens.

Diferentemente da comunicação HTTP, os serviços envolvidos não se comunicam diretamente. Ao invés disso, os serviços enviam mensagens para um agente intermediário de mensagens que outros serviços assinam. Isso elimina muita complexidade associada à comunicação HTTP.

Isso não requer que serviços saibam como conversar entre si; elimina a necessidade de serviços chamarem um ao outro diretamente. Ao invés disso, todos os serviços conhecem um agente intermediário de mensagens e enviam mensagens para esse intermediário. Outros serviços podem optar por assinar as mensagens que eles se preocupam desse agente.

Se nosso aplicativo estiver no Amazon Web Services, podemos usar o Serviço de Notificação Simples (SNS) como nosso agente intermediário de mensagens. Agora ServiceA pode enviar mensagens para um tópico do SNS que ServiceB escuta.

function asyncProcessMessage(name: string): Promise<string> {
    /** lógica do ServiceA
        ....
        ....
    */
    /**
     * envia mensagem para o SNS que ServiceB está assinado
    */
    let snsClient = new AWS.SNS()
    let params = {
        Message: JSON.stringify({
            'data': 'our message data'
        }),
        TopicArn: 'our-sns-topic-message-broker'
    }

    return snsClient.publish(params)
        .then((response) => {
            return response.MessageId
        })
}

ServiceB escuta as mensagens no tópico do SNS. Quando ele recebe alguma que ele está interessado, sua lógica de negócios é executada.

Isso introduz suas próprias complexidades. Observe que ServiceA não recebe mais um URL de status para verificar o progresso. Isso ocorre porque sabemos apenas que a mensagem foi enviada, não que ServiceB a recebeu.

Isso poderia ser resolvido de várias maneiras diferentes. Uma maneira é retornar o MessageI do chamador. Ele pode ser usado para consultar ServiceB, o que armazenará MessageId das mensagens recebidas.

Observe que ainda há algum acoplamento entre os dois serviços usando esse padrão. Por exemplo, ServiceB e ServiceA devem concordar como que é a estrutura da mensagem e o que ela contém.

Comunicação Orientada a Eventos

O padrão final de comunicação que iremos ver neste artigo, é o padrão orientado a eventos. Essa é outra abordagem assíncrona e parece remover completamente o acoplamento entre serviços.

Ao contrário do padrão de mensagens em que os serviços precisam conhecer uma estrutura de mensagens comum, uma abordagem orientada a eventos não precisa disso. A comunicação entre serviços ocorre através de eventos que serviços individuais produzem.

Um agente intermediário de mensagens ainda é necessário aqui, pois serviços individuais gravam seus eventos nele. Mas, diferentemente da abordagem da mensagem, os serviços consumidores não precisam conhecer os detalhes do evento; eles reagem à ocorrência do evento, não à mensagem que o evento pode ou não entregar.

Em termos formais, isso geralmente é chamado de "comunicação orientada apenas a eventos". Nosso código é como nossa abordagem de mensagens, mas o evento que enviamos para o SNS é genérico.

function asyncProcessEvent(name: string): Promise<string> {
    /** lógica do ServiceA
        ....
        ....
    */
    /**
     * envia mensagem para o SNS que ServiceB está assinado
    */
    let snsClient = new AWS.SNS()
    let params = {
        Message: JSON.stringify({
            'event': 'service-a-event'
        }),
        TopicArn: 'our-sns-topic-message-broker'
    }

    return snsClient.publish(params)
        .then((response) => {
            return response.MessageId
        })
}

Observe aqui que nossa mensagem de tópico do SNS tem uma propriedade event simples. Todo serviço concorda em enviar eventos ao agente intermediário neste formato, o que mantém a comunicação fracamente acoplada. Os serviços podem ouvir aos eventos de que se importam e sabem qual lógica executar em resposta a eles.

Esse padrão mantém os serviços pouco acoplados, pois nenhum dado/conteúdo é incluído no evento. Cada serviço nessa abordagem reage à ocorrência de um evento para executar sua lógica de negócios. Aqui, estamos enviando eventos por meio de um tópico do SNS. Outros eventos podem ser usados, como uploads de arquivos ou atualizações de linhas do banco de dados.

Conclusão

Esses são todos os padrões de comunicação possíveis em uma arquitetura baseada em micro serviços? Definitivamente não. Há mais maneiras de os serviços se comunicarem em um padrão síncrono e assíncrono.

Porém, esses três destacam as vantagens e desvantagens de favorecer a síncrona vs assíncrona. Há considerações de acoplamento a serem levadas em consideração ao escolher uma sobre a outra, mas também há as considerações de desenvolvimento e depuração a serem consideradas.

Se você tiver alguma dúvida sobre este post do blog, AWS, serverless ou codificação em geral, sinta-se à vontade para me enviar um ping via twitter @kylegalbraith. Verifique também meu boletim informativo semanal Aprenda Fazendo ou meu curso Aprenda AWS usando AWS para aprender ainda mais sobre nuvem, codificação e DevOps.


Créditos

Top comments (0)