Introdução ao Spring Cloud Stream
1. Visão geral
Spring Cloud Stream é uma estrutura construída em cima do Spring Boot e Spring Integration quehelps in creating event-driven or message-driven microservices.
Neste artigo, apresentaremos conceitos e construções do Spring Cloud Stream com alguns exemplos simples.
2. Dependências do Maven
Para começar, precisaremos adicionar a dependência MavenSpring Cloud Starter Stream with the broker RabbitMQ como messaging-middleware ao nossopom.xml:
org.springframework.cloud
spring-cloud-starter-stream-rabbit
1.3.0.RELEASE
E adicionaremosmodule dependency from Maven Central para habilitar o suporte JUnit também:
org.springframework.cloud
spring-cloud-stream-test-support
1.3.0.RELEASE
test
3. Conceitos principais
A arquitetura de microsserviços segue o princípio "https://martinfowler.com/articles/microservices.html#SmartEndpointsAndDumbPipes[nós de extremidade inteligentes e tubos flexíveis]". A comunicação entre os pontos de extremidade é conduzida por partes de middleware de mensagens como RabbitMQ ou Apache Kafka. Services communicate by publishing domain events via these endpoints or channels.
Vamos examinar os conceitos que compõem a estrutura do Spring Cloud Stream, junto com os paradigmas essenciais dos quais devemos estar cientes para construir serviços orientados por mensagens.
3.1. Construções
Vejamos um serviço simples no Spring Cloud Stream que escuta a ligaçãoinput e envia uma resposta à ligaçãooutput:
@SpringBootApplication
@EnableBinding(Processor.class)
public class MyLoggerServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MyLoggerServiceApplication.class, args);
}
@StreamListener(Processor.INPUT)
@SendTo(Processor.OUTPUT)
public LogMessage enrichLogMessage(LogMessage log) {
return new LogMessage(String.format("[1]: %s", log.getMessage()));
}
}
A anotação@EnableBinding configura o aplicativo para vincular os canaisINPUTeOUTPUT definidos na interfaceProcessor. Both channels are bindings that can be configured to use a concrete messaging-middleware or binder.
Vamos dar uma olhada na definição de todos esses conceitos:
-
Bindings - uma coleção de interfaces que identificam os canais de entrada e saída declarativamente
-
Binder - implementação de middleware de mensagens, como Kafka ou RabbitMQ
-
Channel - representa o canal de comunicação entre o middleware de mensagens e o aplicativo
-
StreamListeners - métodos de tratamento de mensagens em beans que serão automaticamente chamados em uma mensagem do canal após oMessageConverter fazer a serialização / desserialização entre eventos específicos de middleware e tipos de objeto de domínio / POJOs
-
MessageSchemas - usado para serialização e desserialização de mensagens, esses esquemas podem ser lidos estaticamente de um local ou carregados dinamicamente, suportando a evolução dos tipos de objeto de domínio
3.2. Padrões de Comunicação
Os editoresMessages designated to destinations are delivered by the Publish-Subscribe messaging pattern. categorizam as mensagens em tópicos, cada um identificado por um nome. Os assinantes manifestam interesse em um ou mais tópicos. O middleware filtra as mensagens, entregando os tópicos interessantes aos assinantes.
Agora, os assinantes podem ser agrupados. Umconsumer group é um conjunto de assinantes ou consumidores, identificados por umgroup id, dentro do qual as mensagens de um tópico ou partição do tópico são entregues de maneira balanceada.
4. Modelo de Programação
Esta seção descreve os conceitos básicos da criação de aplicativos Spring Cloud Stream.
4.1. Teste funcional
O suporte de teste é uma implementação de fichário que permite interagir com os canais e inspecionar mensagens.
Vamos enviar uma mensagem para o serviçoenrichLogMessage acima e verificar se a resposta contém o texto“[1]: “ no início da mensagem:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyLoggerServiceApplication.class)
@DirtiesContext
public class MyLoggerApplicationTests {
@Autowired
private Processor pipe;
@Autowired
private MessageCollector messageCollector;
@Test
public void whenSendMessage_thenResponseShouldUpdateText() {
pipe.input()
.send(MessageBuilder.withPayload(new LogMessage("This is my message"))
.build());
Object payload = messageCollector.forChannel(pipe.output())
.poll()
.getPayload();
assertEquals("[1]: This is my message", payload.toString());
}
}
4.2. Canais Personalizados
No exemplo acima, usamos a interfaceProcessor fornecida pelo Spring Cloud, que possui apenas um canal de entrada e um de saída.
Se precisarmos de algo diferente, como uma entrada e dois canais de saída, podemos criar um processador personalizado:
public interface MyProcessor {
String INPUT = "myInput";
@Input
SubscribableChannel myInput();
@Output("myOutput")
MessageChannel anOutput();
@Output
MessageChannel anotherOutput();
}
O Spring fornecerá a implementação adequada dessa interface para nós. Os nomes dos canais podem ser definidos usando anotações como em@Output(“myOutput”).
Caso contrário, o Spring usará os nomes dos métodos como os nomes dos canais. Portanto, temos três canais chamadosmyInput,myOutput eanotherOutput.
Agora, vamos imaginar que queremos rotear as mensagens para uma saída se o valor for menor que 10 e para outra saída se o valor for maior ou igual a 10:
@Autowired
private MyProcessor processor;
@StreamListener(MyProcessor.INPUT)
public void routeValues(Integer val) {
if (val < 10) {
processor.anOutput().send(message(val));
} else {
processor.anotherOutput().send(message(val));
}
}
private static final Message message(T val) {
return MessageBuilder.withPayload(val).build();
}
4.3. Despacho Condicional
Usando a anotação@StreamListener, também podemosfilter the messages we expect in the consumer usando qualquer condição que definirmos comSpEL expressions.
Como exemplo, poderíamos usar o envio condicional como outra abordagem para rotear mensagens para diferentes saídas:
@Autowired
private MyProcessor processor;
@StreamListener(
target = MyProcessor.INPUT,
condition = "payload < 10")
public void routeValuesToAnOutput(Integer val) {
processor.anOutput().send(message(val));
}
@StreamListener(
target = MyProcessor.INPUT,
condition = "payload >= 10")
public void routeValuesToAnotherOutput(Integer val) {
processor.anotherOutput().send(message(val));
}
O únicolimitation of this approach is that these methods must not return a value.
5. Configuração
Vamos configurar o aplicativo que processará a mensagem do corretor RabbitMQ.
5.1. Configuração de fichário
Podemos configurar nosso aplicativo para usar a implementação de fichário padrão por meio deMETA-INF/spring.binders:
rabbit:\
org.springframework.cloud.stream.binder.rabbit.config.RabbitMessageChannelBinderConfiguration
Ou podemos adicionar a biblioteca de fichário para RabbitMQ ao classpath incluindothis dependency:
org.springframework.cloud
spring-cloud-stream-binder-rabbit
1.3.0.RELEASE
Se nenhuma implementação de binder for fornecida, o Spring usará a comunicação de mensagem direta entre os canais.
5.2. Configuração RabbitMQ
Para configurar o exemplo na seção 3.1 para usar o fichário RabbitMQ, precisamos atualizar oapplication.yml localizado emsrc/main/resources:
spring:
cloud:
stream:
bindings:
input:
destination: queue.log.messages
binder: local_rabbit
output:
destination: queue.pretty.log.messages
binder: local_rabbit
binders:
local_rabbit:
type: rabbit
environment:
spring:
rabbitmq:
host:
port: 5672
username:
password:
virtual-host: /
A ligaçãoinput usará a troca chamadaqueue.log.messages, e a ligaçãooutput usará a trocaqueue.pretty.log.messages. Ambas as ligações usarão o fichário chamadolocal_rabbit.
Observe que não precisamos criar as trocas ou filas RabbitMQ com antecedência. Ao executar o aplicativo,both exchanges are automatically created.
Para testar o aplicativo, podemos usar o site de gerenciamento RabbitMQ para publicar uma mensagem. No painelPublish Message da trocaqueue.log.messages, precisamos inserir a solicitação no formato JSON.
5.3. Customizing Message Conversion
O Spring Cloud Stream nos permite aplicar a conversão de mensagens para tipos de conteúdo específicos. No exemplo acima, em vez de usar o formato JSON, queremos fornecer texto sem formatação.
Para fazer isso, vamosapply a custom transformation to LogMessage using a MessageConverter:
@SpringBootApplication
@EnableBinding(Processor.class)
public class MyLoggerServiceApplication {
//...
@Bean
public MessageConverter providesTextPlainMessageConverter() {
return new TextPlainMessageConverter();
}
//...
}
public class TextPlainMessageConverter extends AbstractMessageConverter {
public TextPlainMessageConverter() {
super(new MimeType("text", "plain"));
}
@Override
protected boolean supports(Class> clazz) {
return (LogMessage.class == clazz);
}
@Override
protected Object convertFromInternal(Message> message,
Class> targetClass, Object conversionHint) {
Object payload = message.getPayload();
String text = payload instanceof String
? (String) payload
: new String((byte[]) payload);
return new LogMessage(text);
}
}
Depois de aplicar essas alterações, voltando ao painelPublish Message, se definirmos o cabeçalho "contentTypes" para "text/plain" e a carga útil para "Hello World", deve trabalhe como antes.
5.4. Grupos de Consumidores
Ao executar várias instâncias de nosso aplicativo,every time there is a new message in an input channel, all subscribers will be notified.
Na maioria das vezes, precisamos que a mensagem seja processada apenas uma vez. O Spring Cloud Stream implementa esse comportamento por meio de grupos de consumidores.
Para habilitar esse comportamento, cada ligação do consumidor pode usar a propriedadespring.cloud.stream.bindings.<CHANNEL>.group para especificar um nome de grupo:
spring:
cloud:
stream:
bindings:
input:
destination: queue.log.messages
binder: local_rabbit
group: logMessageConsumers
...
6. Microsserviços orientados por mensagem
Nesta seção, apresentamos todos os recursos necessários para executar nossos aplicativos Spring Cloud Stream em um contexto de microsserviços.
6.1. Ampliando
Quando vários aplicativos estão em execução, é importante garantir que os dados sejam divididos corretamente entre os consumidores. Para fazer isso, o Spring Cloud Stream fornece duas propriedades:
-
spring.cloud.stream.instanceCount - número de aplicativos em execução
-
spring.cloud.stream.instanceIndex - índice da aplicação atual
Por exemplo, se implantamos duas instâncias do aplicativoMyLoggerServiceApplication acima, a propriedadespring.cloud.stream.instanceCount deve ser 2 para ambos os aplicativos e a propriedadespring.cloud.stream.instanceIndex deve ser 0 e 1, respectivamente.
Essas propriedades são definidas automaticamente se implantarmos os aplicativos Spring Cloud Stream usando Spring Data Flow conforme descrito emthis article.
6.2. Particionamento
Os eventos de domínio podem ser mensagens dePartitioned. Isso ajuda quando somosscaling up the storage and improving application performance.
O evento do domínio geralmente possui uma chave de partição para que ele termine na mesma partição com mensagens relacionadas.
Digamos que desejamos que as mensagens de registro sejam particionadas pela primeira letra da mensagem, que seria a chave de partição, e agrupadas em duas partições.
Haveria uma partição para as mensagens de log que começam comA-Me outra partição paraN-Z.. Isso pode ser configurado usando duas propriedades:
-
spring.cloud.stream.bindings.output.producer.partitionKeyExpression - a expressão para particionar as cargas úteis
-
spring.cloud.stream.bindings.output.producer.partitionCount - o número de grupos
Sometimes the expression to partition is too complex to write it in only one line. Para esses casos, podemos escrever nossa estratégia de partição customizada usando a propriedadespring.cloud.stream.bindings.output.producer.partitionKeyExtractorClass.
6.3. Indicador de Saúde
Em um contexto de microsserviços, também precisamosdetect when a service is down or starts failing. Spring Cloud Stream fornece a propriedademanagement.health.binders.enabled para habilitar os indicadores de integridade para os ligantes.
Ao executar o aplicativo, podemos consultar o estado de saúde emhttp://<host>:<port>/health.
7. Conclusão
Neste tutorial, apresentamos os principais conceitos do Spring Cloud Stream e mostramos como usá-lo através de alguns exemplos simples sobre o RabbitMQ. Mais informações sobre o Spring Cloud Stream podem ser encontradashere.
O código-fonte deste artigo pode ser encontradoover on GitHub.