Skip to content

Chapter 2: Product Catalogue Microservice

Reyas Khan M edited this page Jun 5, 2025 · 1 revision

Show Rendered

Chapter 2: Product Catalogue Microservice

Welcome back! In Chapter 1: Shopfront Microservice, we learned that the Shopfront is the part of our application that customers interact with. It's like the shop window, displaying products and handling requests. We also saw that the Shopfront doesn't store all the information it needs. Specifically, it needs details about the products themselves, like their names, descriptions, and prices.

Where does the Shopfront get this crucial product information? That's the job of the service we'll explore in this chapter: the Product Catalogue Microservice.

What is the Product Catalogue Microservice?

Imagine a traditional store. They have a master catalogue – maybe a big binder or a computer system – that lists every product they sell. This catalogue holds the fundamental facts about each item: its unique identifier, what it's called, what it's like, and how much it costs. It doesn't worry about how many are currently on the shelves or if they are selling fast. Its only job is to be the definitive source for what the products are.

In our online shop application, the Product Catalogue Microservice plays this exact role. It is the central authority for product information.

Its main responsibilities are:

  1. Storing Product Data: It keeps a list of all products and their basic details (ID, name, description, price).
  2. Providing Product Data: It offers a way for other services (like our Shopfront from Chapter 1) to ask for and receive this product information.

It intentionally does not handle things like how many items are in stock. That's a different concern handled by another service, as we'll see in the next chapter.

Why a Separate Product Catalogue Service?

Just like with the Shopfront, making the Product Catalogue a separate microservice offers benefits:

  • Focus: This service only focuses on managing product data. It doesn't get bogged down with user interfaces or stock management logic.
  • Maintainability: If we need to change how product details are stored or retrieved (e.g., switch databases), we only work on this service.
  • Scalability: If lots of services need product information frequently, we can scale just the Product Catalogue service to handle those requests, without affecting other parts of the system.
  • Independence: The Product Catalogue can be updated or deployed independently of the Shopfront or Stock Manager.

This separation keeps our application pieces smaller, simpler, and easier to manage individually.

How the Product Catalogue Provides Data

The Product Catalogue makes its information available through an API (Application Programming Interface). Think of an API as a menu in a restaurant – it lists the things you can order (requests you can make) and what you'll get back (the data you'll receive).

Specifically, the Product Catalogue provides endpoints (like specific pages on a website, but for computer services to talk to) that other services can call. For example:

  • One endpoint might be used to get a list of all products.
  • Another endpoint might be used to get details for a specific product (like asking for product number '3').

When the Shopfront needs product details (as shown in the diagram in Chapter 1), it sends a request to the Product Catalogue's API. The Product Catalogue receives the request, looks up the information it has, and sends it back to the Shopfront.

Here's a very simple view of this interaction specifically focusing on the Product Catalogue's role:

sequenceDiagram
    participant OtherService as Other Microservice (e.g., Shopfront)
    participant ProductCatalogue as Product Catalogue Microservice
    participant InternalData as Product Data (e.g., In Memory List)

    OtherService->>ProductCatalogue: Request Product List (via API)
    ProductCatalogue->>InternalData: Lookup all products
    InternalData-->>ProductCatalogue: Provide Product Data
    ProductCatalogue-->>OtherService: Send Product Data (via API Response)

    OtherService->>ProductCatalogue: Request Details for Product ID '1' (via API)
    ProductCatalogue->>InternalData: Lookup Product ID '1'
    InternalData-->>ProductCatalogue: Provide Product ID '1' Data
    ProductCatalogue-->>OtherService: Send Product ID '1' Data (via API Response)
Loading

The Product Catalogue sits there, waiting for requests, and responds with the relevant product data it holds.

Looking at the Product Catalogue Code

Let's peek inside the productcatalogue directory to see how this service is built in our Java project. Unlike the Shopfront which uses Spring Boot, this service uses a different Java framework called Dropwizard. This is a common practice in microservices – different services can use the best tools for their specific job! Don't worry too much about the framework differences for now; the core concepts are what's important.

First, let's look at what defines a "Product" in this service:

// productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/model/Product.java
package uk.co.danielbryant.djshopping.productcatalogue.model;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.math.BigDecimal;

public class Product {
    private String id;
    private String name;
    private String description;
    private BigDecimal price;

    // Constructor used when creating Product objects
    public Product(String id, String name, String description, BigDecimal price) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.price = price;
    }

    // Get methods expose the properties (used when sending data via API)
    @JsonProperty // This helps the service turn the object into JSON data
    public String getId() { return id; }

    @JsonProperty
    public String getName() { return name; }

    @JsonProperty
    public String getDescription() { return description; }

    @JsonProperty
    public BigDecimal getPrice() { return price; }

    // ... (Constructor needed for framework, not shown for brevity) ...
}

This Product class is a simple data model. It defines the structure of the product information: an id (a unique number or code), the name, a brief description, and the price. This is the shape of the data the Product Catalogue service manages and provides.

Next, let's see the component that holds the actual product list and the logic to retrieve products:

// productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/services/ProductService.java
package uk.co.danielbryant.djshopping.productcatalogue.services;

import uk.co.danielbryant.djshopping.productcatalogue.model.Product;
import java.math.BigDecimal;
import java.util.*;

public class ProductService {

    // A simple list of products stored right here in memory
    private Map<String, Product> fakeProductDAO = new HashMap<>();

    // When the service starts, this code runs to populate the list
    public ProductService() {
        fakeProductDAO.put("1", new Product("1", "Widget", "Premium ACME Widgets", new BigDecimal("1.20")));
        fakeProductDAO.put("2", new Product("2", "Sprocket", "Grade B sprockets", new BigDecimal("4.10")));
        // ... (more products added) ...
    }

    // Method to get all products
    public List<Product> getAllProducts() {
        return new ArrayList<>(fakeProductDAO.values());
    }

    // Method to get a specific product by ID
    public Optional<Product> getProduct(String id) {
        return Optional.ofNullable(fakeProductDAO.get(id));
    }
}

The ProductService is like the "brain" for finding products. In a real application, fakeProductDAO would be replaced by code that talks to a database. But for this tutorial, we simply store the product data in a HashMap right in the service's memory when it starts. The getAllProducts() method returns the list of all products, and getProduct(id) finds a specific one.

Now, let's look at the part that handles incoming API requests:

// productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/resources/ProductResource.java
package uk.co.danielbryant.djshopping.productcatalogue.resources;

import com.google.inject.Inject;
import uk.co.danielbryant.djshopping.productcatalogue.services.ProductService;
import javax.ws.rs.GET; // Annotation for HTTP GET requests
import javax.ws.rs.Path; // Annotation to define the URL path
import javax.ws.rs.PathParam; // Annotation to get ID from 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; // Object to build the API response
import java.util.Optional;

@Path("/products") // This resource handles requests starting with /products
@Produces(MediaType.APPLICATION_JSON) // This resource will send back JSON data
public class ProductResource {

    private ProductService productService;

    @Inject // This tells the framework to provide a ProductService object
    public ProductResource(ProductService productService) {
        this.productService = productService;
    }

    @GET // This method handles GET requests to /products
    public Response getAllProducts() {
        // Use the ProductService to get all products
        // Return them in an OK (200) response
        return Response.status(200)
                .entity(productService.getAllProducts())
                .build();
    }

    @GET // This method handles GET requests to /products/{id} (e.g., /products/1)
    @Path("{id}") // Define the path parameter {id}
    public Response getProduct(@PathParam("id") String id) { // Get the ID from the path
        // Use the ProductService to get the product by ID
        Optional<Product> result = productService.getProduct(id);

        // Check if the product was found
        if (result.isPresent()) {
            // Return the product data in an OK (200) response
            return Response.status(Response.Status.OK)
                    .entity(result.get())
                    .build();
        } else {
            // If not found, return a 404 Not Found response
            return Response.status(Response.Status.NOT_FOUND)
                    .build();
        }
    }
}

The ProductResource is the API endpoint handler. The @Path("/products") annotation means any request starting with /products comes here.

  • The getAllProducts() method, marked with @GET, handles requests to just /products. It uses our ProductService to get the full list and returns it as a JSON response.
  • The getProduct(@PathParam("id") String id) method, also marked @GET but with an additional @Path("{id}"), handles requests like /products/1, /products/2, etc. It extracts the {id} from the URL, uses the ProductService to find that specific product, and returns either the product data (with a 200 OK status) or a 404 Not Found status if the ID doesn't exist.

This is how the Product Catalogue service makes its data accessible to the outside world (meaning, other microservices).

Finally, the main application class that starts everything up:

// productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/ProductServiceApplication.java
package uk.co.danielbryant.djshopping.productcatalogue;

import com.google.inject.Guice; // Used for dependency injection
import com.google.inject.Injector;
import io.dropwizard.Application; // The main Dropwizard application class
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import uk.co.danielbryant.djshopping.productcatalogue.resources.ProductResource; // Our API handler

public class ProductServiceApplication extends Application<ProductServiceConfiguration> {

    // The standard main method to run the application
    public static void main(String[] args) throws Exception {
        new ProductServiceApplication().run(args);
    }

    @Override
    public String getName() {
        return "product-list-service";
    }

    @Override
    public void initialize(Bootstrap<ProductServiceConfiguration> bootstrap) {
        // Setup before running (minimal for this example)
    }

    @Override
    public void run(ProductServiceConfiguration config,
                    Environment environment) {
        // This method sets up the service when it starts
        Injector injector = Guice.createInjector(); // Setup Guice
        // Register our ProductResource so it can handle incoming requests
        environment.jersey().register(injector.getInstance(ProductResource.class));

        // ... (Health check setup, not shown for brevity) ...
    }
}

This class is the starting point for our Product Catalogue Dropwizard application. The run method is where we tell Dropwizard about our ProductResource so it knows to send requests coming to /products there.

We also need a configuration file for settings like the port:

# productcatalogue/product-catalogue.yml
version: 1.0-SNAPSHOT
 

server:
  applicationConnectors:
  - type: http
    port: 8020 # The port this service will listen on for API requests
  adminConnectors:
  - type: http
    port: 8025 # A separate port for administration/health checks

This product-catalogue.yml file is the configuration for our Dropwizard application. It tells the service to listen for application traffic (our API requests) on port 8020. Remember in Chapter 1: Shopfront Microservice, the shopfront/src/main/resources/application.properties file had productCatalogueUri = http://productcatalogue:8020? This is where that 8020 comes from! The Shopfront is configured to know the Product Catalogue will be available on this port.

Packaging the Product Catalogue

Just like the Shopfront, we need to package the Product Catalogue service so it can be run easily.

The pom.xml file manages the project's dependencies (the libraries it needs, like Dropwizard and Guice) and defines how to build it:

<!-- productcatalogue/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>productcatalogue</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <dropwizard.version>1.3.27</dropwizard.version>
        <guice.version>4.2.3</guice.version>
    </properties>

    <dependencies>
        <!-- Core Dropwizard dependency -->
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-core</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <!-- Guice for dependency injection -->
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>${guice.version}</version>
        </dependency>
        <!-- ... (other dependencies) ... -->
    </dependencies>

    <build>
        <plugins>
            <!-- Plugin to create an executable "fat" JAR -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>1.6</version>
                <configuration>
                    <!-- ... (configuration for shading) ... -->
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <!-- ... (transformers to make the JAR executable) ... -->
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <!-- Specify the main class to run -->
                                    <mainClass>uk.co.danielbryant.djshopping.productcatalogue.ProductServiceApplication</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!-- ... (other plugins like maven-compiler-plugin) ... -->
        </plugins>
    </build>
</project>

The key part here is the maven-shade-plugin. For Dropwizard applications, this plugin is used to create a single, executable "fat" JAR file that includes all the service's code and its dependencies. The mainClass configuration tells the plugin which class to make executable.

Finally, the Dockerfile describes how to build a Docker image for this service:

# productcatalogue/Dockerfile
FROM openjdk:8-jre # Start from a base Java 8 runtime image
ADD target/productcatalogue-0.0.1-SNAPSHOT.jar app.jar # Copy the executable JAR into the image
ADD product-catalogue.yml app-config.yml # Copy the configuration file into the image
EXPOSE 8020 # Indicate that the container listens on port 8020
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","app.jar", "server", "app-config.yml"] # Command to run when the container starts

This Dockerfile is similar to the Shopfront's, but notice it copies both the app.jar and the app-config.yml file into the image. The ENTRYPOINT command then runs the Java application, telling it to start as a "server" using the provided configuration file (app-config.yml). This packages our Product Catalogue service, including its configuration, into a self-contained unit. We'll explore Docker in depth in Chapter 6: Docker Container Packaging.

Conclusion

In this chapter, we explored the Product Catalogue Microservice. We learned that its core responsibility is to store and provide basic product information (ID, name, description, price). It acts as the central catalogue, making this data available to other services, such as the Shopfront Microservice, via an API. We looked at its structure, including the Product data model, the ProductService logic for finding data, and the ProductResource that handles API requests. We also saw how its configuration sets its operating port and how its pom.xml and Dockerfile prepare it for packaging into a container.

Now that we know how the Shopfront gets product details, we still need the other crucial piece of information: stock levels. Let's move on to the service that handles that.

Next Chapter: Stock Manager Microservice


Doc by Reyas Khan. References: [1], [2], [3], [4], [5], [6], [7]

Clone this wiki locally