From 5222716b5f4cd88cab4ea50b58f51483a4f16747 Mon Sep 17 00:00:00 2001 From: Waluya Date: Mon, 16 May 2022 20:14:04 +0200 Subject: [PATCH 01/10] added initial version for solution communication with kafka --- solutions/communication_kafka/README.md | 53 ++++++++++++++++++++ solutions/communication_kafka/index.asciidoc | 0 2 files changed, 53 insertions(+) create mode 100644 solutions/communication_kafka/README.md create mode 100644 solutions/communication_kafka/index.asciidoc diff --git a/solutions/communication_kafka/README.md b/solutions/communication_kafka/README.md new file mode 100644 index 000000000..1f068bde8 --- /dev/null +++ b/solutions/communication_kafka/README.md @@ -0,0 +1,53 @@ +### Table of Contents +## Abstract +A short summary (less than 100 words) that describe the core elements of the problem and its solution. + +What is solved (Short problemstatement) +Which environment +What tools are used +Why should you use this solution? What are the key benefits? + +## Introduction and goals + +Goal: The application should show how spring-kafka can be implemented within a sample application to leverage Apache Kafka's functionalities. + +## Context and scope +Everything that relates to the design decision of the solution + +Non-functional requirements that are defined and that lead to this solution + +Given tools that are set and cannot be changed: Spring-kafka, Spring-boot, Apache Kafka + +This application is not depended on the cloud vendor and the cloud strategy... + +Technology stack (when of interest for the solution) +Java, Postman, Lombok, Docker, JUnit + +## Solution Strategy +https://docs.arc42.org/section-4/ + +Describe the solution. +technology decisions +decisions about the top-level decomposition of the system, e.g. usage of an architectural pattern or design pattern +decisions on how to achieve key quality goals +relevant organizational decisions, e.g. selecting a development process or delegating certain tasks to third parties. +Use one or more images to give an overview of the architecture of the solution +What tools are used and how do they work together +Highlight in a short sentence the use of each tool +Use links to the tools and description. Do not describe each tool with your own words. + +## Constraints and Alternatives +What are the constraints of this solution +What points need an architect to have a look on +Where are the limits? +What alternatives exist on the marked. Why have they not been considered. <- Only links and a short sentence. This is not the main focus! + +## Concrete Steps to create the solution + + +# Kafka + +This sample application demonstrates a ship booking service. Customers can book ships and multiple customers can book the same ships. The bookings can then be confirmed. Bookings can be cancelled by shipDamagedEvents that can be sent and received through kafka. The user is also able to see the booking status (PENDING, CONFIRMED, CANCELLED). + +The usage of this application can be visualized through the following use case: +shipDamagedEvents will be sent to the topic "shipDamagedTopic" defined with spring-kafka to the kafka message broker. Application receives the shipDamagedEvents through the kafka message broker. This changes the booking status of the bookings with the damaged ship defined by shipDamagedEvents to "CANCELLED" \ No newline at end of file diff --git a/solutions/communication_kafka/index.asciidoc b/solutions/communication_kafka/index.asciidoc new file mode 100644 index 000000000..e69de29bb From b34bd07a40e4678c4fd29ec20551258682e2b735 Mon Sep 17 00:00:00 2001 From: Waluya Date: Mon, 16 May 2022 20:41:47 +0200 Subject: [PATCH 02/10] added initial sample application description --- solutions/communication_kafka/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solutions/communication_kafka/README.md b/solutions/communication_kafka/README.md index 1f068bde8..e0c86f6dc 100644 --- a/solutions/communication_kafka/README.md +++ b/solutions/communication_kafka/README.md @@ -50,4 +50,4 @@ What alternatives exist on the marked. Why have they not been considered. <- Onl This sample application demonstrates a ship booking service. Customers can book ships and multiple customers can book the same ships. The bookings can then be confirmed. Bookings can be cancelled by shipDamagedEvents that can be sent and received through kafka. The user is also able to see the booking status (PENDING, CONFIRMED, CANCELLED). The usage of this application can be visualized through the following use case: -shipDamagedEvents will be sent to the topic "shipDamagedTopic" defined with spring-kafka to the kafka message broker. Application receives the shipDamagedEvents through the kafka message broker. This changes the booking status of the bookings with the damaged ship defined by shipDamagedEvents to "CANCELLED" \ No newline at end of file +shipDamagedEvents will be sent to the topic "shipDamagedTopic" defined with spring-kafka to the kafka message broker. Application receives the shipDamagedEvents through the kafka message broker. This changes the booking status of the bookings with the damaged ship defined by shipDamagedEvents to "CANCELLED". \ No newline at end of file From ed8bcbc1b2448970c7ff6fbbdbcdb4f09abbabff Mon Sep 17 00:00:00 2001 From: Waluya Date: Wed, 18 May 2022 10:17:23 +0200 Subject: [PATCH 03/10] added explanation regarding the updated sample application architecture --- solutions/communication_kafka/README.md | 9 ++++++++- solutions/communication_kafka/architecture.png | Bin 0 -> 94101 bytes 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 solutions/communication_kafka/architecture.png diff --git a/solutions/communication_kafka/README.md b/solutions/communication_kafka/README.md index e0c86f6dc..03db8b5b1 100644 --- a/solutions/communication_kafka/README.md +++ b/solutions/communication_kafka/README.md @@ -50,4 +50,11 @@ What alternatives exist on the marked. Why have they not been considered. <- Onl This sample application demonstrates a ship booking service. Customers can book ships and multiple customers can book the same ships. The bookings can then be confirmed. Bookings can be cancelled by shipDamagedEvents that can be sent and received through kafka. The user is also able to see the booking status (PENDING, CONFIRMED, CANCELLED). The usage of this application can be visualized through the following use case: -shipDamagedEvents will be sent to the topic "shipDamagedTopic" defined with spring-kafka to the kafka message broker. Application receives the shipDamagedEvents through the kafka message broker. This changes the booking status of the bookings with the damaged ship defined by shipDamagedEvents to "CANCELLED". \ No newline at end of file + +shipDamagedEvents will be sent to the topic "shipDamagedTopic" defined with spring-kafka to the kafka message broker. Application receives the shipDamagedEvents through the kafka message broker. This changes the booking status of the bookings with the damaged ship defined by shipDamagedEvents to "CANCELLED". + +![alt text](architecture.png "architecture diagram") + +The bookingService has two entities. The first one is "Customer" and the other one is "Booking". A customer can create bookings for ships and some of their containers. The bookings are saved in form of a list inside the Customer entity. + +When the booking is created for the first time, the booking status will be set to PENDING. Immediately after the creation, the booking will be sent to the booking topic. The shipService listens on the booking topic, ready to consume the incoming booking. The consummed booking will be checked for the available container. If the ship has enough available containers, the ship service reduces the available containers of the ship and sends the confirmation to the ship-booking topic. In the other case, if the ship doesn't have enough available containers, then the ship service also sends the rejection to the ship-booking topic. The bookingService consumes these messages and at the end, the booking status gets changed to the appropriate booking status (CONFIRMED or DENIED). diff --git a/solutions/communication_kafka/architecture.png b/solutions/communication_kafka/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..fab479e611595a62321bffba377d070fbc72c067 GIT binary patch literal 94101 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 literal 0 HcmV?d00001 From ccb07e6f2dd51ba6211fbf52f6be37344db28884 Mon Sep 17 00:00:00 2001 From: Waluya Date: Mon, 13 Jun 2022 23:35:48 +0200 Subject: [PATCH 04/10] updated solution --- solutions/communication_kafka/README.md | 441 +++++++++++++++++++++--- 1 file changed, 402 insertions(+), 39 deletions(-) diff --git a/solutions/communication_kafka/README.md b/solutions/communication_kafka/README.md index 03db8b5b1..6be56bc28 100644 --- a/solutions/communication_kafka/README.md +++ b/solutions/communication_kafka/README.md @@ -1,60 +1,423 @@ ### Table of Contents -## Abstract -A short summary (less than 100 words) that describe the core elements of the problem and its solution. -What is solved (Short problemstatement) -Which environment -What tools are used -Why should you use this solution? What are the key benefits? +# Abstract +The goal of documentation is to present the Spring Kafka functionalities in the context of microservices. The main focus would be how Kafka-based messaging solutions are implemented with Spring Boot. Furthermore, the documentation also provides information regarding the implementation of logging, tracing, and retry within an application. -## Introduction and goals +Spring Kafka, Docker, and Postman are the tools being used during the creation of this document. To run both Kafka message broker and Zookeeper, Docker image is used. For the app testing, we use Postman by sending already defined http requests to examine the expected outputs. -Goal: The application should show how spring-kafka can be implemented within a sample application to leverage Apache Kafka's functionalities. +# Introduction and Goals +The application should show how spring-kafka can be implemented within a sample application to leverage Apache Kafka's functionalities. -## Context and scope -Everything that relates to the design decision of the solution +The primary use case of this documentation is to explain the implementation of message transfer process based on Kafka (Produce-Consume Topics). -Non-functional requirements that are defined and that lead to this solution +1. Kafka Message Broker (Produce-Consume Topics) + Kafka is a message broker in part because it allows several consumers to perform different logic focused on a single message. A message broker allows services and applications to exchange messages with one another. The structure of the messages is explicitly specified and independent of the services that send them. -Given tools that are set and cannot be changed: Spring-kafka, Spring-boot, Apache Kafka + The Producer is in charge of sending messages through a linked application to the message broker. In the meanwhile, the consumer consumes the messages that have been queued in the message broker. Messages are stored in a queue/topic by the message broker. + -This application is not depended on the cloud vendor and the cloud strategy... +2. Streaming + The Kafka Streams API is a component of the Apache Kafka open-source data transformation and enrichment project. Kafka Streams is most commonly used to create real-time streaming data pipelines and applications that react to the data streams. It uses a combination of messaging, storage, and stream processing to store and analyze both historical and real-time data. -Technology stack (when of interest for the solution) -Java, Postman, Lombok, Docker, JUnit + As previously stated, Kafka Streams provides an API for developing client applications that alter data in Apache Kafka and then publish the modified data onto a new topic. The client application is where the data is processed. + + +3. Temporary/Permanent Data Storing + Kafka is a streaming data system that may hold data for a period of time before eliminating it. Meaning, Kafka does not discard messages as soon as the consumer reads them. + + Kafka may also store data on permanent storage and replicate data among brokers in a cluster, giving it the aspect of a database. Kafka's data can be stored indefinitely by the user. -## Solution Strategy -https://docs.arc42.org/section-4/ +4. Logging + Apache Kafka Logs are a collection of numerous data segments stored on disk that are referred to as form-topic partitions or specific topic-partitions. Each Kafka log represents a logical unique topic-based partitioning. + + Logging with Apache Kafka makes the transformation of unstructured logging streams into an analyzable and understandable output possible. The output can then be used for detecting potential and ongoing problems as well as better environment monitoring. -Describe the solution. -technology decisions -decisions about the top-level decomposition of the system, e.g. usage of an architectural pattern or design pattern -decisions on how to achieve key quality goals -relevant organizational decisions, e.g. selecting a development process or delegating certain tasks to third parties. -Use one or more images to give an overview of the architecture of the solution -What tools are used and how do they work together -Highlight in a short sentence the use of each tool -Use links to the tools and description. Do not describe each tool with your own words. +5. Retry + Failures in a distributed system may happen, i.e. failed message process, network errors, runtime exceptions. Therefore, the retry logic implementation is something essential to have. -## Constraints and Alternatives -What are the constraints of this solution -What points need an architect to have a look on -Where are the limits? -What alternatives exist on the marked. Why have they not been considered. <- Only links and a short sentence. This is not the main focus! + It is important to note that Retries in Kafka can be quickly implemented at the consumer side. This is known as Simple Blocking Retries. To accomplish visible error handling without causing real-time disruption, Non-Bloking Retries and Dead Letter Topics are implemented. + +6. Data Monitoring (Metrics) + Metrics are measurements that capture a value about the systems at a specific point in time, such as (provide a use case as an example from sample application). They are often collected every second, minute, or other regular interval to track a system's performance over time. + + Kafka Monitoring Metrics measure how well a component, for example network latency, performs its role. It's vital to keep an eye on the health of Kafka deployment to ensure that the applications that rely on it keep working. + +7. Event Sourcing + Event sourcing is a style of application design where state changes are logged as a time-ordered sequence of records. Kafka's support for very large stored log data makes it an excellent backend for an application built in this style. + + +8. Tracking (Activity Tracking) + With activity tracking it is possible to track page views, searches, uploads or other actions users may take. When an event happens in the blog, for example a user logs in, an information about the event and a tracking event and will be placed into a record. The record will be placed on a specified Kafka topic. -## Concrete Steps to create the solution +Kafka-clients have many implementations in different languages. However, this document focuses on Spring Kafka using Java. +The following are the advantages and (or) disadvantages of Kafka. + -# Kafka +1. Streaming -This sample application demonstrates a ship booking service. Customers can book ships and multiple customers can book the same ships. The bookings can then be confirmed. Bookings can be cancelled by shipDamagedEvents that can be sent and received through kafka. The user is also able to see the booking status (PENDING, CONFIRMED, CANCELLED). + Advantages + - Transferring or filtering data between topics is easier + - Events can be sent to external systems + - Allows data parallelism, distributed coordination, fault + tolerance, scalability + - supports the creation of lightweight and small microservices + - it's flexible, scalable and fault-tolerant + - Data operations might be stateless, stateful or windowed. + - non-buffering system => streams are processed one at a time + -The usage of this application can be visualized through the following use case: +2. Logging -shipDamagedEvents will be sent to the topic "shipDamagedTopic" defined with spring-kafka to the kafka message broker. Application receives the shipDamagedEvents through the kafka message broker. This changes the booking status of the bookings with the damaged ship defined by shipDamagedEvents to "CANCELLED". + Advantages + - supports many integrations + - a clearly readable data set + - simple Implementation + - provides organizations access to a large number of tools -![alt text](architecture.png "architecture diagram") + Disadvantages + - Log messages are maintained for a certain amount of time before + deleting them + - During a spike, events can be deleted or lost without a user's + knowledge while they are timed out + - lack of scalability within a large enterprise environment + - An expanding company will have to increase the number pf resources on + servers, as well as process more logs. -The bookingService has two entities. The first one is "Customer" and the other one is "Booking". A customer can create bookings for ships and some of their containers. The bookings are saved in form of a list inside the Customer entity. +3. Message Broker -When the booking is created for the first time, the booking status will be set to PENDING. Immediately after the creation, the booking will be sent to the booking topic. The shipService listens on the booking topic, ready to consume the incoming booking. The consummed booking will be checked for the available container. If the ship has enough available containers, the ship service reduces the available containers of the ship and sends the confirmation to the ship-booking topic. In the other case, if the ship doesn't have enough available containers, then the ship service also sends the rejection to the ship-booking topic. The bookingService consumes these messages and at the end, the booking status gets changed to the appropriate booking status (CONFIRMED or DENIED). + Advantages + - Enables communication between several services that may not be + executing at the same time. + - Asynchronous processing improves system performance. + - Ensuring that messages are delivered reliably + + Disadvantages + - Increasing the complexity of the system + - Debugging can be difficult + - Learning takes time + + +4. Retry + + Advantages: + - Real time traffic is not disrupted (Batch processing can be unblocked) + - Error handling is observable (metadata can be obtained on the retries) + - Higher real-time throughput can be achieved + + Disadvantage: + - Kafka's ordering guarantees for the topic on which non-blocking retries is implemented is no longer applied. + - The batch processing is blocked, as failed messages are repeatedly processed in real time (they are re-consumed again and again). + +5. Data Monitoring (Metrics) + + Advantages: + Assist in identifying potential bottlenecks and performance concerns before they cause major issues + + Disadvantages: + - Kafka lacks a full set of management and monitoring capabilities. This makes enterprise support staff wary about implementing kafka and maintaining it in the long run. + - The first challenge above can be overcome by installing third-party, open-source, or commercial graphical tools that offer administrative and monitoring features. + +6. Event Sourcing + + Advantages: + - Kafka can be use as a natural backbone for Event Sourcing + Disadvantages: + + +7. Tracking (Activity Tracking) + + Advantages: - + + + Disadvantages: + - often requires a very high volume of throughput + + +8. Temporary/Permanent Data Storing + + Advantages: + - can be easily integrated with databases and cloud data lake storage + - quite easy to understand + - helps to avoid copying the full dump of one database to another + + + Disadvantages: + - doesn’t provide arbitrary access lookup to the data + - there isn’t a query language like SQL available to help you access data + - it is not a replacement for databases + +# Context and Scope (in progress...) + +# Solution Strategy + +# Apache Kafka +Apache Kafka was built to make real-time data available to all the applications that need to use it. + +## Use cases + +### Publish-Subscribe Messaging + +Kafka can be used as a distributed pub/sub messaging system that replaces traditional message brokers like ActiveMQ and RabbitMQ. + +### Streams + +Kafka can be used to perform real-time computations on event streams. + +### Metrics and Monitoring + +Kafka can be used to aggregate statistics from distributed applications to produce centralized feeds with real-time metrics. + +### Log Aggregation + +Kafka can be used as a single source of truth by centralizing logging data across all sources in a distributed system. + +## Kafka Concepts + +### Kafka and Events + +An event is any type of action, incident, or change that's identified or recorded by software or applications. Kafka models events as key/value pairs. Keys are often primitive types like strings or integers. Values are serialized representations of an application domain object or a raw message. + +### Kafka Topics + +A topic is a log of events. They are append only: New messages always go to the end of a log. The logs are immutable - once something has happened, it is difficult to make it un-happen. +Traditional messaging systems have topics and queues, which store messages temporarily for a short time. The advantage of Kafka topics is, that every topic can be configured to expire data after it reaches a certain age, that means the data can be stored for as short as seconds to indefinitely. The logs are files stored on disk. + +### Kafka Partitioning + +Partitioning takes the single topic log and breaks in into multiple logs, each of which can live on a separate node in the Kafka cluster. This makes Kafka a distributed system and helps it scale. Kafka messages can be given a key, the key guarantees that all the events from a given entity will always arrive in order. Without a key, the messages will be distributed among all the topic's partitions and any kind of ordering will be ignored. + +### Kafka Brokers + +Kafka is composed of a network of containers or machines called brokers, each running the Kafka broker process. Each broker hosts some set of partitions and handles incoming requests to write new events to those partitions or read events from them. Brokers also handle replication of partitions between each other. + +### Replication + +Partition data is copied automatically from a leader broker to follower brokers in the network. If one node in the cluster dies, another will take its role. This helps availability of data. + +### Kafka Producers and Consumers + +Producers connect and send key/value pairs to specific topic on the cluster. Consumers can then connect to the cluster and subscribe to one or more topics, listening and consuming key/value pairs of Kafka messages. Consuming a message does not destroy it, it can still be read by any other consumer. This differentiates Kafka from other traditional message brokers. Many consumers can read from one topic. Applications can be both consumers and producers. + +#### Source: https://developer.confluent.io/what-is-apache-kafka/ + +# spring-kafka + +The focus of this documentation is to demonstrate a Kafka-based messaging solution with the use of spring-kafka. + +>The Spring for Apache Kafka (spring-kafka) project applies core Spring concepts to the development of Kafka-based messaging solutions. It provides a "template" as a high-level abstraction for sending messages. +> -- https://spring.io/projects/spring-kafka + +To use `spring-kafka` in a project, add the following dependency to the `pom.xml`: + +``` + + org.springframework.kafka + spring-kafka + +``` + +For a gradle project add the following line to the dependencies in the `build.gradle` file: +``` +implementation 'org.springframework.kafka:spring-kafka' +``` + + +## Example Application + +An example application was written to demonstrate the presented concepts and functionalities. + +### Architecture + +The example application consists of two services that communicate through Kafka topics. + +The `booking-service` sends newly created/requested bookings to the Kafka `bookings` topic. The `ship-service` listens for incoming events (new bookings). After the `ship-service` receives a booking, it verifies if it is possible to execute the booking. The condition would be that if there are enough containers on the requested ship. If it is, then the booking gets confirmed. Otherwise, it gets rejected. The response, which is visualized as `bookingStatus`, is then sent back to the `bookings` topic and the `booking-service` listen to this topic. Only when the response is consumed, the `bookingStatus` gets updated. + +![](https://i.imgur.com/Bb8c5l1.png) + + +There are 3 possible outcomes, which are visualized as `bookingStatus`: `REQUESTED`, `CONFIRMED`, `CANCELED`. The `bookingStatus` of newly requested bookings, which are sent from `booking-service` to `bookings`, is `REQUESTED`. The booking of a ship with enough available containers will result in `CONFIRMED`. + +However, there might be some cases when a booking will not be confirmed. If a ship with not enough container is booked, the booking is not possible and hence gives `CANCELED` in the `bookingStatus`. Furthermore, when an already booked ship is damaged, the `ship-service` will then send this new response to Kafka `ship-damaged` topic. Afterwards, the `bookingStatus` is updated to `CANCELED`. + +### Implementation +#### Prerequisites +The following are the necessary tech stacks that are needed to be installed before running a spring kafka application: +- Apache Kafka +- Zookeeper +**OR** +- Docker images (container that includes both Kafka Message Broker and Zookeeper) + + +(**TODO: further elaboration is required**). + +#### Topics Creation + +The `booking-service` automatically creates the necessary topics on application startup by defining beans as followed: + +``` +@Bean +public NewTopic bookings() { + return TopicBuilder.name("bookings") + .partitions(2) + .compact() + .build(); +} +``` + +This creates the `bookings` topic with two partitions. The method `compact()` enables log compaction on the topic. A topic with log compaction removes any old records when there is a more recent update with the same primary key. This is optional and needs to be decided on a case-by-case basis. + +The creation of all necessary topics can be found in class `BookingComponentMessagingGateway`, in which the *Messaging Gateway* pattern is implemented. The class basically isolates messaging-specific functionalities (necessary codes to send or receive a message) from the rest of the application code. Hence, only the Messaging Gate code is aware of the messaging system. + +Within the class, another topic called `shipDamagedTopic` is defined and used to fetch messages whenever the `ship-service` informs the `booking-service` that the booked ship is currently damaged. + +``` +@Bean + public NewTopic shipDamagedTopic(){ + return TopicBuilder.name("shipDamagedTopic").build(); + } +``` + +The `booking-service` receives events from the `ship-service`. Every booking event contains an `id`, which was set by the `booking-service`. + + +#### Add new booking (Sending messages): + +After topics are created with kafka, the subsequent step is to send messages/data to it. `spring-kafka` simplifies sending messages to topics with the class `KafkaTemplate`, which provides methods for sending messages to Kafka topics in a feasible way. + +``` +private final KafkaTemplate longTemplate; +public void sendMessage(String topic, Long key, T message) { + LOG.info("Sending message : {}", message.toString()); + longTemplate.send(topic, key, message); + } +``` +The `send` API will send the data to the provided topic with the provided key and no partition. It returns a `ListenableFuture` object after a send. The `sendMessage` function is **for example implemented for adding a new booking**, which can be seen in class `BookingComponentBusinessLogic`. The new booking with its own associated id (which is retrieved using `getId()`) and the number of requested containers given by the customer is sent to `bookings` topic with message `booking`. + +``` +@Transactional(rollbackFor = {CustomerNotFoundException.class}) + public Booking addBooking(Long customerId, BookingCreateDTO bookingCreateDTO) throws CustomerNotFoundException{ + Optional optionalCustomer = customerRepository.findById(customerId); + + if (optionalCustomer.isPresent()) { + Customer customer = optionalCustomer.get(); + + //Booking booking = bookingRepository.save(Booking.of(bookingCreateDTO)); + Booking booking = bookingRepository.save(new Booking(bookingCreateDTO.getShipId(), bookingCreateDTO.getContainerCount())); + + customer.addBooking(booking); + customerRepository.save(customer); + + bookingComponentMessagingGateway.sendMessage("bookings", booking.getId(), booking); + + + return booking; + } else { + throw new CustomerNotFoundException(customerId); + } + } +``` +It is important to note that the status of the newly created booking is `REQUESTED`. The status of the bookings is managed by `BookingStatus`, which is an enum. The other constants within this enum are `CONFIRMED` and `CANCELED`. + +#### onBookingEvent, listenBooking (Receiving messages): +The booking is then consumed via `bookings` topic by `ship-service` and will be checked, if that booking already existed in the system or the ship that would be booked also exists. The process can be found in class `ShipRestController`. +``` +@KafkaListener(id = "bookings", topics = "bookings", groupId = "ship") + public void onBookingEvent(Booking booking) throws ShipNotFoundException, BookingAlreadyConfirmedException { + LOG.info("Received: {}", booking); + shipComponentLogic.confirmBooking(booking); + } +``` +The booking can be confirmed through `confirmBooking` function, which can be found in `ShipComponentLogic` class. Firstly, it will check if the status of this newly created booking is already confirmed or not. If it is, then `BookingAlreadyConfirmedException` will be thrown. If it's newly created, the ship that'll be booked is checked whether there's enough available container or not. If there is not enough available containers, the booking is cancelled (`BookingStatus.CANCELED`). Otherwise, it will be confirmed (`BookingStatus.CONFIRMED`) and this confirmation is afterwards sent to the `ship-bookings` topic. +``` +@Transactional(rollbackFor = {BookingAlreadyConfirmedException.class}) + public void confirmBooking(Booking booking) throws BookingAlreadyConfirmedException, ShipNotFoundException { + Ship ship = shipRepository.findById(booking.getShipId()).orElseThrow(() -> new ShipNotFoundException(booking.getShipId())); + LOG.info("Found: {}", ship); + + if (booking.getBookingStatus() == BookingStatus.CONFIRMED) { + throw new BookingAlreadyConfirmedException(booking.getId()); + } else if (booking.getBookingStatus().equals(BookingStatus.REQUESTED)) { + if(booking.getContainerCount() < ship.getAvailableContainers()){ + ship.setAvailableContainers(ship.getAvailableContainers() - booking.getContainerCount()); + booking.updateBookingStatus(BookingStatus.CONFIRMED); + shipRepository.save(ship); + } else { + booking.updateBookingStatus(BookingStatus.CANCELED); + } + } + + template.send("ship-bookings", booking.getId(), booking); + LOG.info("Sent: {}", booking); + } +``` + +The confirmation is finally consumed by `booking-service`, in which the booking is processed, hence updating the booking status to `CONFIRMED`. +``` +@KafkaListener(id ="ship-bookings", topics = "ship-bookings", groupId = "booking") + public void listenBooking(Booking booking) throws BookingNotFoundException { + LOG.info("Received message: {}", booking.toString()); + bookingComponentBusinessLogic.processBooking(booking); + } +``` + +#### Logging +As logging tool, SLF4J (Simple Logging Facade for Java) is used. It is an abstraction layer for different Java logging frameworks, such as Log4j2 or logback. + +The general pattern (common solution) for accessing loggers by defining logger as static final instance is no longer recommended, as well as defining logger as instance variable (as slf4j.org used to recommend). For example, in `ShipComponentLogic`, the log is declared as static final instance as following: + +``` +private static final Logger LOG = LoggerFactory.getLogger(ShipComponentLogic.class); +``` + +In `BookingComponentMessagingGateway`, the log is defined as instance variable as following: +``` +private final Logger LOG = LoggerFactory.getLogger(getClass()); +``` +Further comparison and explanation between these two different declarations can be found in https://www.slf4j.org/faq.html#declared_static. + +SLF4J standardized the logging levels, which are different for the particular implementations. The usage of SLF4J is straightforward yet adaptable, allowing for better readability and performance. + +The logging levels used in SLF4J are: *TRACE*, *INFO*, *DEBUG*, *ERROR*, and *WARN*. *FATAL* logging level (introduced in Log4j) is removed in SLF4J due to redundancy +>The Marker interface, part of the org.slf4j package, renders the FATAL level largely redundant. +> -- https://www.slf4j.org/faq.html#fatal + +It is also dropped due to the fact that we should not determine when to terminate an application in a logging framework (https://www.baeldung.com/slf4j-with-log4j2-logback). + + +#### Tracing +In microservice architecture, tracing is implemented to monitor applications as well as to help identify where errors or failures occur, which may cause poor performance. Just like in our application which contains several services, it is necessary to trace the invocation from one service to another, either directly or through a kafka message broker. + +In this sample application, tracing implementations are supported by Spring Cloud Sleuth. Sleuth seamlessly interfaces with logging frameworks such as SLF4J and logback to add unique IDs that aid in the tracking and diagnosis of issues leveraging logs. Before it is implemented, its dependency must be defined in `build.gradle` file as following: +``` +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:2021.0.2" + } +} + +dependencies { + implementation 'org.springframework.cloud:spring-cloud-starter-sleuth' +} +``` +Once added within the application, Spring Cloud Sleuth automatically formats the logs that contain traceId and spanId. Below you can see the logs when the application just started and when a customer just booked a ship. + +``` +2022-06-13 22:01:16.243 INFO [shipkafka,bd2025db4480982a,bd2025db4480982a] 14272 --- [nio-8080-exec-5] o.a.kafka.common.utils.AppInfoParser : Kafka startTimeMs: 1655150476243 +``` +``` +2022-06-13 22:01:16.430 INFO [shipkafka,bd2025db4480982a,cfcad1e77644ff13] 14272 --- [ bookings-0-C-1] c.d.s.s.api.ShipRestController : Received: Booking(createdOn=Mon Jun 13 22:01:16 CEST 2022, id=12, lastUpdatedOn=Mon Jun 13 22:01:16 CEST 2022, containerCount=2, shipId=4, bookingStatus=REQUESTED, version=0) +``` +The part of normal log with additional core information from Spring Sleuth follows the format of: +**[application name, traceId, spanId]** + +- **Application name** - name that is set in the properties file/settings.gradle file. It can be used to aggregate logs from multiple instances of the same application. +- **traceId** - unique identifier for a specific job, request, or action. It is the same across all microservices. +- **spanId** - unique identifier that tracks a unit of work. It is assigned to each operation and therefore can be vary depends on what request is performed. + + +Once the application is started, the traceID and spanID **will be the same** by default. + +Further reading: https://www.baeldung.com/spring-cloud-sleuth-single-application#:~:text=TraceId%20%E2%80%93%20This%20is%20an%20id,that%20consists%20of%20multiple%20steps. + +#### Retry (in progress...) \ No newline at end of file From 963bf5f482a90fb37245db764d43601aa8360293 Mon Sep 17 00:00:00 2001 From: Waluya Date: Thu, 23 Jun 2022 08:52:58 +0200 Subject: [PATCH 05/10] add finalised solution --- solutions/communication_kafka/index.asciidoc | 295 +++++++++++++++++++ 1 file changed, 295 insertions(+) diff --git a/solutions/communication_kafka/index.asciidoc b/solutions/communication_kafka/index.asciidoc index e69de29bb..745e95e17 100644 --- a/solutions/communication_kafka/index.asciidoc +++ b/solutions/communication_kafka/index.asciidoc @@ -0,0 +1,295 @@ +:toc: + +== Abstract + +The goal of documentation is to present the Spring Kafka functionalities +in the context of microservices. The main focus would be how Kafka-based +messaging solutions are implemented with Spring Boot. Furthermore, the +documentation also provides information regarding the implementation of +logging, tracing, and retry within an application. + +Spring Kafka, Docker, and Postman are the tools being used during the +creation of this document. To run both Kafka message broker and +Zookeeper, Docker image is used. For the app testing, we use Postman by +sending already defined http requests to examine the expected outputs. + +== Introduction and Goals + +The goal of this solution is to provide guidance on how to use Kafka +efficiently with spring boot leveraging the spring-kafka module. + +____ +The Spring for Apache Kafka (spring-kafka) project applies core Spring +concepts to the development of Kafka-based messaging solutions. It +provides a "template" as a high-level abstraction for sending messages. +-- +https://spring.io/projects/spring-kafka[https://spring.io/projects/spring-kafka] +____ + +Basic concepts and functionalities of kafka can be found +https://developer.confluent.io/what-is-apache-kafka/[here]. + +In short, Apache Kafka was built to make real-time data available to all +the applications that need to use it. Kafka models events as key/value +pairs. These pairs are saved in an distributed append-only log in +specific topics. This way Kafka can be used as a distributed pub/sub +messaging system that replaces traditional message brokers like ActiveMQ +and RabbitMQ. + +This soulution concentrates on the following aspects: + +* Configuration +* Basic functionalities and Retry +* Logging +* Tracing + +== Context and Scope + +There are several common use cases for kafka (_further reading: +https://kafka.apache.org/uses[https://kafka.apache.org/uses]_), such as: + +* Messaging +* Website Activity Tracking +* Metrics +* Log Aggregation +* Stream Processing +* Event Sourcing + +The scope of this solution is limited to the implementation of the +following functionalities in a spring boot application: + +* Logging +* Monitoring +* Tracing +* Retry + +The tools that are set and implemented are as follow: + +* Spring Boot An extension of the popular Spring frameworks, but it has +a few unique features that make it ideal for quickly developing web +applications and microservices. +* Spring Kafka Extends the simple and common Spring template programming +model with a KafkaTemplate as well as Message-driven POJOs via +@KafkaListener annotation. +* Java / Kotlin +* Spring Cloud Sleuth Provides Spring Boot auto-configuration for +distributed tracing (_read more: +https://spring.io/projects/spring-cloud-sleuth[https://spring.io/projects/spring-cloud-sleuth]_). + +== Solution Strategy + +=== Configuration + +This provides basic guidelines on how to set up prerequisites before +implementing spring-kafka, such as defining the necessary dependencies +and setting up specific spring-kafka configurations. + +To use `spring-kafka` in a project, add the following dependency to the +`pom.xml` of a maven project: + +.... + + org.springframework.kafka + spring-kafka + +.... + +For a gradle project add the following line to the dependencies in the +`build.gradle` file: + +.... +implementation 'org.springframework.kafka:spring-kafka' + +.... + +To set specific spring-kafka configurations, the spring boot +`application.yml` can be used: + +.... +spring: + application: + name: shipkafka + kafka: + bootstrap-servers: "localhost:9092" + producer: + key-serializer: "org.apache.kafka.common.serialization.LongSerializer" + value-serializer: "org.springframework.kafka.support.serializer.JsonSerializer" + consumer: + key-deserializer: "org.apache.kafka.common.serialization.LongDeserializer" + value-deserializer: "org.springframework.kafka.support.serializer.JsonDeserializer" + properties: + spring: + json: + trusted: + packages: "*" +.... + +The Kafka broker is specified at `localhost:9092` in this example +configuration. + +Spring-kafka uses the type String for keys and values per default. This +behavior can be changed by setting the specific serializers and +deserializers like in the example. + +To deserialize objects received in json, the specific object needs to be +added to the trusted packages. In this example all packages of the +application are trusted through the use of the wildcard `*`. + +=== Creating topics + +The spring-boot application can create topics automatically: + +.... +@Bean +public NewTopic bookings() { + return TopicBuilder.name("bookings") + .partitions(2) + .compact() + .build(); +} +.... + +This creates a topic with the name `bookings` with two partitions and +compact logging. Further options for `TopicBuilder` can be found +https://docs.spring.io/spring-kafka/api/org/springframework/kafka/config/TopicBuilder.html[here]. + +=== Sending messages + +The class `KafkaTemplate` simplifies the sending of messages to the +broker. It can be autowired. + +.... +private final KafkaTemplate longTemplate; +.... + +This defines a template for sending messages with a `Long` key and an +object as a value. The Object will be serialized as json as specified in +the `application.yml`. + +The class has the methods `send()` for sending messages. The different +methods can be looked up in the +https://docs.spring.io/spring-kafka/api/org/springframework/kafka/core/KafkaTemplate.html[class +documentation]. + +.... +longTemplate.send(topic, key, message); +.... + +This sends a message with a key to the specified topic. + +=== Receiving messages + +Receiving messages from a topic is simplified with the +https://docs.spring.io/spring-kafka/reference/html/#annotation-properties[`@KafkaListener`] +annotation. + +.... +@KafkaListener(id = "bookings", topics = "bookings", groupId = "ship") +public void listenBookings(Booking booking){ + ... +} +.... + +In this example messages of the type Booking are consumed from the +`bookings` topic. + +=== Retry + +Failures in a distributed system may happen, i.e. failed message +process, network errors, runtime exceptions. Therefore, the retry logic +implementation is something essential to have. + +It is important to note that Retries in Kafka can be quickly implemented +at the consumer side. This is known as Simple Blocking Retries. To +accomplish visible error handling without causing real-time disruption, +Non-Blocking Retries and Dead Letter Topics are implemented. + +Non-Blocking Retries can easily be added to a listener: + +.... +@RetryableTopic(attempts = "3", backoff = @Backoff(delay = 2_000, maxDelay = 10_000, multiplier = 2)) +@KafkaListener(id = "bookings", topics = "bookings", groupId = "ship") +public void listenBookings(Booking booking){ + ... +} + +@DltHandler +public void listenBookingsDlt(Booking booking){ + LOG.info("Received DLT message: {}", booking); +} + +.... + +In this example the `@RetryableTopic` annotation attempts to process a +received message 3 times. The first retry is done after a delay of 2 +seconds. Each further attempt multiplies the delay by 2 with a max delay +of 10 seconds. If the message couldn't be processed, it gets send to the +deadletter topic annotated with `@DltHandler`. + +=== Logging + +Spring-kafka doesn't log everything that's happening in the applicaiton. +The usage of Slf4J is recommended to implement further logging. It's +straightforward yet adaptable, allowing for better readability and +performance. Sending and receiving messages should be logged +appropriately. It needs to be implemented manually as spring-kafka +doesnt create logs of it automatically. + +This is a simple example for logging received messages: + +.... +LOG.info("Received message: {}", message); +.... + +=== Tracing + +In microservice architecture, tracing is implemented to monitor +applications as well as to help identifying where errors or failures +occur, which may cause poor performance. In applications that may +contain several services, it is necessary to trace the invocation from +one service to another. + +The Spring Cloud Sleuth library adds tracing to spring-kafka. The +dependency can be added to a project by adding the following to the +`pom.xml` file: + +.... + + + + org.springframework.cloud + spring-cloud-dependencies + ${release.train.version} + pom + import + + + + + + org.springframework.cloud + spring-cloud-starter-sleuth + + +.... + +For a gradle project add the following to the `build.gradle` file: + +.... +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:2021.0.2" + } +} + +dependencies{ + implementation 'org.springframework.cloud:spring-cloud-starter-sleuth' +} +.... + +This will add a traceId and spanId to the Slf4J logs. If an application +name is specified in the `application.yml` like in the example, the +service name will be added to the logs as well. + +Further information can be found in the +https://spring.io/projects/spring-cloud-sleuth[official documentation]. From bb7eedf5249a618f49ff56747b0f87e38a938bbe Mon Sep 17 00:00:00 2001 From: Waluya Date: Thu, 23 Jun 2022 09:11:46 +0200 Subject: [PATCH 06/10] removed readme and updated solution file --- solutions/communication_kafka/README.md | 423 ------------------- solutions/communication_kafka/index.asciidoc | 14 +- 2 files changed, 8 insertions(+), 429 deletions(-) delete mode 100644 solutions/communication_kafka/README.md diff --git a/solutions/communication_kafka/README.md b/solutions/communication_kafka/README.md deleted file mode 100644 index 6be56bc28..000000000 --- a/solutions/communication_kafka/README.md +++ /dev/null @@ -1,423 +0,0 @@ -### Table of Contents - -# Abstract -The goal of documentation is to present the Spring Kafka functionalities in the context of microservices. The main focus would be how Kafka-based messaging solutions are implemented with Spring Boot. Furthermore, the documentation also provides information regarding the implementation of logging, tracing, and retry within an application. - -Spring Kafka, Docker, and Postman are the tools being used during the creation of this document. To run both Kafka message broker and Zookeeper, Docker image is used. For the app testing, we use Postman by sending already defined http requests to examine the expected outputs. - -# Introduction and Goals -The application should show how spring-kafka can be implemented within a sample application to leverage Apache Kafka's functionalities. - -The primary use case of this documentation is to explain the implementation of message transfer process based on Kafka (Produce-Consume Topics). - -1. Kafka Message Broker (Produce-Consume Topics) - Kafka is a message broker in part because it allows several consumers to perform different logic focused on a single message. A message broker allows services and applications to exchange messages with one another. The structure of the messages is explicitly specified and independent of the services that send them. - - The Producer is in charge of sending messages through a linked application to the message broker. In the meanwhile, the consumer consumes the messages that have been queued in the message broker. Messages are stored in a queue/topic by the message broker. - - -2. Streaming - The Kafka Streams API is a component of the Apache Kafka open-source data transformation and enrichment project. Kafka Streams is most commonly used to create real-time streaming data pipelines and applications that react to the data streams. It uses a combination of messaging, storage, and stream processing to store and analyze both historical and real-time data. - - As previously stated, Kafka Streams provides an API for developing client applications that alter data in Apache Kafka and then publish the modified data onto a new topic. The client application is where the data is processed. - - -3. Temporary/Permanent Data Storing - Kafka is a streaming data system that may hold data for a period of time before eliminating it. Meaning, Kafka does not discard messages as soon as the consumer reads them. - - Kafka may also store data on permanent storage and replicate data among brokers in a cluster, giving it the aspect of a database. Kafka's data can be stored indefinitely by the user. - -4. Logging - Apache Kafka Logs are a collection of numerous data segments stored on disk that are referred to as form-topic partitions or specific topic-partitions. Each Kafka log represents a logical unique topic-based partitioning. - - Logging with Apache Kafka makes the transformation of unstructured logging streams into an analyzable and understandable output possible. The output can then be used for detecting potential and ongoing problems as well as better environment monitoring. - -5. Retry - Failures in a distributed system may happen, i.e. failed message process, network errors, runtime exceptions. Therefore, the retry logic implementation is something essential to have. - - It is important to note that Retries in Kafka can be quickly implemented at the consumer side. This is known as Simple Blocking Retries. To accomplish visible error handling without causing real-time disruption, Non-Bloking Retries and Dead Letter Topics are implemented. - -6. Data Monitoring (Metrics) - Metrics are measurements that capture a value about the systems at a specific point in time, such as (provide a use case as an example from sample application). They are often collected every second, minute, or other regular interval to track a system's performance over time. - - Kafka Monitoring Metrics measure how well a component, for example network latency, performs its role. It's vital to keep an eye on the health of Kafka deployment to ensure that the applications that rely on it keep working. - -7. Event Sourcing - Event sourcing is a style of application design where state changes are logged as a time-ordered sequence of records. Kafka's support for very large stored log data makes it an excellent backend for an application built in this style. - - -8. Tracking (Activity Tracking) - With activity tracking it is possible to track page views, searches, uploads or other actions users may take. When an event happens in the blog, for example a user logs in, an information about the event and a tracking event and will be placed into a record. The record will be placed on a specified Kafka topic. - -Kafka-clients have many implementations in different languages. However, this document focuses on Spring Kafka using Java. - -The following are the advantages and (or) disadvantages of Kafka. - - -1. Streaming - - Advantages - - Transferring or filtering data between topics is easier - - Events can be sent to external systems - - Allows data parallelism, distributed coordination, fault - tolerance, scalability - - supports the creation of lightweight and small microservices - - it's flexible, scalable and fault-tolerant - - Data operations might be stateless, stateful or windowed. - - non-buffering system => streams are processed one at a time - - -2. Logging - - Advantages - - supports many integrations - - a clearly readable data set - - simple Implementation - - provides organizations access to a large number of tools - - Disadvantages - - Log messages are maintained for a certain amount of time before - deleting them - - During a spike, events can be deleted or lost without a user's - knowledge while they are timed out - - lack of scalability within a large enterprise environment - - An expanding company will have to increase the number pf resources on - servers, as well as process more logs. - -3. Message Broker - - Advantages - - Enables communication between several services that may not be - executing at the same time. - - Asynchronous processing improves system performance. - - Ensuring that messages are delivered reliably - - Disadvantages - - Increasing the complexity of the system - - Debugging can be difficult - - Learning takes time - - -4. Retry - - Advantages: - - Real time traffic is not disrupted (Batch processing can be unblocked) - - Error handling is observable (metadata can be obtained on the retries) - - Higher real-time throughput can be achieved - - Disadvantage: - - Kafka's ordering guarantees for the topic on which non-blocking retries is implemented is no longer applied. - - The batch processing is blocked, as failed messages are repeatedly processed in real time (they are re-consumed again and again). - -5. Data Monitoring (Metrics) - - Advantages: - Assist in identifying potential bottlenecks and performance concerns before they cause major issues - - Disadvantages: - - Kafka lacks a full set of management and monitoring capabilities. This makes enterprise support staff wary about implementing kafka and maintaining it in the long run. - - The first challenge above can be overcome by installing third-party, open-source, or commercial graphical tools that offer administrative and monitoring features. - -6. Event Sourcing - - Advantages: - - Kafka can be use as a natural backbone for Event Sourcing - Disadvantages: - - -7. Tracking (Activity Tracking) - - Advantages: - - - - Disadvantages: - - often requires a very high volume of throughput - - -8. Temporary/Permanent Data Storing - - Advantages: - - can be easily integrated with databases and cloud data lake storage - - quite easy to understand - - helps to avoid copying the full dump of one database to another - - - Disadvantages: - - doesn’t provide arbitrary access lookup to the data - - there isn’t a query language like SQL available to help you access data - - it is not a replacement for databases - -# Context and Scope (in progress...) - -# Solution Strategy - -# Apache Kafka -Apache Kafka was built to make real-time data available to all the applications that need to use it. - -## Use cases - -### Publish-Subscribe Messaging - -Kafka can be used as a distributed pub/sub messaging system that replaces traditional message brokers like ActiveMQ and RabbitMQ. - -### Streams - -Kafka can be used to perform real-time computations on event streams. - -### Metrics and Monitoring - -Kafka can be used to aggregate statistics from distributed applications to produce centralized feeds with real-time metrics. - -### Log Aggregation - -Kafka can be used as a single source of truth by centralizing logging data across all sources in a distributed system. - -## Kafka Concepts - -### Kafka and Events - -An event is any type of action, incident, or change that's identified or recorded by software or applications. Kafka models events as key/value pairs. Keys are often primitive types like strings or integers. Values are serialized representations of an application domain object or a raw message. - -### Kafka Topics - -A topic is a log of events. They are append only: New messages always go to the end of a log. The logs are immutable - once something has happened, it is difficult to make it un-happen. -Traditional messaging systems have topics and queues, which store messages temporarily for a short time. The advantage of Kafka topics is, that every topic can be configured to expire data after it reaches a certain age, that means the data can be stored for as short as seconds to indefinitely. The logs are files stored on disk. - -### Kafka Partitioning - -Partitioning takes the single topic log and breaks in into multiple logs, each of which can live on a separate node in the Kafka cluster. This makes Kafka a distributed system and helps it scale. Kafka messages can be given a key, the key guarantees that all the events from a given entity will always arrive in order. Without a key, the messages will be distributed among all the topic's partitions and any kind of ordering will be ignored. - -### Kafka Brokers - -Kafka is composed of a network of containers or machines called brokers, each running the Kafka broker process. Each broker hosts some set of partitions and handles incoming requests to write new events to those partitions or read events from them. Brokers also handle replication of partitions between each other. - -### Replication - -Partition data is copied automatically from a leader broker to follower brokers in the network. If one node in the cluster dies, another will take its role. This helps availability of data. - -### Kafka Producers and Consumers - -Producers connect and send key/value pairs to specific topic on the cluster. Consumers can then connect to the cluster and subscribe to one or more topics, listening and consuming key/value pairs of Kafka messages. Consuming a message does not destroy it, it can still be read by any other consumer. This differentiates Kafka from other traditional message brokers. Many consumers can read from one topic. Applications can be both consumers and producers. - -#### Source: https://developer.confluent.io/what-is-apache-kafka/ - -# spring-kafka - -The focus of this documentation is to demonstrate a Kafka-based messaging solution with the use of spring-kafka. - ->The Spring for Apache Kafka (spring-kafka) project applies core Spring concepts to the development of Kafka-based messaging solutions. It provides a "template" as a high-level abstraction for sending messages. -> -- https://spring.io/projects/spring-kafka - -To use `spring-kafka` in a project, add the following dependency to the `pom.xml`: - -``` - - org.springframework.kafka - spring-kafka - -``` - -For a gradle project add the following line to the dependencies in the `build.gradle` file: -``` -implementation 'org.springframework.kafka:spring-kafka' -``` - - -## Example Application - -An example application was written to demonstrate the presented concepts and functionalities. - -### Architecture - -The example application consists of two services that communicate through Kafka topics. - -The `booking-service` sends newly created/requested bookings to the Kafka `bookings` topic. The `ship-service` listens for incoming events (new bookings). After the `ship-service` receives a booking, it verifies if it is possible to execute the booking. The condition would be that if there are enough containers on the requested ship. If it is, then the booking gets confirmed. Otherwise, it gets rejected. The response, which is visualized as `bookingStatus`, is then sent back to the `bookings` topic and the `booking-service` listen to this topic. Only when the response is consumed, the `bookingStatus` gets updated. - -![](https://i.imgur.com/Bb8c5l1.png) - - -There are 3 possible outcomes, which are visualized as `bookingStatus`: `REQUESTED`, `CONFIRMED`, `CANCELED`. The `bookingStatus` of newly requested bookings, which are sent from `booking-service` to `bookings`, is `REQUESTED`. The booking of a ship with enough available containers will result in `CONFIRMED`. - -However, there might be some cases when a booking will not be confirmed. If a ship with not enough container is booked, the booking is not possible and hence gives `CANCELED` in the `bookingStatus`. Furthermore, when an already booked ship is damaged, the `ship-service` will then send this new response to Kafka `ship-damaged` topic. Afterwards, the `bookingStatus` is updated to `CANCELED`. - -### Implementation -#### Prerequisites -The following are the necessary tech stacks that are needed to be installed before running a spring kafka application: -- Apache Kafka -- Zookeeper -**OR** -- Docker images (container that includes both Kafka Message Broker and Zookeeper) - - -(**TODO: further elaboration is required**). - -#### Topics Creation - -The `booking-service` automatically creates the necessary topics on application startup by defining beans as followed: - -``` -@Bean -public NewTopic bookings() { - return TopicBuilder.name("bookings") - .partitions(2) - .compact() - .build(); -} -``` - -This creates the `bookings` topic with two partitions. The method `compact()` enables log compaction on the topic. A topic with log compaction removes any old records when there is a more recent update with the same primary key. This is optional and needs to be decided on a case-by-case basis. - -The creation of all necessary topics can be found in class `BookingComponentMessagingGateway`, in which the *Messaging Gateway* pattern is implemented. The class basically isolates messaging-specific functionalities (necessary codes to send or receive a message) from the rest of the application code. Hence, only the Messaging Gate code is aware of the messaging system. - -Within the class, another topic called `shipDamagedTopic` is defined and used to fetch messages whenever the `ship-service` informs the `booking-service` that the booked ship is currently damaged. - -``` -@Bean - public NewTopic shipDamagedTopic(){ - return TopicBuilder.name("shipDamagedTopic").build(); - } -``` - -The `booking-service` receives events from the `ship-service`. Every booking event contains an `id`, which was set by the `booking-service`. - - -#### Add new booking (Sending messages): - -After topics are created with kafka, the subsequent step is to send messages/data to it. `spring-kafka` simplifies sending messages to topics with the class `KafkaTemplate`, which provides methods for sending messages to Kafka topics in a feasible way. - -``` -private final KafkaTemplate longTemplate; -public void sendMessage(String topic, Long key, T message) { - LOG.info("Sending message : {}", message.toString()); - longTemplate.send(topic, key, message); - } -``` -The `send` API will send the data to the provided topic with the provided key and no partition. It returns a `ListenableFuture` object after a send. The `sendMessage` function is **for example implemented for adding a new booking**, which can be seen in class `BookingComponentBusinessLogic`. The new booking with its own associated id (which is retrieved using `getId()`) and the number of requested containers given by the customer is sent to `bookings` topic with message `booking`. - -``` -@Transactional(rollbackFor = {CustomerNotFoundException.class}) - public Booking addBooking(Long customerId, BookingCreateDTO bookingCreateDTO) throws CustomerNotFoundException{ - Optional optionalCustomer = customerRepository.findById(customerId); - - if (optionalCustomer.isPresent()) { - Customer customer = optionalCustomer.get(); - - //Booking booking = bookingRepository.save(Booking.of(bookingCreateDTO)); - Booking booking = bookingRepository.save(new Booking(bookingCreateDTO.getShipId(), bookingCreateDTO.getContainerCount())); - - customer.addBooking(booking); - customerRepository.save(customer); - - bookingComponentMessagingGateway.sendMessage("bookings", booking.getId(), booking); - - - return booking; - } else { - throw new CustomerNotFoundException(customerId); - } - } -``` -It is important to note that the status of the newly created booking is `REQUESTED`. The status of the bookings is managed by `BookingStatus`, which is an enum. The other constants within this enum are `CONFIRMED` and `CANCELED`. - -#### onBookingEvent, listenBooking (Receiving messages): -The booking is then consumed via `bookings` topic by `ship-service` and will be checked, if that booking already existed in the system or the ship that would be booked also exists. The process can be found in class `ShipRestController`. -``` -@KafkaListener(id = "bookings", topics = "bookings", groupId = "ship") - public void onBookingEvent(Booking booking) throws ShipNotFoundException, BookingAlreadyConfirmedException { - LOG.info("Received: {}", booking); - shipComponentLogic.confirmBooking(booking); - } -``` -The booking can be confirmed through `confirmBooking` function, which can be found in `ShipComponentLogic` class. Firstly, it will check if the status of this newly created booking is already confirmed or not. If it is, then `BookingAlreadyConfirmedException` will be thrown. If it's newly created, the ship that'll be booked is checked whether there's enough available container or not. If there is not enough available containers, the booking is cancelled (`BookingStatus.CANCELED`). Otherwise, it will be confirmed (`BookingStatus.CONFIRMED`) and this confirmation is afterwards sent to the `ship-bookings` topic. -``` -@Transactional(rollbackFor = {BookingAlreadyConfirmedException.class}) - public void confirmBooking(Booking booking) throws BookingAlreadyConfirmedException, ShipNotFoundException { - Ship ship = shipRepository.findById(booking.getShipId()).orElseThrow(() -> new ShipNotFoundException(booking.getShipId())); - LOG.info("Found: {}", ship); - - if (booking.getBookingStatus() == BookingStatus.CONFIRMED) { - throw new BookingAlreadyConfirmedException(booking.getId()); - } else if (booking.getBookingStatus().equals(BookingStatus.REQUESTED)) { - if(booking.getContainerCount() < ship.getAvailableContainers()){ - ship.setAvailableContainers(ship.getAvailableContainers() - booking.getContainerCount()); - booking.updateBookingStatus(BookingStatus.CONFIRMED); - shipRepository.save(ship); - } else { - booking.updateBookingStatus(BookingStatus.CANCELED); - } - } - - template.send("ship-bookings", booking.getId(), booking); - LOG.info("Sent: {}", booking); - } -``` - -The confirmation is finally consumed by `booking-service`, in which the booking is processed, hence updating the booking status to `CONFIRMED`. -``` -@KafkaListener(id ="ship-bookings", topics = "ship-bookings", groupId = "booking") - public void listenBooking(Booking booking) throws BookingNotFoundException { - LOG.info("Received message: {}", booking.toString()); - bookingComponentBusinessLogic.processBooking(booking); - } -``` - -#### Logging -As logging tool, SLF4J (Simple Logging Facade for Java) is used. It is an abstraction layer for different Java logging frameworks, such as Log4j2 or logback. - -The general pattern (common solution) for accessing loggers by defining logger as static final instance is no longer recommended, as well as defining logger as instance variable (as slf4j.org used to recommend). For example, in `ShipComponentLogic`, the log is declared as static final instance as following: - -``` -private static final Logger LOG = LoggerFactory.getLogger(ShipComponentLogic.class); -``` - -In `BookingComponentMessagingGateway`, the log is defined as instance variable as following: -``` -private final Logger LOG = LoggerFactory.getLogger(getClass()); -``` -Further comparison and explanation between these two different declarations can be found in https://www.slf4j.org/faq.html#declared_static. - -SLF4J standardized the logging levels, which are different for the particular implementations. The usage of SLF4J is straightforward yet adaptable, allowing for better readability and performance. - -The logging levels used in SLF4J are: *TRACE*, *INFO*, *DEBUG*, *ERROR*, and *WARN*. *FATAL* logging level (introduced in Log4j) is removed in SLF4J due to redundancy ->The Marker interface, part of the org.slf4j package, renders the FATAL level largely redundant. -> -- https://www.slf4j.org/faq.html#fatal - -It is also dropped due to the fact that we should not determine when to terminate an application in a logging framework (https://www.baeldung.com/slf4j-with-log4j2-logback). - - -#### Tracing -In microservice architecture, tracing is implemented to monitor applications as well as to help identify where errors or failures occur, which may cause poor performance. Just like in our application which contains several services, it is necessary to trace the invocation from one service to another, either directly or through a kafka message broker. - -In this sample application, tracing implementations are supported by Spring Cloud Sleuth. Sleuth seamlessly interfaces with logging frameworks such as SLF4J and logback to add unique IDs that aid in the tracking and diagnosis of issues leveraging logs. Before it is implemented, its dependency must be defined in `build.gradle` file as following: -``` -dependencyManagement { - imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:2021.0.2" - } -} - -dependencies { - implementation 'org.springframework.cloud:spring-cloud-starter-sleuth' -} -``` -Once added within the application, Spring Cloud Sleuth automatically formats the logs that contain traceId and spanId. Below you can see the logs when the application just started and when a customer just booked a ship. - -``` -2022-06-13 22:01:16.243 INFO [shipkafka,bd2025db4480982a,bd2025db4480982a] 14272 --- [nio-8080-exec-5] o.a.kafka.common.utils.AppInfoParser : Kafka startTimeMs: 1655150476243 -``` -``` -2022-06-13 22:01:16.430 INFO [shipkafka,bd2025db4480982a,cfcad1e77644ff13] 14272 --- [ bookings-0-C-1] c.d.s.s.api.ShipRestController : Received: Booking(createdOn=Mon Jun 13 22:01:16 CEST 2022, id=12, lastUpdatedOn=Mon Jun 13 22:01:16 CEST 2022, containerCount=2, shipId=4, bookingStatus=REQUESTED, version=0) -``` -The part of normal log with additional core information from Spring Sleuth follows the format of: -**[application name, traceId, spanId]** - -- **Application name** - name that is set in the properties file/settings.gradle file. It can be used to aggregate logs from multiple instances of the same application. -- **traceId** - unique identifier for a specific job, request, or action. It is the same across all microservices. -- **spanId** - unique identifier that tracks a unit of work. It is assigned to each operation and therefore can be vary depends on what request is performed. - - -Once the application is started, the traceID and spanID **will be the same** by default. - -Further reading: https://www.baeldung.com/spring-cloud-sleuth-single-application#:~:text=TraceId%20%E2%80%93%20This%20is%20an%20id,that%20consists%20of%20multiple%20steps. - -#### Retry (in progress...) \ No newline at end of file diff --git a/solutions/communication_kafka/index.asciidoc b/solutions/communication_kafka/index.asciidoc index 745e95e17..2a7b2d1fb 100644 --- a/solutions/communication_kafka/index.asciidoc +++ b/solutions/communication_kafka/index.asciidoc @@ -1,3 +1,5 @@ += Implementing Kafka with Spring Boot and Spring Kafka Module + :toc: == Abstract @@ -8,7 +10,7 @@ messaging solutions are implemented with Spring Boot. Furthermore, the documentation also provides information regarding the implementation of logging, tracing, and retry within an application. -Spring Kafka, Docker, and Postman are the tools being used during the +Spring Kafka, Docker, and Postman are some of the tools used during the creation of this document. To run both Kafka message broker and Zookeeper, Docker image is used. For the app testing, we use Postman by sending already defined http requests to examine the expected outputs. @@ -36,7 +38,7 @@ specific topics. This way Kafka can be used as a distributed pub/sub messaging system that replaces traditional message brokers like ActiveMQ and RabbitMQ. -This soulution concentrates on the following aspects: +This solution concentrates on the following aspects: * Configuration * Basic functionalities and Retry @@ -65,14 +67,14 @@ following functionalities in a spring boot application: The tools that are set and implemented are as follow: -* Spring Boot An extension of the popular Spring frameworks, but it has +* Spring Boot: An extension of the popular Spring frameworks, but it has a few unique features that make it ideal for quickly developing web applications and microservices. -* Spring Kafka Extends the simple and common Spring template programming +* Spring Kafka: Extends the simple and common Spring template programming model with a KafkaTemplate as well as Message-driven POJOs via @KafkaListener annotation. * Java / Kotlin -* Spring Cloud Sleuth Provides Spring Boot auto-configuration for +* Spring Cloud Sleuth: Provides Spring Boot auto-configuration for distributed tracing (_read more: https://spring.io/projects/spring-cloud-sleuth[https://spring.io/projects/spring-cloud-sleuth]_). @@ -190,7 +192,7 @@ public void listenBookings(Booking booking){ } .... -In this example messages of the type Booking are consumed from the +In this example, messages of the type Booking are consumed from the `bookings` topic. === Retry From 8ab4f8239e116c7eedbf6658daa30e253a7866c7 Mon Sep 17 00:00:00 2001 From: Waluya Date: Tue, 28 Jun 2022 18:45:48 +0200 Subject: [PATCH 07/10] updated Alternatives to kafka --- solutions/communication_kafka/index.asciidoc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/solutions/communication_kafka/index.asciidoc b/solutions/communication_kafka/index.asciidoc index 2a7b2d1fb..df2847b24 100644 --- a/solutions/communication_kafka/index.asciidoc +++ b/solutions/communication_kafka/index.asciidoc @@ -12,8 +12,7 @@ logging, tracing, and retry within an application. Spring Kafka, Docker, and Postman are some of the tools used during the creation of this document. To run both Kafka message broker and -Zookeeper, Docker image is used. For the app testing, we use Postman by -sending already defined http requests to examine the expected outputs. +Zookeeper, a Docker image is used. == Introduction and Goals @@ -60,10 +59,10 @@ https://kafka.apache.org/uses[https://kafka.apache.org/uses]_), such as: The scope of this solution is limited to the implementation of the following functionalities in a spring boot application: +* Configuration +* Basic functionalities and Retry * Logging -* Monitoring * Tracing -* Retry The tools that are set and implemented are as follow: @@ -295,3 +294,5 @@ service name will be added to the logs as well. Further information can be found in the https://spring.io/projects/spring-cloud-sleuth[official documentation]. + +=== Health Monitoring From 2c529ddb349c66d59bc3a4c27a109803aa4f696b Mon Sep 17 00:00:00 2001 From: Waluya Date: Tue, 28 Jun 2022 18:46:19 +0200 Subject: [PATCH 08/10] final edit --- solutions/communication_kafka/index.asciidoc | 117 +++++++++++++++---- 1 file changed, 93 insertions(+), 24 deletions(-) diff --git a/solutions/communication_kafka/index.asciidoc b/solutions/communication_kafka/index.asciidoc index df2847b24..30911b302 100644 --- a/solutions/communication_kafka/index.asciidoc +++ b/solutions/communication_kafka/index.asciidoc @@ -1,6 +1,6 @@ = Implementing Kafka with Spring Boot and Spring Kafka Module -:toc: +:toc: == Abstract @@ -10,7 +10,7 @@ messaging solutions are implemented with Spring Boot. Furthermore, the documentation also provides information regarding the implementation of logging, tracing, and retry within an application. -Spring Kafka, Docker, and Postman are some of the tools used during the +Spring Kafka, Docker, and Postman are the tools being used during the creation of this document. To run both Kafka message broker and Zookeeper, a Docker image is used. @@ -37,7 +37,7 @@ specific topics. This way Kafka can be used as a distributed pub/sub messaging system that replaces traditional message brokers like ActiveMQ and RabbitMQ. -This solution concentrates on the following aspects: +This soulution concentrates on the following aspects: * Configuration * Basic functionalities and Retry @@ -56,30 +56,31 @@ https://kafka.apache.org/uses[https://kafka.apache.org/uses]_), such as: * Stream Processing * Event Sourcing -The scope of this solution is limited to the implementation of the -following functionalities in a spring boot application: +This solution focuses not on the many use cases, it is limited to the +implementation of the following functionalities in a general spring boot +application: * Configuration * Basic functionalities and Retry * Logging * Tracing -The tools that are set and implemented are as follow: +The tools that are set for this solution are as followed: -* Spring Boot: An extension of the popular Spring frameworks, but it has +* Spring Boot An extension of the popular Spring frameworks, but it has a few unique features that make it ideal for quickly developing web applications and microservices. -* Spring Kafka: Extends the simple and common Spring template programming +* Spring Kafka Extends the simple and common Spring template programming model with a KafkaTemplate as well as Message-driven POJOs via @KafkaListener annotation. * Java / Kotlin -* Spring Cloud Sleuth: Provides Spring Boot auto-configuration for +* Spring Cloud Sleuth Provides Spring Boot auto-configuration for distributed tracing (_read more: https://spring.io/projects/spring-cloud-sleuth[https://spring.io/projects/spring-cloud-sleuth]_). -== Solution Strategy +=== Solution Strategy -=== Configuration +==== Configuration This provides basic guidelines on how to set up prerequisites before implementing spring-kafka, such as defining the necessary dependencies @@ -136,7 +137,7 @@ To deserialize objects received in json, the specific object needs to be added to the trusted packages. In this example all packages of the application are trusted through the use of the wildcard `*`. -=== Creating topics +==== Creating topics The spring-boot application can create topics automatically: @@ -154,7 +155,7 @@ This creates a topic with the name `bookings` with two partitions and compact logging. Further options for `TopicBuilder` can be found https://docs.spring.io/spring-kafka/api/org/springframework/kafka/config/TopicBuilder.html[here]. -=== Sending messages +==== Sending messages The class `KafkaTemplate` simplifies the sending of messages to the broker. It can be autowired. @@ -178,11 +179,12 @@ longTemplate.send(topic, key, message); This sends a message with a key to the specified topic. -=== Receiving messages +==== Receiving messages -Receiving messages from a topic is simplified with the -https://docs.spring.io/spring-kafka/reference/html/#annotation-properties[`@KafkaListener`] -annotation. +To receive messages you have to define a listener. + +The listener is defined by implementing and annotating a method like in +the following example: .... @KafkaListener(id = "bookings", topics = "bookings", groupId = "ship") @@ -191,10 +193,12 @@ public void listenBookings(Booking booking){ } .... -In this example, messages of the type Booking are consumed from the -`bookings` topic. +Receiving messages from a topic is simplified with the +https://docs.spring.io/spring-kafka/reference/html/#annotation-properties[`@KafkaListener`] +annotation. In this example messages of the type Booking are consumed +from the `bookings` topic. -=== Retry +==== Retry Failures in a distributed system may happen, i.e. failed message process, network errors, runtime exceptions. Therefore, the retry logic @@ -227,14 +231,31 @@ seconds. Each further attempt multiplies the delay by 2 with a max delay of 10 seconds. If the message couldn't be processed, it gets send to the deadletter topic annotated with `@DltHandler`. -=== Logging +.... +bookings-retry-5000 +.... + +Each retry creates a new topic like in the example above. + +DLT creates a topic for messages that couldn't get processed. The topic +gets named like the example below: + +.... +bookings-dlt +.... + +Further information can be found in the +https://docs.spring.io/spring-kafka/reference/html/#retry-topic[official +documentation]. + +==== Logging Spring-kafka doesn't log everything that's happening in the applicaiton. The usage of Slf4J is recommended to implement further logging. It's straightforward yet adaptable, allowing for better readability and performance. Sending and receiving messages should be logged appropriately. It needs to be implemented manually as spring-kafka -doesnt create logs of it automatically. +doesn't create logs of it automatically. This is a simple example for logging received messages: @@ -242,7 +263,7 @@ This is a simple example for logging received messages: LOG.info("Received message: {}", message); .... -=== Tracing +==== Tracing In microservice architecture, tracing is implemented to monitor applications as well as to help identifying where errors or failures @@ -295,4 +316,52 @@ service name will be added to the logs as well. Further information can be found in the https://spring.io/projects/spring-cloud-sleuth[official documentation]. -=== Health Monitoring +==== Health Monitoring + +Spring-kafka doesn't provide an inbuild health indicator for Kafka as a +general implementation for all use cases isn't possible. Further +information and updates on the situation can be found on +https://github.com/spring-projects/spring-boot/issues/14088#issuecomment-830410907[GitHub]. + +=== Alternatives + +This section presents some alternatives to Apache Kafka as a message +broker. + +==== ActiveMQ + +While Kafka is designed to process a high load of data in real-time, +ActiveMQ is intended to process only a small number of messages with a +high level of reliability. It can also be used for ETL jobs. + +ActiveMQ is a push-based messaging system. The producer has to ensure +that messages are delivered to the consumers. The acknowlegment of +messages decreases the throughput and increases the latency. Kafka is +pull-based and the consumers consume the messages from the topic. + +Kafka is a complex system, but it is highly scalable through partitions +and replicas. ActiveMQ is much simpler to deploy, but the performance +slows down the more consumers there are. + +If big data processing is not required, ActiveMQ is a good alternative +to Kafka. + +==== RabbitMQ + +RabbitMQ is a traditional message broker. It's highly flexible, +supporting many messaging protocols like AMQP, MQTT and STOMP. +Functionalities can be added through plugins. + +Unlike Kafka, RabbitMQ is mainly a push-based system. Consumers define a +prefetch limit. Messages will be prefetched until this limit is met. +After messages are processed and acknowleged, more messages can be +fetched until the limit is met again. + +While events are stored in append-only logs in Kafka, RabbitMQ stores +the events in queues. Messages are retained until they are acknowleged. + +RabbitMQ is easier to deploy than Kafka, but it cant reach the +scalability and performance of Kafka. + +If big data processing is not required and a highly flexible message +broker is required, RabbitMQ is a good alternative to Kafka. From 0de53de6f2bae0337a7300cadf5b7101ac87aa37 Mon Sep 17 00:00:00 2001 From: Waluya Date: Tue, 28 Jun 2022 18:50:23 +0200 Subject: [PATCH 09/10] minor fix --- solutions/communication_kafka/index.asciidoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/solutions/communication_kafka/index.asciidoc b/solutions/communication_kafka/index.asciidoc index 30911b302..7c717ed55 100644 --- a/solutions/communication_kafka/index.asciidoc +++ b/solutions/communication_kafka/index.asciidoc @@ -37,7 +37,7 @@ specific topics. This way Kafka can be used as a distributed pub/sub messaging system that replaces traditional message brokers like ActiveMQ and RabbitMQ. -This soulution concentrates on the following aspects: +This solution concentrates on the following aspects: * Configuration * Basic functionalities and Retry @@ -67,14 +67,14 @@ application: The tools that are set for this solution are as followed: -* Spring Boot An extension of the popular Spring frameworks, but it has +* Spring Boot: An extension of the popular Spring frameworks, but it has a few unique features that make it ideal for quickly developing web applications and microservices. -* Spring Kafka Extends the simple and common Spring template programming +* Spring Kafka: Extends the simple and common Spring template programming model with a KafkaTemplate as well as Message-driven POJOs via @KafkaListener annotation. * Java / Kotlin -* Spring Cloud Sleuth Provides Spring Boot auto-configuration for +* Spring Cloud Sleuth: Provides Spring Boot auto-configuration for distributed tracing (_read more: https://spring.io/projects/spring-cloud-sleuth[https://spring.io/projects/spring-cloud-sleuth]_). From 1fc4c57c8286bd6394b2273d87174afca39f8f1b Mon Sep 17 00:00:00 2001 From: Waluya Date: Thu, 30 Jun 2022 15:37:09 +0200 Subject: [PATCH 10/10] added necessary tags --- solutions/communication_kafka/index.asciidoc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/solutions/communication_kafka/index.asciidoc b/solutions/communication_kafka/index.asciidoc index 7c717ed55..27ac90e32 100644 --- a/solutions/communication_kafka/index.asciidoc +++ b/solutions/communication_kafka/index.asciidoc @@ -1,6 +1,10 @@ -= Implementing Kafka with Spring Boot and Spring Kafka Module +//Category=Communication;Kafka;Microservice Platforms;Tracing;Logging;Retry; +//Product=Apache Kafka;Spring Kafka;Spring Cloud Sleuth;Spring Boot; +//Maturity level=Advanced + +:toc: -:toc: += Implementing Kafka with Spring Boot and Spring Kafka Module == Abstract