-
Notifications
You must be signed in to change notification settings - Fork 0
Chapter 4: Microservice Communication (REST APIs)
Welcome to Chapter 4! In the previous chapters, we introduced the main characters of our online shop: the Shopfront Microservice (the part the customer sees), the Product Catalogue Microservice (which knows about product details), and the Stock Manager Microservice (which tracks how many items are available).
We saw that the Shopfront needs information from both the Product Catalogue and the Stock Manager to display a complete list of products (name, description, price, and stock level).
But how do these separate services actually talk to each other? They are independent programs running potentially on different machines. This chapter is all about understanding that crucial communication layer.
Imagine our three services are like different departments in a large company:
- Shopfront (Sales Department): Deals directly with customers. Needs info to answer customer questions.
- Product Catalogue (Marketing/Cataloguing Department): Knows all the specs, descriptions, and prices of products.
- Stock Manager (Warehouse/Inventory Department): Knows exactly how much of each product is in stock.
When the Sales Department needs product specs and current stock to tell a customer if something is available, they don't just magically know this information. They have to ask the Marketing Department and the Warehouse Department. They might send a memo, make a phone call, or use an internal system.
In the world of microservices, these "memos," "phone calls," or "internal systems" are typically APIs (Application Programming Interfaces). And a very common way for microservices to talk to each other is using REST APIs over HTTP.
Don't let the terms scare you! At its core, it's about using the familiar rules of the internet (HTTP) to ask for or send information between computer programs.
-
HTTP: This is the protocol your web browser uses to talk to websites. When you type a URL like
https://www.example.com/
, your browser sends an HTTP request to the example.com server, and the server sends back an HTTP response (which includes the web page content). Microservices use HTTP in a similar way, but instead of asking for web pages, they ask for data. - REST (Representational State Transfer): This is a set of principles or guidelines for designing networked applications. When applied to HTTP, it means thinking about your application's data as "resources" (like a list of "products" or a list of "stocks") and using standard HTTP methods to interact with them.
Think of HTTP methods like actions you can take:
HTTP Method | Common Action in REST | Example Request |
---|---|---|
GET |
Get (read) data from a resource |
GET /products (Get all products) |
POST |
Create new data on a resource |
POST /orders (Create a new order) |
PUT |
Update existing data for a resource |
PUT /products/123 (Update product 123) |
DELETE |
Delete data for a resource |
DELETE /stocks/abc (Delete stock for ABC) |
In our case, the Shopfront mostly needs to GET
data from the Product Catalogue and Stock Manager.
When microservices communicate via REST APIs, they need a common language or format to send data back and forth. A very popular format is JSON (JavaScript Object Notation).
JSON is a lightweight way to organize data using simple key-value pairs and lists. It looks like this:
{
"id": "1",
"name": "Widget",
"price": 1.20
}
This represents a single product with properties like id
, name
, and price
.
[
{
"id": "1",
"name": "Widget"
},
{
"id": "2",
"name": "Sprocket"
}
]
This represents a list of products.
JSON is easy for both humans to read and computers to parse, making it ideal for API communication.
Let's revisit the Shopfront's main task: displaying products on the home page.
As we saw in Chapter 1: Shopfront Microservice, the Shopfront's ProductService
needs product details from the Product Catalogue Microservice and stock levels from the Stock Manager Microservice.
Here's the communication flow:
sequenceDiagram
participant Shopfront as Shopfront
participant ProductCatalogue as Product Catalogue
participant StockManager as Stock Manager
Shopfront->>ProductCatalogue: GET /products (Ask for all product details)
ProductCatalogue-->>Shopfront: HTTP Response (List of Product details in JSON)
Shopfront->>StockManager: GET /stocks (Ask for all stock levels)
StockManager-->>Shopfront: HTTP Response (List of Stock info in JSON)
Note over Shopfront: Shopfront combines details and stock data
Note over Shopfront: to create final Product list for display
The Shopfront acts as the client, making requests. The Product Catalogue and Stock Manager act as servers, receiving requests and sending responses.
Let's look at the Shopfront's code again to see how it makes these HTTP calls.
First, the Shopfront needs to know where the other services are located. This is defined in its configuration file:
# shopfront/src/main/resources/application.properties
# ... other properties ...
productCatalogueUri = http://productcatalogue:8020
stockManagerUri = http://stockmanager:8030
These lines tell the Shopfront the base address (URI) for each service. The names productcatalogue
and stockmanager
are service names that will be resolved later by Docker Compose or Kubernetes (we'll see this in future chapters!). For now, think of them as hostnames, and 8020
and 8030
are the ports the services listen on, as we configured in Chapter 2 and Chapter 3.
To actually make the HTTP calls in Java, the Shopfront uses a tool provided by Spring: RestTemplate
. This is set up in the main application class:
// shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/ShopfrontApplication.java
// ... imports ...
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableHystrix // Used for fault tolerance (more later!)
public class ShopfrontApplication {
// ... main method ...
@Bean(name = "stdRestTemplate") // Register RestTemplate as a usable component
public RestTemplate getRestTemplate() {
return new RestTemplate(); // Create and return a new RestTemplate
}
}
The @Bean
annotation makes the RestTemplate
available to be "injected" into other components that need it.
Now, let's look at the ProductRepo
class (introduced briefly in Chapter 1), which is responsible for talking specifically to the Product Catalogue:
// shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/repo/ProductRepo.java
// ... imports ...
import org.springframework.web.client.RestTemplate;
import uk.co.danielbryant.djshopping.shopfront.services.dto.ProductDTO; // Data structure for Product
@Component // Marks this as a component Spring can manage
public class ProductRepo {
@Value("${productCatalogueUri}") // Get the URI from application.properties
private String productCatalogueUri;
@Autowired // Inject the RestTemplate we defined earlier
@Qualifier(value = "stdRestTemplate")
private RestTemplate restTemplate;
// Method to get Product data from the Product Catalogue service
public Map<String, ProductDTO> getProductDTOs() {
// Make the HTTP GET request
ResponseEntity<List<ProductDTO>> productCatalogueResponse =
restTemplate.exchange(
productCatalogueUri + "/products", // The full URL: e.g., http://productcatalogue:8020/products
HttpMethod.GET, // The HTTP method
null, // No request body for GET
new ParameterizedTypeReference<List<ProductDTO>>() { // How to read the response (List of ProductDTO)
});
// Get the list of ProductDTO objects from the response body
List<ProductDTO> productDTOs = productCatalogueResponse.getBody();
// Convert the list into a Map for easier lookup by product ID later
return productDTOs.stream()
.collect(Collectors.toMap(ProductDTO::getId, Function.identity()));
}
}
This code shows the Shopfront using RestTemplate.exchange()
to send a GET
request to the URL constructed from productCatalogueUri
and the path /products
. It specifies that it expects a list of ProductDTO
objects back. RestTemplate
handles the details of sending the HTTP request and receiving and parsing the JSON response into Java objects (List<ProductDTO>
).
The StockRepo
works in a very similar way to call the Stock Manager:
// shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/repo/StockRepo.java
// ... imports ...
import org.springframework.web.client.RestTemplate;
import uk.co.danielbryant.djshopping.shopfront.services.dto.StockDTO; // Data structure for Stock
@Component
public class StockRepo {
@Value("${stockManagerUri}") // Get the URI from application.properties
private String stockManagerUri;
@Autowired // Inject the RestTemplate
@Qualifier(value = "stdRestTemplate")
private RestTemplate restTemplate;
// Method to get Stock data from the Stock Manager service
@HystrixCommand(fallbackMethod = "stocksNotFound") // Added for resilience (ignore for now!)
public Map<String, StockDTO> getStockDTOs() {
LOGGER.info("getStocksDTOs");
// Make the HTTP GET request
ResponseEntity<List<StockDTO>> stockManagerResponse =
restTemplate.exchange(
stockManagerUri + "/stocks", // The full URL: e.g., http://stockmanager:8030/stocks
HttpMethod.GET, // The HTTP method
null, // No request body for GET
new ParameterizedTypeReference<List<StockDTO>>() { // How to read the response (List of StockDTO)
});
// Get the list of StockDTO objects from the response body
List<StockDTO> stockDTOs = stockManagerResponse.getBody();
// Convert the list into a Map for easier lookup by product ID later
return stockDTOs.stream()
.collect(Collectors.toMap(StockDTO::getProductId, Function.identity()));
}
// ... fallback method ...
}
Again, RestTemplate.exchange()
sends a GET
request, this time to stockManagerUri + "/stocks"
, expecting a list of StockDTO
objects in return.
These ProductDTO
and StockDTO
classes represent the shape of the data that the Product Catalogue and Stock Manager send back, respectively. We'll look at these Data Transfer Objects (DTOs) in more detail in the next chapter, Chapter 5: Data Models / DTOs.
Finally, the ProductService
in the Shopfront uses both repos:
// shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/ProductService.java
// ... imports ...
import uk.co.danielbryant.djshopping.shopfront.repo.StockRepo; // Import the StockRepo
import uk.co.danielbryant.djshopping.shopfront.repo.ProductRepo; // Import the ProductRepo
import uk.co.danielbryant.djshopping.shopfront.model.Product; // The combined Product model
// ... imports for DTOs ...
@Service // Marks this as a Service component
public class ProductService {
@Autowired
private StockRepo stockRepo; // Shopfront's component to talk to Stock Manager
@Autowired
private ProductRepo productRepo; // Shopfront's component to talk to Product Catalogue
// This method gets data from both services and combines it
public List<Product> getProducts() {
// 1. Get product details by calling the ProductRepo (which calls Product Catalogue)
Map<String, ProductDTO> productDTOs = productRepo.getProductDTOs();
// 2. Get stock info by calling the StockRepo (which calls Stock Manager)
Map<String, StockDTO> stockDTOMap = stockRepo.getStockDTOs();
// 3. Combine the data from both sources
return productDTOs.values().stream()
.map(productDTO -> {
// For each product, find its matching stock info
StockDTO stockDTO = stockDTOMap.get(productDTO.getId());
// Create a final Product object for the Shopfront with combined info
return new Product(productDTO.getId(), stockDTO.getSku(),
productDTO.getName(), productDTO.getDescription(),
productDTO.getPrice(), stockDTO.getAmountAvailable());
})
.collect(Collectors.toList()); // Collect into a List
}
// ... (other methods) ...
}
This ProductService
orchestrates the calls, first getting data from productRepo
, then from stockRepo
, and finally combining the results into the Product
model that the Shopfront uses to display the information.
On the other side of the communication are the services providing the data. They expose API endpoints that listen for incoming HTTP requests.
In the Product Catalogue Microservice, the ProductResource
class defines the API:
// productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/resources/ProductResource.java
// ... imports ...
import uk.co.danielbryant.djshopping.productcatalogue.model.Product; // The data model it manages
import javax.ws.rs.GET; // Annotation for HTTP GET
import javax.ws.rs.Path; // Annotation for URL path
import javax.ws.rs.Produces; // Annotation for data format
import javax.ws.rs.core.MediaType; // Defines JSON format
import javax.ws.rs.core.Response; // Used to build the HTTP response
import java.util.Optional;
@Path("/products") // This resource handles requests starting with /products
@Produces(MediaType.APPLICATION_JSON) // It will send back JSON data
public class ProductResource {
// ... constructor with injected ProductService ...
@GET // This method handles GET requests
public Response getAllProducts() {
// Get products from its internal service
// Return them with a 200 OK status, formatted as JSON
return Response.status(200)
.entity(productService.getAllProducts())
.build();
}
@GET // Handles GET requests
@Path("{id}") // For specific product IDs in the URL, like /products/1
public Response getProduct(@PathParam("id") String id) { // Get the ID from the URL
// ... logic to get product by ID ...
// Returns 200 OK with product data or 404 Not Found
// ...
return Response.status(Response.Status.OK).entity(result.get()).build(); // Example success response
}
}
The annotations like @Path("/products")
and @GET
are how the Dropwizard framework knows to map incoming HTTP requests (specifically GET
requests to /products
or /products/{id}
) to the correct methods (getAllProducts
, getProduct
). The Response.entity()
method is used to put the Java objects (like a List<Product>
) into the response body, and because of @Produces(MediaType.APPLICATION_JSON)
, Dropwizard automatically converts these objects into JSON format before sending the HTTP response back.
Similarly, in the Stock Manager Microservice, the StockResource
uses Spring annotations to define its API:
// stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/resources/StockResource.java
// ... imports ...
import org.springframework.web.bind.annotation.*; // Spring Web annotations
import uk.co.danielbryant.djshopping.stockmanager.model.Stock; // The data model it manages
@RestController // Marks this as a REST controller (handles requests and sends data)
@RequestMapping("/stocks") // All endpoints here start with /stocks
public class StockResource {
// ... injected StockService ...
@RequestMapping() // Handles GET requests to /stocks by default
public List<Stock> getStocks() {
// Get all stock items from its internal service
return stockService.getStocks(); // Spring automatically converts List<Stock> to JSON
}
@RequestMapping("{productId}") // Handles requests to /stocks/{productId}
public Stock getStock(@PathVariable("productId") String productId) throws StockNotFoundException {
// Get stock for a specific product ID
return stockService.getStock(productId); // Spring converts Stock object to JSON
}
// ... Exception handler for 404 ...
}
Here, @RequestMapping("/stocks")
maps requests to the base path, and subsequent @RequestMapping()
or @RequestMapping("{productId}")
map to specific sub-paths. The @RestController
annotation tells Spring to automatically handle converting the returned Java objects (like List<Stock>
or a single Stock
) into JSON format for the HTTP response.
This is how the Product Catalogue and Stock Manager services expose their data via well-defined REST APIs that other services (like the Shopfront) can consume.
In this chapter, we learned the fundamental way our microservices communicate: using REST APIs over HTTP, sending and receiving data in JSON format.
We saw how the Shopfront Microservice acts as a client, using RestTemplate
to make GET
requests to the API endpoints provided by the Product Catalogue Microservice (/products
) and the Stock Manager Microservice (/stocks
). These calls return the necessary product details and stock levels in JSON format, which the Shopfront then combines to display to the user.
We also briefly saw how the Product Catalogue (using Dropwizard) and Stock Manager (using Spring Boot) define their API endpoints using annotations to listen for these incoming HTTP requests and return data.
This pattern of independent services communicating via well-defined APIs is a cornerstone of microservice architecture.
Now that we understand how they talk (REST/HTTP/JSON), let's look closer at what exactly they are saying to each other – the structure of the data they are passing back and forth using Data Transfer Objects (DTOs).
Next Chapter: Data Models / DTOs
TO Begin: Start here