Olá pessoal, vou iniciar uma série de posts mais técnicos desta vez. Como mencionei no post anterior, estivemos analisando o uso do Microsoft SQL Server Service Broker como uma alternativa para implementação de Arquitetura de Microservicess. Aproveitando uma necessidade de um de nossos clientes, conseguimos definir e implementar um barramento de microservices com sucesso, que já está em execução em ambiente de produção! Nesta série de posts vou detalhar a arquitetura utilizada, tentando mencionar os desafios que enfrentamos e as soluções adotadas. Espero que sirva de apoio para outros desenvolvedores se aventurando por esta linha.
Inicialmente um pouco de contexto. Em um de nossos clientes, da área financeira, possuímos um grande sistema .NET, em operação há mais de 20 anos. Apesar dele ser relativamente bem estruturado (separação de camadas, mapeamento objeto-relacional com nHibernate, interfaces MVC etc.), ele acumulou, por todo este tempo de evolução, uma infinidade de regras de negócio para cada uma das áreas atendidas. Por ser um sistema central da empresa, praticamente todas as operações passam pelo mesmo e ele é integrado à vários outros, por diversas formas de comunicação. Assim, temos cada vez mais dificuldade para evoluí-lo e mantê-lo, já que sua arquitetura monolítica faz com que testes de integração e homologação sejam extremamente complexos e demorados. É com grande dificuldade que conseguimos manter um ritmo saudável de trocas de uma metodologia ágil, pois o tempo de homologação de usuários quase que inviabiliza nossas janelas semanais.
Outro grande problema de nosso sistema monolítico é a escalabilidade. Com o aumento de demanda, não temos opção a não ser aumentar o número e capacidade de máquinas, pois o sistema tem que ser replicado por inteiro. gerando aumento de custos e complexidade de infra-estrutura.
Este seria um dos típicos problemas resolvidos por um sistema que utiliza uma arquitetura de microservices. Cada área de um sistema de grande porte seria implementada por um serviço independente, que usa um barramento de mensagens para comunicação entre si (ver figura a seguir). Desta forma, a manutenção ou evolução de uma área não afetaria outras, tendo seu próprio ciclo de desenvolvimento independente.
Porém, é raro termos o luxo de projetarmos um sistema do zero já incorporando este tipo de arquitetura. Então, nosso primeiro desafio foi achar uma maneira de fazer com que um sistema monolítico como o nosso pudesse utilizar microservices sem que ele tivesse que ser refeito do zero. A solução que encontramos está mostrada na figura a seguir. Inicialmente, isolamos uma área que possa ser implementada como um serviço isolado (A). Depois, quebramos este bloco em duas porções: uma pequena interface de comando e recepção de resultados (“Thin” API); e o módulo que implementa os executores de ações e regras de negócio específicas (B). Entre estes blocos, incluímos o suporte ao envio e recepção de mensagens, para que a API se comunique com o executor através do barramento de microservices (C).
Desta forma, conseguimos alguns benefícios da arquitetura de microservices: 1) o executor é completamente isolado do sistema principal, podendo ser evoluído de maneira independente; 2) a execução de atividades passa a ser feita de forma assíncrona, liberando recursos para o sistema e permitindo a escalabilidade horizontal dos executores. Se conseguirmos isolar cada área desta forma poderemos, a longo prazo, quebrar nosso sistema monolítico em vários microservices, chegando bem próximo de um sistema que fosse projetado do zero para esta arquitetura.
Pode-se argumentar que outras arquiteturas poderiam gerar benefícios similares. Uma alternativa, por exemplo, seria fazer uso de componentes intercambiáveis. Embora isto garantisse o isolamento da área de negócio, não resolveria por completo a questão da substituição em produção. Com microservices, podemos parar um dos executores de serviço, ou mesmo todos eles e todo o sistema continua funcionando normalmente (as mensagens simplesmente se acumulam, sendo processadas posteriormente quando o serviço for restabelecido). No caso de componentes, seria muito mais complexo de resolver este cenário, pois há um acoplamento direto entre o mesmo e o sistema; se o componente parar o sistema também para. Com componentes também não teríamos solução trivial para o problema de escalabilidade.
Outra alternativa seria o uso de webservices tradicionais, baseados em SOAP ou outro protocolo. Webservices simplificam o problema da substituição em produção, pois podemos ter um cluster de servidores, com vários em paralelo, e ir substituindo aos poucos, com o uso de versionamento. Eles resolvem também o problema de escalabilidade, pois são de menor porte e podemos aumentar o cluster conforme necessário. O grande problema de webservices é a latência que existe para as chamadas de regras de negócio, já que eles normalmente se encontram em outros servidores. Para ações muito frequentes, em operações comuns do sistema, poderíamos ter uma demora excessiva para a execução, afetando o usuário. E operações assíncronas em sistemas web chamando webservices são extremamente difíceis de serem implementadas.
Analisando estes e outros cenários, a arquitetura de microservices parece ser a mais adequada. Da mesma forma que um webservice, ela separa a camada de negócios, permitindo escalabilidade e isolamento. E é tão rápido quanto um componente, já que a sua “Thin” API reside dentro da aplicação. É claro que o lado negativo é que todas as operações, que antes eram síncronas, passam a ser assíncronas. Este é o maior limitador, já que é necessário reimaginar o comportamento do sistema considerando que as ações não são mais imediatas, mofidifcando a usabilidade do mesmo.
Para a implementação do nosso barramento de serviços, foi considerado o uso vários produtos, open source e comerciais. O RabbitMQ foi uma das alternativas que foram melhor avaliadas. No entanto, no final, a escolha foi o Microsoft SQL Server Service Broker. As razões foram a facilidade de se montar um barramento de serviços simples e o fato dos nossos sistemas já o usarem o SQL Server como DBMS, o que simplificou bastante nossa infraestrutura.
Bom, por hoje é só. Em breve devo detalhar a parte técnica da solução, desde a implementação das filas de mensagens e processadores até o processo de ativação de executores. Até a próxima!
#1 por Ricardo Santos em 01/08/2015 - 10:15 pm
Bom post!!!
Nao conheço bem o broker do SQL Server…
Geralmente nesta arquitetura uso estrutura de filas que atendam o padrao AMQP. Neste caso conseguimos usar e abusar de filas topics para realizacao do pattern de pub/sub. sincronizacao ou atualizando eventos que sejam comuns entre os microservices (event-driven). O bom de utilizar um padrao como AMQP eh que esta arquitetura esta habilitada a cloud uma vez que muitos produtos de mensageria sao aderentes ao padrão.
Dicas para pesquisa:
MuleSoft como API Gateway e ESB + RAML tipando as Rest apis.
HateOAS para state machine.
Da uma olhada em CQRS Pattern / Actors Model tb pois pode ser util.
#2 por Alexandre Valente em 01/08/2015 - 10:17 pm
Também nunca tinha usado, mas gostei muito… No próximo vou detalhar, achei bem simples de desenvolver e operar…. (claro, tem que descobrir uns macetes, mas depois vai bem! :))
Sobre a cloud, estamos pensando, quando for o caso, em usar o Azure… Parece que o barramento de serviços dele é bem legal também…
Vou ver as suas indicações, vlw!!!
#3 por Ricardo Santos em 01/08/2015 - 10:19 pm
Waiting for… Abs!!!