-
Notifications
You must be signed in to change notification settings - Fork 0
Chapter 3: Stock Manager Microservice
Show Rendered
Welcome back to the tutorial! In the previous chapters, we met the Shopfront Microservice, which is the customer-facing part, and the Product Catalogue Microservice, which holds all the details about the products themselves (like name, description, and price).
But our online shop isn't just a list of products; customers also need to know if an item is actually available to buy! Imagine going to a physical store. You find something you like (using the Product Catalogue), but then you need to see if they actually have any in the back room (checking the stock).
In our microservices application, the service that acts like this "back room" or "warehouse" is the Stock Manager Microservice.
Think of the Stock Manager Microservice as the inventory system for our online store. Its main job is to keep track of how many of each product we currently have available. It's like the store's stock keeper, constantly updating counts as items are sold or new stock arrives.
Its core responsibilities are:
-
Storing Stock Data: It holds information about each product's stock level. This typically includes:
- The Product ID (so it knows which product's stock it's tracking).
- A Stock Keeping Unit (SKU), which is often another identifier used in inventory.
- The quantity currently available.
- Providing Stock Data: It offers a way for other services (especially the Shopfront Microservice) to ask for the current stock level for a specific product or list of products.
This service focuses only on the quantity aspect. It doesn't care about the product's name or price; it just knows how many units are in stock for a given product ID.
Again, separating the Stock Manager into its own microservice brings familiar advantages:
- Clear Responsibility: This service is solely responsible for managing stock levels. It doesn't need to worry about showing web pages (Shopfront) or listing product details (Product Catalogue).
- Easier Updates: If we need to change how stock is tracked (e.g., add warehouse locations, integrate with a physical inventory system), we only need to modify and redeploy the Stock Manager service.
- Scalability: If the system receives a huge number of requests just to check stock (e.g., a popular item is trending), we can scale only the Stock Manager service to handle the load, without scaling the entire application.
- Technology Choice: Just like the Product Catalogue could use Dropwizard while the Shopfront uses Spring Boot, the Stock Manager can use the best tools for its specific job, potentially even a different database technology optimized for frequently changing counts. (In this project, it uses Spring Boot like the Shopfront, but focuses on data persistence with a simple embedded database).
This microservice approach keeps each part manageable and focused.
Just like the Product Catalogue, the Stock Manager makes its stock information available through an API. The Shopfront Microservice (or any other service that needs to know stock levels, like a potential Order service later on) can send requests to the Stock Manager's API.
The key API endpoints the Stock Manager provides are likely:
- An endpoint to get the stock information for a specific product ID.
- An endpoint to get stock information for all products (which the Shopfront uses).
When the Shopfront needs to display products on its homepage, it first gets product details from the Product Catalogue. Then, for each product (or for all of them at once), it sends a request to the Stock Manager's API to get the corresponding stock level.
Here's how this interaction looks in our system, specifically showing the Shopfront asking the Stock Manager for data:
sequenceDiagram
participant Shopfront as Shopfront Microservice
participant StockManager as Stock Manager Microservice
participant InternalData as Stock Data (e.g., Database)
Shopfront->>StockManager: Request Stock Levels (via API)
StockManager->>InternalData: Lookup all stock items
InternalData-->>StockManager: Provide Stock Data
StockManager-->>Shopfront: Send Stock Data (via API Response)
Note over Shopfront,StockManager: Shopfront combines this with Product Details from Product Catalogue
The Stock Manager is the expert on stock and responds accordingly.
Let's dive into the stockmanager directory to see how this service is built using Spring Boot.
First, let's look at what defines a "Stock" item in this service:
// stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/model/Stock.java
package uk.co.danielbryant.djshopping.stockmanager.model;
import javax.persistence.Entity; // This annotation tells Spring this is a database entity
import javax.persistence.Id; // This annotation marks the primary key
@Entity
public class Stock {
@Id // The productId is the unique identifier for stock records
private String productId;
private String sku; // Stock Keeping Unit - another identifier
private int amountAvailable; // The actual stock count!
// Default constructor needed by JPA/Hibernate
protected Stock() {
}
// Constructor to create new Stock objects
public Stock(String productId, String sku, int amountAvailable) {
this.productId = productId;
this.sku = sku;
this.amountAvailable = amountAvailable;
}
// Get methods to access the data
public String getProductId() { return productId; }
public String getSku() { return sku; }
public int getAmountAvailable() { return amountAvailable; }
}This Stock class is our data model. It defines the structure of the stock information we store: productId (linking it back to the product), sku, and amountAvailable. The @Entity and @Id annotations are part of Java Persistence API (JPA), which Spring Boot uses to interact with databases.
Next, the component that handles saving and retrieving stock data from a database:
// stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/repositories/StockRepository.java
package uk.co.danielbryant.djshopping.stockmanager.repositories;
import org.springframework.data.repository.CrudRepository; // Provides basic database operations
import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
// By extending CrudRepository, Spring Data JPA automatically creates
// methods like findById, findAll, save, delete etc. for our Stock model.
public interface StockRepository extends CrudRepository<Stock, String> {
// No methods needed here for our basic requirements!
}The StockRepository is a Spring Data JPA component. By simply extending CrudRepository<Stock, String>, Spring automatically provides standard database operations (like finding, saving, deleting) for our Stock objects using the productId (which is a String) as the identifier. This is a powerful Spring feature that reduces boilerplate code! In this project, it's configured to use an in-memory H2 database for simplicity.
Now, the service layer that uses the repository to implement our business logic (in this case, just retrieving stock data):
// stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/services/StockService.java
package uk.co.danielbryant.djshopping.stockmanager.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import uk.co.danielbryant.djshopping.stockmanager.exceptions.StockNotFoundException;
import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
import uk.co.danielbryant.djshopping.stockmanager.repositories.StockRepository;
import java.util.List;
import java.util.Optional; // Used for getStock to handle not found case
import java.util.stream.Collectors;
import java.util.stream.StreamSupport; // Helper for findAll()
@Service // Marks this class as a Service component
public class StockService {
private StockRepository stockRepository;
@Autowired // Spring injects the StockRepository here
public StockService(StockRepository stockRepository) {
this.stockRepository = stockRepository;
}
// Get a list of all stock items
public List<Stock> getStocks() {
// stockRepository.findAll() returns an Iterable, so we convert it to a List
return StreamSupport.stream(stockRepository.findAll().spliterator(), false)
.collect(Collectors.toList());
}
// Get stock for a specific product ID
public Stock getStock(String productId) throws StockNotFoundException {
// stockRepository.findById() returns an Optional (it might not find it)
return stockRepository.findById(productId)
// If found, return the Stock object, otherwise throw an exception
.orElseThrow(() -> new StockNotFoundException("Stock not found with productId: " + productId));
}
}The StockService class contains the logic to get stock data. It uses the StockRepository to fetch the data from the database. getStocks() retrieves all stock records, and getStock(productId) retrieves a single record by its ID, throwing a custom StockNotFoundException if the ID doesn't exist. The @Service annotation marks this as a Spring service component, which can be injected into other parts of the application (like our API handler).
Next, the part that handles incoming API requests:
// stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/resources/StockResource.java
package uk.co.danielbryant.djshopping.stockmanager.resources;
import org.slf4j.Logger; // For logging messages
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; // For dependency injection
import org.springframework.http.HttpStatus; // HTTP status codes
import org.springframework.web.bind.annotation.*; // Spring Web annotations
import uk.co.danielbryant.djshopping.stockmanager.exceptions.StockNotFoundException;
import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
import uk.co.danielbryant.djshopping.stockmanager.services.StockService;
import java.util.List;
@RestController // Marks this class as a REST API controller
@RequestMapping("/stocks") // All endpoints in this class start with /stocks
public class StockResource {
private static final Logger LOGGER = LoggerFactory.getLogger(StockResource.class); // Setup logger
@Autowired // Spring injects the StockService here
private StockService stockService;
@RequestMapping() // Handles requests to /stocks (default GET)
public List<Stock> getStocks() {
LOGGER.info("getStocks (All stocks)"); // Log the request
return stockService.getStocks(); // Call the service to get data
}
@RequestMapping("{productId}") // Handles requests to /stocks/{productId} (e.g., /stocks/1)
public Stock getStock(@PathVariable("productId") String productId) throws StockNotFoundException {
LOGGER.info("getStock with productId: {}", productId); // Log with product ID
return stockService.getStock(productId); // Call the service with the ID
}
// This method handles the StockNotFoundException thrown by the service
@ExceptionHandler
@ResponseStatus(HttpStatus.NOT_FOUND) // Return 404 Not Found status
public void handleStockNotFound(StockNotFoundException snfe) {
// No response body needed, just the 404 status
}
}The StockResource is our API controller using Spring MVC.
-
@RestControllerindicates it's a RESTful service. -
@RequestMapping("/stocks")means all endpoints here start with/stocks. - The first
@RequestMapping()method handlesGETrequests to/stocksand returns the list of allStockitems by callingstockService.getStocks(). Spring automatically converts the list ofStockobjects into JSON format for the API response. - The second
@RequestMapping("{productId}")method handlesGETrequests to paths like/stocks/1,/stocks/2, etc. The@PathVariable("productId")annotation extracts the ID from the URL path. It then callsstockService.getStock(productId)to get the specific stock item. - The
handleStockNotFoundmethod uses@ExceptionHandlerto catch theStockNotFoundExceptionour service can throw. When this happens,@ResponseStatus(HttpStatus.NOT_FOUND)tells Spring to send back an HTTP 404 status code, which is the standard way to indicate that the requested resource (a specific stock item by ID) wasn't found.
This StockResource exposes the functionality of the StockService as a web API that other services can call.
Where does the initial stock data come from? In this project, there's a simple component to populate the in-memory database when the service starts:
// stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/config/DataGenerator.java
package uk.co.danielbryant.djshopping.stockmanager.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; // Ensure database operations are a single unit
import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
import uk.co.danielbryant.djshopping.stockmanager.repositories.StockRepository;
import javax.annotation.PostConstruct; // This method runs after dependency injection is done
@Component // Marks this as a Spring component
public class DataGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger(DataGenerator.class);
private StockRepository stockRepository;
@Autowired // Inject the repository
protected DataGenerator(StockRepository stockRepository) {
this.stockRepository = stockRepository;
}
@PostConstruct // This method runs automatically after the component is created
@Transactional // Ensures all saves happen together
public void init() {
LOGGER.info("Generating synthetic data for demonstration purposes...");
// Create and save some initial stock data using the repository
stockRepository.save(new Stock("1", "12345678", 5)); // Product 1 has 5 items
stockRepository.save(new Stock("2", "34567890", 2)); // Product 2 has 2 items
stockRepository.save(new Stock("3", "54326745", 999)); // Product 3 has 999 items
stockRepository.save(new Stock("4", "93847614", 0)); // Product 4 is out of stock
stockRepository.save(new Stock("5", "11856388", 1)); // Product 5 has 1 item
LOGGER.info("... data generation complete");
}
}The DataGenerator is a Spring component that runs code automatically after the application starts up (@PostConstruct). Its init() method uses the StockRepository to save a few initial Stock records into the database (which is the in-memory H2 database in this case). This provides some data for our API endpoints to return when the service first runs.
Finally, the main application class and configuration:
// stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/StockManagerApplication.java
package uk.co.danielbryant.djshopping.stockmanager;
import org.springframework.boot.SpringApplication; // Standard Spring Boot runner
import org.springframework.boot.autoconfigure.SpringBootApplication; // Main annotation
@SpringBootApplication // This annotation combines configuration, auto-configuration, and component scanning
public class StockManagerApplication {
// Standard main method to start the Spring Boot application
public static void main(String[] args) {
SpringApplication.run(StockManagerApplication.class, args);
}
}This is the standard main class for a Spring Boot application. The @SpringBootApplication annotation does a lot of work behind the scenes to configure and start the service based on the code structure and dependencies.
# stockmanager/src/main/resources/application.properties
server.port = 8030This application.properties file is simple but important. It tells the Spring Boot application to run on port 8030. As we saw in the Shopfront Microservice chapter, its configuration (shopfront/src/main/resources/application.properties) includes stockManagerUri = http://stockmanager:8030, which matches this port. This is how the Shopfront knows where to find the Stock Manager when they are running.
Like the other services, we need to package the Stock Manager into an executable format that can be easily run, especially in a container.
The pom.xml file manages dependencies (Spring Boot, H2 database, JPA) and build process using Maven:
<!-- stockmanager/pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>uk.co.danielbryant.djshopping</groupId>
<artifactId>stockmanager</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- ... (name, description, parent - inheriting Spring Boot versions) ... -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<!-- ... (other properties) ... -->
</properties>
<dependencies>
<!-- Needed for building web services -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Needed for database interaction with JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- The in-memory database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!-- ... (other dependencies like actuator, test) ... -->
</dependencies>
<build>
<plugins>
<!-- Plugin to build an executable "fat" JAR for Spring Boot -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- ... (other plugins) ... -->
</plugins>
</build>
</project>Similar to the Shopfront, the spring-boot-maven-plugin is configured in the pom.xml. When we build the project using Maven, this plugin creates a single, executable JAR file containing all the Stock Manager's code and dependencies.
Finally, the Dockerfile prepares the service for running inside a container:
# stockmanager/Dockerfile
FROM openjdk:8-jre # Use a base Java 8 runtime image
ADD target/stockmanager-0.0.1-SNAPSHOT.jar app.jar # Copy the executable JAR into the image
EXPOSE 8030 # Document that the container listens on port 8030
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] # Command to run the JARThis Dockerfile is very similar to the Shopfront's. It starts from a base Java image, copies the executable JAR file into the container, exposes the port the service runs on (8030), and defines the command to execute when the container starts, which is running the Java application contained in the JAR.
This packages our Stock Manager service into a self-contained Docker image, ready to be run consistently anywhere. We'll learn more about Docker containers in Chapter 6: Docker Container Packaging.
In this chapter, we explored the Stock Manager Microservice. We learned that its main purpose is to act as the application's inventory system, keeping track of the quantity of each product available. It stores this information (Product ID, SKU, amount) and makes it accessible to other services, particularly the Shopfront Microservice, via a simple API. We looked at its key components, including the Stock data model, the StockRepository for database interaction, the StockService for business logic, and the StockResource for handling API requests. We also saw how its configuration sets its port and how Maven and Docker prepare it for deployment.
Now that we understand the three main services - the Shopfront (user interface), the Product Catalogue (product details), and the Stock Manager (stock levels) - we have seen how they need to talk to each other. But how exactly do microservices communicate? In the next chapter, we'll dive into the common ways they do this, focusing on REST APIs.
Next Chapter: Microservice Communication (REST APIs)
Doc by Reyas Khan. References: [1], [2], [3], [4], [5], [6], [7], [8], [9]
TO Begin: Start here