The Basics of Service-to-Service Communication: A Beginner’s Guide

Konstantin Borimechkov
7 min readJun 7, 2023

--

Last week, I got the question about how should we handle a REST call to external service and use the data we get from it, as a response, to update our local database and continue with the business logic.

Although this task may initially appear simple and uncomplicated, based on my experience at Tide, I have come to realize that it is far from being straightforward. This is primarily because there are numerous potential complexities and challenges that can arise during the execution of what may seem like a seemingly simple implementation.

When I faced this question/task, it made me question the whole concept of service-to-service communication and with that I dove deeper into the topic here ⏬

Working with microservices in a distributed environment (like we do at Tide Engineering) , offers a lot of advantages like:

  • Scalability
  • Independent development and deployment of services
  • Tech diversity
  • Fault isolation and resilience
  • Maintainability and evolvability

but with that, it also brings a lot of complexities, one of the which is service-to-service communication.

What is S2S communication?

Service-to-service communication refers to the interaction and exchange of data between different services in a distributed system.

In a microservices architecture, each service is responsible for a specific business capability and can operate independently. However, to fulfil complex requirements, services need to communicate with each other to exchange data, trigger actions, or coordinate workflows.

Very ugly but at the same time precise image of S2S communication in a typical backend microservices architecture 👆

When & Why to use it?

Service-to-service communication is done when:

  1. multiple services need to work together to accomplish a specific business goal or workflow
  2. services need/require data from other services to perform it’s tasks. For example, an e-commerce service may need to communicate with payment, inventory and/or shipping service to process an order.
  3. services play the role of consumers and need to react upon hearing of an event (in asynchronous communication)
  4. services communication needs to be load-balanced
  5. services are designed to be loosely coupled, allowing independent scaling and evolution.

.. more on the why should we use it:

  1. separation of concerns and modularity, because each microservice should be designed to be self — contained and focus on specific business capabilities. S2S communication enables the services to exchange data, helping the system to deliver end-to-end functionality while maintaining a clear separation of concerns.
  2. polyglot programming. Each service can be implemented using the most appropriate technology for its specific requirements.
  3. .. and more 😁

Types of S2S communication

Microservices architectures often involve a combination of multiple communication patterns based on the specific requirements of the system.

For example, synchronous APIs for simple interactions, messaging for event-driven scenarios, and RPC for low-latency communication.

That being said, here are the core ways of S2S communication:

Synchronous HTTP/REST APIs

  • a simple, widely adopted and suitable for many inter-srvice communication scenarios approach, allowing for communication over HTTP using the REST architectural style
  • to achieve it, the service that produces the data should expose a well-defined endpoints (URLs) that other service can call over the network and get data from!

Asynchronous Messaging:

  • a more complex way of allowing services to asynchronously communicate with each other via the use of events, promoting loose coupling between services and allowing for the event-driven architectural approach
  • I dove deeper into asynchronous communication here!

RPC (Remote Procedure Call):

  • services invoke methods or procedures exposed by other services over the network, making it appear as if they were local method calls.
  • RPC frameworks handle the underlying network communication and serialization/deserialization of data. Such frameworks are gRPC or Spring Cloud Netflix’s Feign.

Service Mesh:

  • A service mesh is dedicated infrastructure layer that handles s2s communication within the microservices architecture. It typically consists of a sidecar proxy deployed alongside each service, enabling advanced features like service discovery, load balancing, security, observability, and traffic management. A simple image, showing this:

Event Sourcing/CQRS (Command Query Responsibility Segregation):

  • This pattern involves capturing all changes as events and using them as the source of truth for state updates in different services. (very useful when you want to see the history of a given action)
  • here, services subscribe to relevant events and update their own data stores accordingly, which allows for better loose coupling, scalability, and auditability of data changes.
  • here is a very good video on the topic

Database Replication:

  • services maintains their own database but can read data from replicated copies of other services’ databases.
  • This pattern provides data isolation but can introduce complexity and consistency challenges.

When is synchronous communication preferred over the use of events?

Although the event-driven approach offers scores of benefits like loose coupling, better scalability and resilience to failures, synchronous REST call to another service may be the preferred choice for an engineer. Here is why:

Security & Authorization 🔐

  • calling another service via HTTPs gives a straightforward way of implementing security and authorization to a s2s call. Also, synchronous communication allows for immediate validation of access credentials and enforcement of security policies.

Real-time interactions ⏳

  • RESTful APIs allow for immediate feedback and facilitate real-time updates. For example, in a chat application, when one user sends a message, immediate synchronization with other users can be achieved through synchronous RESTful API calls.

Ease of use 😌

  • applying this way of communication is easier than configuring event-driven communication between services. Especially if you are already familiar with HTTP communication and the request/response cycle.

Request/Response Interaction 🔁

  • using synchronous S2S communication with REST requires for you to use a request/response model, which is beneficial when the calling service needs immediate feedback or requires the result of the operation to continue its own processing.

Bonus: Working with S2S communication in a transactional context

Let’s define a simplification of the whole scenario, so that you can get a basic understanding:

I’m writing a REST API, using Java & Spring Boot. I’ve defined my three layers and now the request I got was to think of a way to call another micro-service, get the response and persist some of it’s data into our database.

Before anything, I would define my @FeignClient with whom I will simplify the consumption of RESTful APIs over the HTTP protocol. Here is an example of a feign call in a Spring Boot app:

@FeignClient(name = "items-service", url = "http://amazon.ai/api")
public interface ExternalServiceClient {

@GetMapping("/items")
ExternalServiceResponse<List<Items>> getItemDetails();
}

.. then comes the most interesting question, how should I handle the request/response HTTP call within a transactional context, because our application needs strong consistency and ACID compliance? 🤔

If you aren’t familiar with the @Transactional annotation and what transactional context means, I strongly recommend you to read this article!

.. then, the service layer with all business logic and external call in one transactional context:

@Service
@Transactional
public class OrderServiceImpl implements OrderService {

private final ExternalServiceClient externalServiceClient;
private final OrderRepository orderRepository;

public OrderServiceImpl(ExternalServiceClient externalServiceClient, OrderRepository orderRepository) {
this.externalServiceClient = externalServiceClient;
this.orderRepository = orderRepository;
}

@Override
public Order addOrderItem(String itemName) {
// Call the external service using the provided client
ExternalServiceResponse response = externalServiceClient.getItemDetails(itemName);

// Create a new order item from the response
OrderItem orderItem = new OrderItem();
orderItem.setName(response.getName());
orderItem.setAmount(response.getAmount());
// Set other fields as required

// Save the order item to the repository
Order order = new Order();
order.addItem(orderItem);
orderRepository.save(order);
return order;
}
}

Firstly, we need to determine the boundaries of our transaction based on the business logic.

I would suggest you go go trough this article in case you struggle to understand the concept transaction management and how to deal with transactional boundaries!

Secondly, I will wrap the feign call with all business logic that surrounds it in the service layer, annotating the service method with the @Transactional annotation, which fully handles the JDBC connection and even provides toggles with whom we can manipulate the ways of propagation/isolation and even the rolling back of a transaction in case of failed s2s call or failure within the business logic itself.

Thirdly, as we are in a microservices architecture, we should also consider the following:

  • distributed transaction coordination (frameworks like: Spring’s Distributed Transaction Management or Atomikos, can help us coordinate transactional operations across multiple services, utilizing protocols like the Two-Phase-Commit and the SAGA Pattern)
  • idempotency (the request/response HTTP calls should be idempotent, meaning that the same request should be safely retried multiple times without unintended effects)
  • compensation and rollback mechanisms (think of the applications’ behaviour when an exception occurs and how should we handle the error — should we rollback the whole transaction, retry the failed call again or something else 💭)

💙 As an engineer at Tide Engineering, you can expect to face an array of challenges, such as:

  • working with distributed transactions
  • employing synchronous and asynchronous communication methods, -
  • implementing roll-back mechanisms
  • utilizing the SAGA pattern to build real-life applications.

📈 These experiences will contribute to your growth and expertise in building robust and scalable systems.

With all that said, if you are interested in learning more about a career at Tide, check out the careers page! 🚀

✍️ Although quite lengthy, this blog post doesn’t even touch the surface of what can be studied around service-to-service communication. And with that being said, I hope you understood the core concepts around S2S communication and what is it’s role in the microservices architecture!

❤️ If you find this article helpful, I would appreciate a clap and if you have any question, feel free to ask me!

--

--

Konstantin Borimechkov

Writing Code For a Living 🚀 | Software Engineer @ Tide | Writing Blogs About Python & CS