Tradução e adaptação de microsserviços Java: um guia prático . Partes anteriores do guia:
Vejamos os problemas inerentes aos microsserviços em Java, começando com coisas abstratas e terminando com bibliotecas concretas.
Como tornar um microsserviço Java resiliente?
Lembre-se de que, ao criar microsserviços, você está essencialmente trocando chamadas de método JVM por chamadas HTTP síncronas ou mensagens assíncronas. Embora seja garantido que uma chamada de método seja concluída (exceto um desligamento inesperado da JVM), uma chamada de rede não é confiável por padrão. Pode funcionar, mas pode não funcionar por vários motivos: a rede está sobrecarregada, uma nova regra de firewall foi implementada e assim por diante. Para ver como isso faz diferença, vamos dar uma olhada no exemplo do BillingService.Padrões de resiliência HTTP/REST
Digamos que os clientes possam comprar e-books no site da sua empresa. Para fazer isso, você acabou de implementar um microsserviço de cobrança que pode ligar para sua loja online para gerar faturas reais em PDF. Por enquanto, faremos essa chamada de forma síncrona, via HTTP (embora faça mais sentido chamar esse serviço de forma assíncrona, já que a geração do PDF não precisa ser instantânea da perspectiva do usuário. Usaremos o mesmo exemplo no próximo seção e observe as diferenças).@Service
class BillingService {
@Autowired
private HttpClient client;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
// ...
}
}
Para resumir, aqui estão três resultados possíveis desta chamada HTTP.
- OK: a chamada foi completada, a conta foi criada com sucesso.
- ATRASO: A chamada foi completada, mas demorou muito para ser concluída.
- ERRO. A chamada falhou, você pode ter enviado uma solicitação incompatível ou o sistema pode não estar funcionando.
Padrões de resiliência de mensagens
Vamos dar uma olhada mais de perto na comunicação assíncrona. Nosso programa BillingService agora pode ser parecido com isto, supondo que estamos usando Spring e RabbitMQ para mensagens. Para criar uma conta, enviamos agora uma mensagem para nosso corretor de mensagens RabbitMQ, onde existem vários trabalhadores aguardando novas mensagens. Esses funcionários criam faturas em PDF e as enviam aos usuários apropriados.@Service
class BillingService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
// преобразует счет, например, в json и использует его How тело messages
rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
// ...
}
}
Os erros potenciais parecem um pouco diferentes agora, pois você não recebe mais respostas imediatas de OK ou ERRO, como acontecia com uma conexão HTTP síncrona. Em vez disso, podemos ter três cenários potenciais que podem dar errado, o que poderia levantar as seguintes questões:
- Minha mensagem foi entregue e usada pelo funcionário? Ou está perdido? (O usuário não recebe fatura).
- Minha mensagem foi entregue apenas uma vez? Ou entregue mais de uma vez e processado apenas uma vez? (O usuário receberá várias faturas).
- Configuração: De "Usei as chaves/nomes de roteamento corretos para a troca" até "Meu agente de mensagens está configurado e mantido corretamente ou suas filas estão cheias?" (O usuário não recebe fatura).
- Se você estiver usando implementações JMS como ActiveMQ, poderá trocar a velocidade pela garantia de confirmações de duas fases (XA).
- Se você estiver usando RabbitMQ, leia este tutorial primeiro e depois pense cuidadosamente sobre confirmações, tolerância a falhas e confiabilidade de mensagens em geral.
- Talvez alguém seja versado em configurar servidores Active ou RabbitMQ, especialmente em combinação com clustering e Docker (alguém? ;))
Qual framework seria a melhor solução para microsserviços Java?
Por um lado, você pode instalar uma opção muito popular como Spring Boot . Torna muito fácil criar arquivos .jar, vem com um servidor web integrado como Tomcat ou Jetty e pode ser executado rapidamente e em qualquer lugar. Ideal para criar aplicativos de microsserviços. Recentemente, surgiram duas estruturas especializadas de microsserviços, Kubernetes ou GraalVM , parcialmente inspiradas na programação reativa. Aqui estão mais alguns concorrentes interessantes: Quarkus , Micronaut , Vert.x , Helidon . No final, você terá que escolher por si mesmo, mas podemos dar algumas recomendações que podem não ser completamente padrão: Com exceção do Spring Boot, todas as estruturas de microsserviços são geralmente comercializadas como incrivelmente rápidas, com inicialização quase instantânea , baixo consumo de memória e capacidade de expansão até o infinito. Os materiais de marketing normalmente apresentam gráficos impressionantes que mostram a plataforma ao lado do gigante Spring Boot ou entre si. Isso, em teoria, poupa os nervos dos desenvolvedores que apoiam projetos legados, que às vezes levam vários minutos para carregar. Ou desenvolvedores que trabalham na nuvem e desejam iniciar/parar quantos microcontêineres forem necessários atualmente em 50 ms. O problema, contudo, é que estes tempos (artificiais) de arranque bare metal e tempos de redistribuição dificilmente contribuem para o sucesso global do projecto. Pelo menos eles influenciam muito menos do que uma forte infraestrutura de framework, forte documentação, comunidade e fortes habilidades de desenvolvedor. Portanto, é melhor ver desta forma: Se até agora:- Você deixa seus ORMs correrem soltos, gerando centenas de consultas para fluxos de trabalho simples.
- Você precisa de gigabytes infinitos para executar seu monólito moderadamente complexo.
- Você tem tanto código e a complexidade é tão alta (não estamos falando de inicializações potencialmente lentas como o Hibernate agora) que seu aplicativo leva vários minutos para carregar.
Quais bibliotecas são melhores para chamadas Java REST síncronas?
No lado técnico de baixo nível, você provavelmente acabará com uma das seguintes bibliotecas de cliente HTTP: HttpClient nativo do Java (desde Java 11), HttpClient do Apache ou OkHttp . Observe que digo "provavelmente" aqui porque existem outras opções, que vão desde os bons e antigos clientes JAX-RS até os modernos clientes WebSocket . De qualquer forma, a tendência é gerar um cliente HTTP, deixando de mexer você mesmo com chamadas HTTP. Para fazer isso, você precisa dar uma olhada no projeto OpenFeign e sua documentação como ponto de partida para leituras adicionais.Quais são os melhores corretores para mensagens Java assíncronas?
Muito provavelmente você encontrará os populares ActiveMQ (Classic ou Artemis) , RabbitMQ ou Kafka .- ActiveMQ e RabbitMQ são corretores de mensagens tradicionais e completos. Eles envolvem a interação de um “corretor inteligente” e “usuários estúpidos”.
- Historicamente, o ActiveMQ teve o benefício de fácil inlining (para teste), que pode ser mitigado com configurações RabbitMQ/Docker/TestContainer.
- Kafka não pode ser chamado de corretor tradicional “inteligente”. Em vez disso, é um armazenamento de mensagens (arquivo de log) relativamente "burro" que requer processamento por consumidores inteligentes.
Quais bibliotecas posso usar para testar microsserviços?
Depende da sua pilha. Se você tiver um ecossistema Spring implantado, seria aconselhável usar as ferramentas específicas do framework . Se JavaEE for algo como Arquillian . Talvez valha a pena dar uma olhada no Docker e na excelente biblioteca Testcontainers , que ajuda, em particular, a configurar de maneira fácil e rápida um banco de dados Oracle para desenvolvimento local ou testes de integração. Para testes simulados de servidores HTTP inteiros, confira Wiremock . Para testar mensagens assíncronas, tente implementar ActiveMQ ou RabbitMQ e depois escrever testes usando Awaitility DSL . Além disso, todas as suas ferramentas usuais são usadas - Junit , TestNG para AssertJ e Mockito . Observe que esta não é uma lista completa. Se você não encontrar sua ferramenta favorita aqui, poste-a na seção de comentários.Como habilitar o log para todos os microsserviços Java?
O registro no caso de microsserviços é um tópico interessante e bastante complexo. Em vez de ter um arquivo de log que pode ser manipulado com comandos less ou grep, agora você tem n arquivos de log e deseja que eles não fiquem muito dispersos. As características do ecossistema madeireiro estão bem descritas neste artigo (em inglês). Certifique-se de lê-lo e preste atenção à seção Registro centralizado da perspectiva de microsserviços . Na prática, você encontrará diferentes abordagens: O administrador do sistema escreve certos scripts que coletam e mesclam arquivos de log de diferentes servidores em um único arquivo de log e os colocam em servidores FTP para download. Executando combinações cat/grep/unig/sort em sessões SSH paralelas. Isso é exatamente o que o Amazon AWS faz, e você pode informar seu gerente. Use uma ferramenta como Graylog ou ELK Stack (Elasticsearch, Logstash, Kibana)Como meus microsserviços se encontram?
Até agora, assumimos que nossos microsserviços se conhecem e conhecem o IPS correspondente. Vamos falar sobre configuração estática. Portanto, nosso monólito bancário [ip = 192.168.200.1] sabe que precisa se comunicar com o servidor de risco [ip = 192.168.200.2], que está codificado no arquivo de propriedades. No entanto, você pode tornar as coisas mais dinâmicas:- Use um servidor de configuração baseado em nuvem do qual todos os microsserviços extraem suas configurações em vez de implantar arquivos application.properties em seus microsserviços.
- Como suas instâncias de serviço podem alterar dinamicamente sua localização, vale a pena examinar serviços que saibam onde seus serviços residem, quais são seus IPs e como roteá-los.
- Agora que tudo é dinâmico, surgem novos problemas, como a eleição automática de um líder: quem é o mestre que trabalha em determinadas tarefas, para não processá-las duas vezes, por exemplo? Quem substitui um líder quando ele falha? Com que base ocorre a substituição?
Como organizar autorização e autenticação usando microsserviços Java?
Este tópico também merece uma história separada. Novamente, as opções variam desde autenticação HTTPS básica codificada com estruturas de segurança personalizadas até a execução de uma instalação Oauth2 com seu próprio servidor de autorização.Como posso ter certeza de que todos os meus ambientes têm a mesma aparência?
O que é verdade para implantações sem microsserviço também é verdade para implantações com um. Experimente uma combinação de Docker/Testcontainers e Scripting/Ansible.Sem dúvida: brevemente sobre YAML
Vamos nos afastar das bibliotecas e dos problemas relacionados por um momento e dar uma olhada rápida no Yaml. Este formato de arquivo é usado de fato como um formato para “escrever configuração como código”. Também é usado por ferramentas simples como Ansible e gigantes como Kubernetes. Para experimentar a dor do recuo YAML, tente escrever um arquivo Ansible simples e veja quanto você precisa editar o arquivo antes que ele funcione conforme o esperado. E isso apesar do formato ser suportado por todos os principais IDEs! Depois disso, volte para terminar de ler este guia.Yaml:
- is:
- so
- great
E quanto às transações distribuídas? Teste de performance? Outros tópicos?
Talvez algum dia, em futuras edições do manual. Por enquanto, isso é tudo. Fique conosco!Problemas conceituais com microsserviços
Além dos problemas específicos de microsserviços em Java, existem outros problemas, digamos, que aparecem em qualquer projeto de microsserviços. Eles se relacionam principalmente com organização, equipe e gestão.Incompatibilidade de front-end e back-end
A incompatibilidade de front-end e back-end é um problema muito comum em muitos projetos de microsserviços. O que isso significa? Só que nos bons e velhos monólitos, os desenvolvedores de interfaces web tinham uma fonte específica para obtenção de dados. Em projetos de microsserviços, os desenvolvedores front-end repentinamente têm n fontes das quais obter dados. Imagine que você está criando algum tipo de projeto de microsserviços IoT (Internet of Things) em Java. Digamos que gere máquinas geodésicas e fornos industriais em toda a Europa. E esses fornos enviam atualizações regulares com suas temperaturas e similares. Mais cedo ou mais tarde, você poderá querer encontrar fornos na interface do administrador, talvez usando microsserviços de "pesquisa de forno". Dependendo de quão estritamente suas contrapartes de back-end aplicam leis de design orientado a domínio ou microsserviços, o microsserviço “encontrar forno” pode retornar apenas IDs de forno e não outros dados, como tipo, modelo ou localização. Para fazer isso, os desenvolvedores front-end precisarão fazer uma ou n chamadas adicionais (dependendo da implementação de paginação) no microsserviço “obter dados do forno” com os IDs que receberam do primeiro microsserviço. E embora este seja apenas um exemplo simples, ainda que retirado de um projeto real (!), ainda demonstra o seguinte problema: os supermercados tornaram-se extremamente populares. Isso porque com eles você não precisa ir a 10 lugares diferentes para comprar verduras, limonada, pizza congelada e papel higiênico. Em vez disso, você vai para um lugar. É mais fácil e rápido. O mesmo vale para desenvolvedores front-end e de microsserviços.Expectativas de gestão
A administração tem a impressão equivocada de que agora precisa contratar um número infinito de desenvolvedores para um projeto (abrangente), uma vez que os desenvolvedores agora podem trabalhar de forma totalmente independente uns dos outros, cada um em seu próprio microsserviço. Apenas um pouco de trabalho de integração é necessário no final (pouco antes do lançamento). Na verdade, esta abordagem é extremamente problemática. Nos parágrafos seguintes tentaremos explicar o porquê.“Pedaços menores” não são iguais a “pedaços melhores”
Seria um grande erro presumir que um código dividido em 20 partes será necessariamente de qualidade superior a uma peça inteira. Mesmo se considerarmos a qualidade de um ponto de vista puramente técnico, nossos serviços individuais ainda podem estar executando 400 consultas do Hibernate para selecionar um usuário do banco de dados, atravessando camadas de código não suportado. Mais uma vez, voltamos à citação de Simon Brown: se você não conseguir construir monólitos adequadamente, será difícil construir microsserviços adequados. Muitas vezes é extremamente tarde para falar sobre tolerância a falhas em projetos de microsserviços. Tanto que às vezes é assustador ver como funcionam os microsserviços em projetos reais. A razão para isso é que os desenvolvedores Java nem sempre estão prontos para estudar tolerância a falhas, redes e outros tópicos relacionados no nível adequado. As “peças” em si são menores, mas as “partes técnicas” são maiores. Imagine que sua equipe de microsserviços seja solicitada a escrever um microsserviço técnico para fazer login em um sistema de banco de dados, algo como isto:@Controller
class LoginController {
// ...
@PostMapping("/login")
public boolean login(String username, String password) {
User user = userDao.findByUserName(username);
if (user == null) {
// обработка варианта с несуществующим пользователем
return false;
}
if (!user.getPassword().equals(hashed(password))) {
// обработка неверного пароля
return false;
}
// 'Ю-ху, залогинorсь!';
// установите cookies, делайте, что угодно
return true;
}
}
Agora sua equipe pode decidir (e talvez até convencer os empresários) que tudo isso é muito simples e chato, em vez de escrever um serviço de login, é melhor escrever um microsserviço UserStateChanged realmente útil, sem quaisquer implicações comerciais reais e tangíveis. E como algumas pessoas atualmente tratam Java como um dinossauro, vamos escrever nosso microsserviço UserStateChanged no moderno Erlang. E vamos tentar usar árvores rubro-negras em algum lugar, porque Steve Yegge escreveu que é preciso conhecê-las de dentro para fora para se candidatar ao Google. Do ponto de vista de integração, manutenção e design geral, isso é tão ruim quanto escrever camadas de código espaguete dentro de um único monólito. Um exemplo artificial e comum? Isto é verdade. No entanto, isso pode acontecer na realidade.
Menos peças – menos compreensão
Aí surge naturalmente a questão de entender o sistema como um todo, seus processos e fluxos de trabalho, mas ao mesmo tempo você, como desenvolvedor, é responsável apenas por trabalhar no seu microsserviço isolado [95: login-101: updateUserProfile]. Harmoniza-se com o parágrafo anterior, mas dependendo da sua organização, nível de confiança e comunicação, isso pode levar a muita confusão, encolher de ombros e culpar se houver uma falha acidental na cadeia de microsserviços. E não há ninguém que assuma total responsabilidade pelo que aconteceu. E não é uma questão de desonestidade. Na verdade, é muito difícil conectar diferentes partes e compreender o seu lugar no quadro geral do projeto.Comunicações e serviços
O nível de comunicação e serviço varia muito dependendo do tamanho da empresa. Contudo, a relação geral é óbvia: quanto mais, mais problemático.- Quem executa o microsserviço nº 47?
- Eles acabaram de implantar uma versão nova e incompatível de um microsserviço? Onde isso foi documentado?
- Com quem preciso falar para solicitar um novo recurso?
- Quem dará suporte a esse microsserviço em Erlang, depois que o único que conhecia esse idioma saiu da empresa?
- Todas as nossas equipes de microsserviços trabalham não apenas em diferentes linguagens de programação, mas também em diferentes fusos horários! Como coordenamos tudo isso corretamente?
GO TO FULL VERSION