Skip to content

Chapter 5: Data Models DTOs

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

Welcome back! In the last chapter, Chapter 4: Microservice Communication (REST APIs), we learned how our microservices talk to each other using REST APIs over HTTP and the JSON data format. We saw that the Shopfront Microservice makes requests to the Product Catalogue Microservice and the Stock Manager Microservice to get information needed to display products.

But when these services send data back in JSON, how does the receiving service (the Shopfront in this case) understand that data and turn it back into usable objects in its own Java code? This is where Data Models and DTOs (Data Transfer Objects) come in.

This chapter is all about understanding the structures we use to organize and exchange data within and between our services.

Why Do We Need Structured Data?

Imagine you're asking someone for information, but they just give you a jumbled pile of notes. It would be hard to figure out what's what! It's much easier if the information is organized into a clear format, like a form or a list with clear headings.

In software, especially when different programs or services need to share information, we need a standardized way to represent that data. This ensures that:

  1. The sending service knows exactly what information to include and how to structure it.
  2. The receiving service knows exactly what information to expect and how to read it.

This standard structure is often defined using Data Models and DTOs.

Data Models: The Shape of Data Inside a Service

A Data Model usually represents the structure of data as it's used within a specific service. This often maps closely to how the data is stored (e.g., in a database) or how it's primarily manipulated by the service's internal logic.

Let's look at the internal data models in our services:

Product Catalogue's Internal Data Model

In the Product Catalogue Microservice, the core piece of data is a product. It has an ID, name, description, and price. The service defines a Product class to represent this:

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

import java.math.BigDecimal;

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

    // Constructor and getter methods ...
    // (Methods like getId(), getName(), etc. would be here)
}

This Product class is the Product Catalogue's internal data model for a product. It represents the full set of product details that that service cares about and manages.

Stock Manager's Internal Data Model

Similarly, in the Stock Manager Microservice, the key data is about stock levels. It tracks the productId (to link it to a product), an sku, and the amountAvailable. The service defines a Stock class for this:

// stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/model/Stock.java
package uk.co.danielbryant.djshopping.stockmanager.model;

import javax.persistence.Entity; // Used for database mapping

@Entity // This Stock class is mapped to a database table
public class Stock {

    private String productId; // Links to the product
    private String sku;
    private int amountAvailable;

    // Constructor and getter methods ...
    // (Methods like getProductId(), getSku(), getAmountAvailable() would be here)
}

This Stock class is the Stock Manager's internal data model for a stock item. It represents the data that that service cares about and manages (how much stock is available). The @Entity annotation hints that this structure is used to interact with a database.

DTOs: The Shape of Data for Transfer

DTO stands for Data Transfer Object. As the name suggests, DTOs are specifically designed for transferring data, typically over a network (like between microservices via an API).

Why use a DTO instead of just sending the internal Data Model class directly?

  1. Decoupling: Services should be independent. Using DTOs means the API contract (the shape of the data exchanged) is separate from the internal data model used within a service. The Stock Manager might add a new field to its internal Stock model (e.g., warehouseLocation), but it doesn't have to include that in the StockDTO it sends to the Shopfront. This prevents internal changes from breaking other services.
  2. Control: You can include only the necessary fields in a DTO. The Product Catalogue's internal Product might have internal fields not needed by the Shopfront (like creation date or internal cost). The ProductDTO only includes what's relevant for the API consumer.
  3. Simplification: Sometimes the data needs to be restructured or combined slightly for transfer compared to its internal representation.

Think of DTOs as the standardized forms that services fill out when they need to send information to another service. The receiving service knows how to read that specific form.

Let's look at the DTOs used in our project, specifically in the Shopfront microservice, because the Shopfront is the receiver of data from the other two services' APIs.

Shopfront's ProductDTO (Receiving Product Data)

When the Shopfront calls the Product Catalogue Microservice's /products API endpoint, the Product Catalogue sends back product details. The Shopfront expects this data to arrive in a specific structure, defined by its ProductDTO class:

// shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/dto/ProductDTO.java
package uk.co.danielbryant.djshopping.shopfront.services.dto;

import java.math.BigDecimal;

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

    // Constructor and getter methods ...
    // (These match the fields expected in the JSON response from Product Catalogue)
}

Notice how this ProductDTO in the Shopfront looks almost exactly like the Product model inside the Product Catalogue. This is common for simple cases. The Shopfront's RestTemplate (as seen in Chapter 4) uses this ProductDTO definition to automatically parse the incoming JSON data from the Product Catalogue's API response into a List of ProductDTO objects.

Shopfront's StockDTO (Receiving Stock Data)

Similarly, when the Shopfront calls the Stock Manager Microservice's /stocks API endpoint, the Stock Manager sends back stock levels. The Shopfront expects this data to match the structure defined by its StockDTO class:

// shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/dto/StockDTO.java
package uk.co.danielbryant.djshopping.shopfront.services.dto;

public class StockDTO {
    private String productId;
    private String sku;
    private int amountAvailable;

    // Constructor and getter methods ...
    // (These match the fields expected in the JSON response from Stock Manager)
}

This StockDTO in the Shopfront defines the structure for the data coming from the Stock Manager. The RestTemplate uses this definition to parse the incoming JSON from the Stock Manager's API response into a List of StockDTO objects.

How DTOs are Used in the Shopfront

Let's see how the Shopfront's ProductService (which we looked at in Chapter 1 and Chapter 4) uses these DTOs.

The ProductService needs to get data from both the Product Catalogue and the Stock Manager and combine it.

Here's the simplified sequence:

sequenceDiagram
    participant ShopfrontService as Shopfront's ProductService
    participant ProductRepo as Shopfront's ProductRepo
    participant StockRepo as Shopfront's StockRepo
    participant ProductCatalogue as Product Catalogue Microservice
    participant StockManager as Stock Manager Microservice

    ShopfrontService->>ProductRepo: Ask for Product Details
    ProductRepo->>ProductCatalogue: GET /products
    ProductCatalogue-->>ProductRepo: JSON (List of Product Data)
    Note over ProductRepo: Converts JSON to List<ProductDTO>
    ProductRepo-->>ShopfrontService: List<ProductDTO>

    ShopfrontService->>StockRepo: Ask for Stock Levels
    StockRepo->>StockManager: GET /stocks
    StockManager-->>StockRepo: JSON (List of Stock Data)
    Note over StockRepo: Converts JSON to List<StockDTO>
    StockRepo-->>ShopfrontService: List<StockDTO>

    Note over ShopfrontService: Combines data from ProductDTOs and StockDTOs
Loading

The ProductRepo and StockRepo components in the Shopfront are responsible for the communication. As we saw in Chapter 4, they use RestTemplate to make the HTTP calls. RestTemplate needs to know the expected shape of the data it will receive, and that's where the ProductDTO and StockDTO come in. It uses these DTO definitions to automatically map the fields from the incoming JSON response to the fields in the DTO classes.

After the ProductService receives the List<ProductDTO> from ProductRepo and the List<StockDTO> from StockRepo, it needs to combine this information.

Here's the relevant snippet from ProductService:

// shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/ProductService.java
// ... imports for Product, ProductDTO, StockDTO ...
// ... Autowired ProductRepo and StockRepo ...

public List<uk.co.danielbryant.djshopping.shopfront.model.Product> getProducts() {
    // 1. Get product details - receives List<ProductDTO> and converts to Map
    Map<String, ProductDTO> productDTOs = productRepo.getProductDTOs();
    // 2. Get stock info - receives List<StockDTO> and converts to Map
    Map<String, StockDTO> stockDTOMap = stockRepo.getStockDTOs();

    // 3. Combine the data from DTOs into the Shopfront's internal Product model
    return productDTOs.values().stream()
            .map(productDTO -> {
                // Find the matching StockDTO using the product ID
                StockDTO stockDTO = stockDTOMap.get(productDTO.getId());
                // Create the final Shopfront.model.Product object
                return new uk.co.danielbryant.djshopping.shopfront.model.Product(
                    productDTO.getId(),
                    stockDTO != null ? stockDTO.getSku() : "N/A", // Use data from StockDTO
                    productDTO.getName(), // Use data from ProductDTO
                    productDTO.getDescription(), // Use data from ProductDTO
                    productDTO.getPrice(), // Use data from ProductDTO
                    stockDTO != null ? stockDTO.getAmountAvailable() : 0 // Use data from StockDTO
                );
            })
            .collect(Collectors.toList()); // Return a List of Shopfront.model.Product
}

This code clearly shows the ProductService working with ProductDTO and StockDTO objects that it received from the repositories. It then uses the data within these DTOs (productDTO.getName(), stockDTO.getAmountAvailable(), etc.) to construct the Shopfront's own internal Product model (uk.co.danielbryant.djshopping.shopfront.model.Product).

Shopfront's Internal Product Model

The Shopfront has its own Product class. This is the model it uses internally to hold the combined data it needs to display on the web page.

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

import java.math.BigDecimal;

public class Product {
    private String id; // From ProductCatalogue
    private String sku; // From StockManager
    private String name; // From ProductCatalogue
    private String description; // From ProductCatalogue
    private BigDecimal price; // From ProductCatalogue
    private int amountAvailable; // From StockManager

    // Constructor and getter methods including all fields ...
}

Notice this Product model in the Shopfront contains fields (name, description, price) that came from the ProductDTO (originally from Product Catalogue's Product) and fields (sku, amountAvailable) that came from the StockDTO (originally from Stock Manager's Stock). This is the final structure the Shopfront uses before passing the data to the web page template for display.

Analogy: Forms and Reports

Let's use the office analogy again:

  • Internal Data Model: This is how a department stores its information in its own filing cabinets or database. The Marketing Department has full product specs (their Product model). The Warehouse has detailed inventory records (their Stock model).
  • DTO: This is a standardized report or form.
    • When the Marketing Department is asked for product details, they fill out a "Product Detail Report" (a ProductDTO). This report contains only the fields requested (name, description, price), not everything from their internal files.
    • When the Warehouse is asked for stock levels, they fill out an "Inventory Status Form" (a StockDTO). This form contains only the fields requested (product ID, quantity), not their full internal inventory records.
  • Transfer (API Call): These forms (DTOs) are put into inter-office envelopes (HTTP requests/responses) and sent between departments.
  • Receiving & Combining: The Sales Department (Shopfront) receives the "Product Detail Reports" (List of ProductDTOs) and the "Inventory Status Forms" (List of StockDTOs). They then take the information from these forms and use it to fill out their own internal "Customer-Ready Product Sheet" (the Shopfront's internal Product model), which contains all the info needed for the customer (name, price, and stock).

Summary

In this chapter, we clarified the important concepts of Data Models and DTOs in our microservices project:

  • Data Models represent the structure of data within a specific service, often tied to how it's stored or processed internally (like the Product Catalogue's Product or the Stock Manager's Stock).
  • DTOs (Data Transfer Objects) represent the structure of data specifically for transferring information between services via APIs (like the Shopfront's ProductDTO and StockDTO). Using DTOs helps keep services independent and define clear API contracts.

We saw how the Shopfront service defines ProductDTO and StockDTO classes to match the JSON data structure it expects from the other services' APIs. Its ProductService uses these DTOs to receive the data, then combines information from the different DTOs to create the Shopfront's own internal Product model, which is finally used to display the complete product information to the user.

Understanding these data structures is key to building applications where different parts need to share information in a standardized way.

Now that we have covered the basic structure and communication of our three microservices, the next crucial step is to understand how we package each of these independent services so they can be reliably run anywhere. This leads us to Docker.

Next Chapter: Docker Container Packaging


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