Synchronous and Asynchronous microservice communication in Spring Framework

Valerii Kantor
Level Up Coding
Published in
4 min readJun 18, 2023

--

Nowadays plenty of Java applications have microservices architecture using Spring Boot. Spring Boot is a powerful tool to build applications based on Spring Framework. In a real application, the services communicate with each other under different protocols but all those protocols are divided into two types: synchronous and asynchronous. Let’s look through them.

Synchronous

Synchronous service communication is the most popular and straightforward solution to communicate between services. The most common and well know protocol is HTTP. HTTP protocol is the heart of technologies like REST, GraphQL but there are alternatives such as gRPC, Apache Thrift, Apache Avro and so on.

When a service makes a request to another service using a synchronous request the execution of the service which initiates the request stops while it gets a response from the requested service or it fails by timeout if the service that was requested does not respond.

Synchronous communication has pros and cons. The main disadvantage of synchronous communication is blocking context while a request is executing but on the other hand, the execution process is straightforward.

Spring Framework has a few ways for synchronous communiration If your services uses REST API for each other communication:

  • REST Template
  • WebClient
  • HTTP Interface Client (since Spring 6)
  • Feign Client (If you using Spring Cloud OpenFeign project)

REST Template

RestTemplate was introduced in Spring 3. The class has overload methods for different HTTP requests, such as GET, POST, PUT and DELETE. By default it uses HTTP but it is possible to configure to use HTTPS (e.g. use Apache HttpComponents and define SSLContext).

@Component
public class RestTemplateCatFactsClient {

private final RestTemplate restTemplate;

public RestTemplateCatFactsClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

public List<Fact> getFacts() {
ResponseEntity<List<Fact>> exchange = restTemplate.exchange(
"https://cat-fact.herokuapp.com/facts",
HttpMethod.GET,
null,
new ParameterizedTypeReference<>() {});

return exchange.getBody();
}
}

WebClient

WebClient was introduced in Spring 5 as part of the Reactive module as reactive, non-blocking solution that works over HTTP. WebClient works as a non-blocking and blocking client. To use WebClient in the non-blocking context you need to invoke subscribe method. If you’d like to use in a blocking context you have to call the block method.

@Component
public class WebClientCatFactsClient {

private final WebClient client;

public WebClientCatFactsClient(@Value("${api.baseurl}") String baseUrl) {
this.client = WebClient.builder().baseUrl(baseUrl).build();
}

public Flux<Fact> getFacts() {
return client.get()
.uri("/facts")
.retrieve()
.bodyToFlux(Fact.class);
}
}

HTTP Interface Client

HTTP Interface Client introduced in Spring 6. The HTTP Interface Client enables to definition a declarative way for HTTP services using Java interfaces. Under the hood, Spring generates a proxy class that implements the interface and performs exchanges.

Define an interface:

public interface CatFactsClient {

@GetExchange(value = "/facts")
List<Fact> getFacts();
}

Definde CatFactsClient bean in configuration

@Configuration
public class CatFactsClientConfig {

@Bean
public CatFactsClient catFactsClient() {
WebClient client = WebClient.builder().baseUrl("https://cat-fact.herokuapp.com").build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
return factory.createClient(CatFactsClient.class);
}
}

Now you can use CatFactsClient in you code like:

@Service
public class MyService {

@Autowired
private CatFactsClient catFactsClient;

public List<Fact> fetchCatFacts() {
return catFactsClient.getFacts();
}

}

Feign Client

Feign Client is a declarative REST client that is a part of Spring Cloud. To use the client you need to create an interface and annotate it. The Feign is pluggable which means it supports Spring MVC annotations and JAX-RS annotations.

Firstly, you need to enable Feign Client auto-configuration by adding @EnableFeignClients annotation in your application configuration.

@EnableFeignClients
public class AppConfig {

}

Then, you need to create an interface and annotate it with @FeignClient annotation.

@FeignClient(name = "cats-facts", url = "https://cat-fact.herokuapp.com")
public interface FeignCatFactsClient {

@GetMapping(value = "/facts")
List<Fact> getFacts();

}

Now, it is ready to use by injecting FeignCatFactsClient into you service.

Asynchronous

Asynchronous communication is alternative to synchronous. This communication protocol doesn’t block executing context until to get the response from another service. In other words, when a service sends an asynchronous request to another service it doesn’t need to wait for receiving the service’s reply. The most popular solutions are message brokers like RabbitMQ, Kafka, ActiveMQ, Amazon Kenesis or even Redis is possible to use as a message broker.

Messaging with RabbitMQ

Spring has anAMQP component that allows publishing and subscribes to messages as a POJO.

Publish a message

Message publishing in the Spring framework is easy. There is RabbitTemplate which provides multiple methods to send a message and encapsulate all logic inside. Basically, RabbitTemplate provides two types of methods to send a message. The first method is called send. This method sends a message as binary data using org.springframework.amqp.core.Message wrapper. The second method is convertAndSend which sends a message as a POJO. Actually convertAndSend executes send method at the end but before it converts POJO to an array of bytes using converters.

Let’s create a publisher to publish a message every second

public class Publisher {
private RabbitTemplate rabbitTemplate;

public Publisher(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}

public void publish(String value) {
rabbitTemplate.convertAndSend(AppConfig.EXCHANGE, "foo.bar", value);
}

@Scheduled(fixedRate = 1000)
public void sayHello() {
publish("Hello");
}
}

Receiving a message

Spring AMQP provides SimpleMessageListenerContainer to receive messages from RabbitMQ. The containers accept MessageListenerAdapter which is an adapter between your receiver implementation and SimpleMessageListenerContainer.

Create a class to consume an incoming message

@Log4j2
@Component
public class Receiver {

public void receive(String message) {
log.info("Received: {}", message);
}
}

Define a SimpleMessageLinsterContainer and MessageListenerAdaper as beans in your configuration:

@Bean
public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(QUEUE_NAME);
container.setMessageListener(listenerAdapter);
return container;
}

@Bean
public MessageListenerAdapter listenerAdapter(Receiver receiver) {
return new MessageListenerAdapter(receiver, "receive");
}

MessageListernerAdapter takes two arguments in the constructor. The first is a consumer that handles a message and the second is the name of the method that handles a message.

This example is related to RabbitMQ but if you are not using RabbitMQ the process in most cases the similar except for some vendor-specific configuration.

Check out github example: https://github.com/golgotha/spring-communication-clients

--

--