By Nobukimi Sasaki (2023-04-23)
Introduction:
This is my study note composed of three services (Spring Boot) with a message broker (kafka).
There services are independent to each other and communicating asynchronously.
Entire project structure:
There are four Spring Boots project under one folder
Service A: order-service
Whenever receiving the HTTP request, the order-service create a event and publish the event to the message broker (Kafka).
Service B (stock-service) & C (email-service)
Just consume the message from the message broker (Kafka) and Service B & C don’t know about Service A (order-service). They are all independent to each other.
Spring Boot for DTO object (base-domains)
The DTO classes for the message that being referred from Service A, B, and C.
Service A – order-service detail:
1. OrderController::placeOrder receives the request from HTTP client
2. OrderController::placeOrder::sendMessage calls Kakfa Producer
3. OrderProducer::sendMessage calls KafkaTemplate::send
spring.kafka.producer.bootstrap-servers: localhost:9092
spring.kafka.producer.key-serializer: org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
spring.kafka.topic.name=order_topics
At Service A, this property configures a Kafka Server (line 1), and topic name (line 4)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>net.javaguides</groupId>
<artifactId>base-domains</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
At Service A, B, and C, the dependency is Kakfa and DTO project (base-domains)
import net.javaguides.basedomains.dto.Order;
import net.javaguides.basedomains.dto.OrderEvent;
import net.javaguides.orderservice.kafka.OrderProducer;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@RequestMapping("/api/v1")
public class OrderController {
private OrderProducer orderProducer;
public OrderController(OrderProducer orderProducer) {
this.orderProducer = orderProducer;
}
// Rest End Point API for testing
@PostMapping("/orders")
public String placeOrder(@RequestBody Order order){
order.setOrderId(UUID.randomUUID().toString());
OrderEvent orderEvent = new OrderEvent();
orderEvent.setStatus("PENDING");
orderEvent.setMessage("order status is in pending state");
orderEvent.setOrder(order);
orderProducer.sendMessage(orderEvent);
return "Order placed successfully ...";
}
}
At Service A, OrderController::placeOrder receives the request from HTTP client, and sendMessage calls Kakfa Producer
import net.javaguides.basedomains.dto.OrderEvent;
import org.apache.kafka.clients.admin.NewTopic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
@Service
public class OrderProducer {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderProducer.class);
private NewTopic topic;
private KafkaTemplate<String, OrderEvent> kafkaTemplate;
// Parameter constructor in this class
public OrderProducer(NewTopic topic, KafkaTemplate<String, OrderEvent> kafkaTemplate) {
this.topic = topic;
this.kafkaTemplate = kafkaTemplate;
}
public void sendMessage(OrderEvent event){
LOGGER.info(String.format("Order event => %s", event.toString()));
// create Message
Message<OrderEvent> message = MessageBuilder
.withPayload(event)
.setHeader(KafkaHeaders.TOPIC, topic.name())
.build();
kafkaTemplate.send(message);
}
}
At Service A, OrderProducer::sendMessage calls KafkaTemplate::send
Service B (stock-service) and C (email-service) Service detail:
Service B (stock-service) and C (email-service) has kafka consumer to receive the message.
server.port=8081
spring.kafka.consumer.bootstrap-servers: localhost:9092
spring.kafka.consumer.group-id: stock
spring.kafka.consumer.auto-offset-reset: earliest
spring.kafka.consumer.key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.trusted.packages=*
spring.kafka.topic.name=order_topics
At Service B (stock-service), Line 1 – the port is 8081, and group id is “stock”
server.port=8082
spring.kafka.consumer.bootstrap-servers: localhost:9092
spring.kafka.consumer.group-id: email
spring.kafka.consumer.auto-offset-reset: earliest
spring.kafka.consumer.key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.trusted.packages=*
spring.kafka.topic.name=order_topics
At Service C (email-service), Line 1 – the port is 8082, and group id is “email”
Both Service B & C, Line 2 – register the kafka server port 9092, and line 8, the topic name of “order_topics”
import net.javaguides.basedomains.dto.OrderEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
@Service
public class OrderConsumer {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderConsumer.class);
@KafkaListener(
topics = "${spring.kafka.topic.name}"
,groupId = "${spring.kafka.consumer.group-id}"
)
public void consume(OrderEvent event){
LOGGER.info(String.format("Order event received in stock service => %s", event.toString()));
// business logic
}
}
Both Service B & C have this consumer class.
DTO object (base-domains) Details:
This DTO classes are referenced from Service A, B, and C
Example of RESTFul client