What is a Microservice?
Instead of building one big app that does everything, you split it into small independent services. Each service does one job and runs separately.
I have two services:
- product-service - manages products (port 8081)
- order-service - manages orders (port 8082)
These two services live in separate Spring Boot apps with separate databases. But when a customer places an order, order-service needs to know about the product — so they need to talk to each other.
The Problem
Order-service doesn't have product data. It only knows the productId from the request. So before saving an order it needs to ask product-service:
"Hey, does this product exist? How much stock do you have?"
The Solution - WebClient
WebClient is Spring's HTTP client that lets one service call another service's REST API.
Step 1 - Configure WebClient as a Bean
@Configuration
public class WebClientConfig {
@Bean
public WebClient productWebClient() {
return WebClient.builder()
.baseUrl("http://localhost:8081/api/products")
.build();
}
}
You set the base URL once here. Now anywhere you inject productWebClient it already knows where product-service lives. Notice the bean is named productWebClient — this matches the field name in the service so Spring knows which WebClient to inject.
Step 2 - Inject and Use It in OrderService
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final OrderMapper orderMapper;
private final WebClient productWebClient; // injected from config
@RequiredArgsConstructor from Lombok automatically creates the constructor and injects all final fields — including productWebClient.
Step 3 - Call product-service to fetch product
ProductResponse product = productWebClient.get()
.uri(uriBuilder -> uriBuilder.path("/{id}").build(itemId))
.retrieve()
.bodyToMono(ProductResponse.class)
.block();
Breaking this down line by line:
productWebClient.get()
Make a GET request. Combined with baseUrl it calls GET http://localhost:8081/api/products/{id}
.uri(uriBuilder -> uriBuilder.path("/{id}").build(itemId))
Append the product id to the URL path safely. itemId replaces {id}.
.retrieve()
Actually send the request and get the response.
.bodyToMono(ProductResponse.class)
Convert the JSON response body into a ProductResponse object. Mono means "a single value coming in the future" — WebClient is async by nature.
.block()
Wait for the result synchronously. Since we need the product data before continuing, we block here.
Step 4 - Use the product data to build the order
// check if enough stock
if (product.getStockQuantity() < orderRequest.getQuantity()) {
throw new Exception("Insufficient stock Available: " + product.getStockQuantity());
}
// calculate total price from product price × quantity
BigDecimal totalPrice = product.getPrice()
.multiply(BigDecimal.valueOf(orderRequest.getQuantity()));
// save order
Order order = orderMapper.toEntity(orderRequest);
order.setTotalPrice(totalPrice);
order.setStatus(Order.OrderStatus.PENDING);
order.setCreatedAt(LocalDateTime.now());
order.setUpdatedAt(LocalDateTime.now());
return orderMapper.toResponse(orderRepository.save(order));
The totalPrice is calculated automatically from product-service data — the client only sends productId and quantity. Everything else comes from the inter-service call.
The Full Flow Visualized
Client (Postman)
|
| POST /api/orders
↓
order-service (8082)
|
| GET /api/products/{id} ← WebClient call
↓
product-service (8081)
|
| returns ProductResponse (name, price, stock)
↓
order-service
|
| checks stock, calculates price, saves order
↓
returns OrderResponse to client ✅
About the ENUM — @JdbcTypeCode(SqlTypes.NAMED_ENUM)
PostgreSQL supports custom ENUM types. In your migration you created:
CREATE TYPE order_status AS ENUM ('PENDING', 'CONFIRMED', 'CANCELLED');
By default Hibernate saves enums as plain strings, but PostgreSQL's custom ENUM type is stricter, it refuses plain strings. The annotation tells Hibernate to treat this field as a PostgreSQL named ENUM type so they speak the same language:
@Enumerated(EnumType.STRING)
@JdbcTypeCode(SqlTypes.NAMED_ENUM)
private OrderStatus status;
Key Things to Remember
WebClient needs a base URL - set it once in config, reuse everywhere.
.block() makes it synchronous - we need the product data before saving, so we wait.
Services only share DTOs - order-service doesn't import product-service's entity or repository, only its ProductResponse DTO. Each service owns its own data.
Each service has its own database - order-service never directly queries product-service's database. It always goes through the REST API.
Top comments (0)