Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 258 additions & 0 deletions code/chapter13/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
= Design of MicroProfile E-commerce Application using Reactive Messaging
:toc: left
:icons: font
:source-highlighter: highlightjs

This document provides a service-by-service design for using Reactive Messaging in the MicroProfile e-Commerce application. It outlines the recommended channels, event payloads, and responsibilities for each microservice, while also recommending a hybrid architecture that combines synchronous REST calls with asynchronous event-driven communication.

== Design goals

* Keep each service responsible for its own data and lifecycle transitions.
* Use events for long-running business workflow progression instead of tightly coupled HTTP chains.
* Keep REST for reads, administration, direct client queries, and immediate request/response interactions.
* Combine REST and Reactive Messaging in a practical hybrid architecture.
* Start simple with in-memory wiring, then switch to Kafka for multi-service deployment.

== Recommended architecture: Hybrid REST + Reactive Messaging

A pure event-driven design is not required for every interaction. In this application, the most practical approach is a hybrid model:

* Use *REST with MicroProfile Rest Client* for operations that need an immediate response, such as fetching data, validating requests, or initiating checkout.
* Use *Reactive Messaging* for asynchronous workflow steps that span multiple services, such as payment authorization, inventory reservation, shipment updates, and status notifications.

=== Use REST for

* shopping cart and checkout initiation
* synchronous reads and lookups
* administrative CRUD operations
* validations where the caller needs an immediate result

=== Use Reactive Messaging for

* order lifecycle events
* payment success or failure notifications
* inventory reservation outcomes
* shipping progress updates
* audit, monitoring, and user notifications

== Recommended channel names

[cols="2,2,3,3", options="header"]
|===
|Channel |Produced by |Consumed by |Purpose

|`order-created`
|Order Service
|Payment Service
|Publish a newly persisted order that is ready for payment.

|`payment-authorized`
|Payment Service
|Inventory Service, Order Service
|Notify that payment succeeded.

|`payment-failed`
|Payment Service
|Order Service
|Notify that payment was rejected or failed.

|`inventory-reserved`
|Inventory Service
|Shipment Service, Order Service
|Notify that stock has been reserved successfully.

|`inventory-rejected`
|Inventory Service
|Order Service
|Notify that stock could not be reserved.

|`shipment-created`
|Shipment Service
|Order Service
|Notify that shipment processing has started.

|`shipment-dispatched`
|Shipment Service
|Order Service, User Service
|Notify that the package has left the warehouse.

|`shipment-delivered`
|Shipment Service
|Order Service, User Service
|Notify that the order has been delivered.

|`order-status-events`
|Order Service
|Shopping Cart Service, User Service, observability components
|Publish normalized order state changes for downstream consumers.
|===

== Suggested event payload classes

Use small, explicit payload types rather than reusing full JPA-style entities everywhere.

* `OrderCreatedEvent`
* `PaymentResultEvent`
* `InventoryReservationEvent`
* `ShipmentEvent`
* `OrderStatusChangedEvent`

Each event should include identifiers such as `orderId`, `userId`, `timestamp`, and only the fields needed by downstream services.

== Service-by-service design

=== Shopping Cart Service

The Shopping Cart service remains the user-facing checkout initiator.
Checkout begins synchronously through REST, and then transitions into an event-driven workflow once the order has been accepted.

*Suggested bean classes:*

* `CheckoutClient`
** Uses `RestClient` to call the Order Service synchronously at checkout time
* `OrderStatusListener`
** Consumes `order-status-events` to show current order progress in the UI

*Recommended responsibility:*

* Keep cart calculation and cart management synchronous.
* Use REST to submit the order when the user confirms checkout.
* Rely on downstream events only after the order has been accepted and persisted.

=== Order Service

The Order service should remain the system of record for order lifecycle state.

*Suggested bean classes:*

* `OrderCreationHandler`
** Called by the existing REST-based checkout flow
** Calls the existing `OrderService.createOrder(...)`
** Emits `OrderCreatedEvent` to `order-created`
* `OrderStatusEventHandler`
** Consumes `payment-authorized`, `payment-failed`, `inventory-reserved`, `inventory-rejected`, `shipment-created`, `shipment-dispatched`, and `shipment-delivered`
** Calls the existing `OrderService.updateOrderStatus(...)`
* `OrderStatusPublisher`
** Emits normalized updates to `order-status-events`

*Recommended responsibility:*

* Persist the initial order.
* Listen for downstream business outcomes.
* Update status transitions such as `CREATED`, `PAID`, `PROCESSING`, `SHIPPED`, and `DELIVERED`.

=== Payment Service

The Payment service should react to newly created orders and publish payment outcomes.

*Suggested bean classes:*

* `PaymentRequestHandler`
** `@Incoming("order-created")`
** Uses payment gateway logic from the current `PaymentService`
** Emits to either `payment-authorized` or `payment-failed`
* `PaymentAuditLogger`
** Consumes `payment-authorized` and `payment-failed` for audit and telemetry

*Recommended responsibility:*

* Own payment authorization and failure handling.
* Avoid direct synchronous callbacks into Order where possible.

=== Inventory Service

The Inventory service should reserve or reject stock after payment authorization.

*Suggested bean classes:*

* `InventoryReservationHandler`
** `@Incoming("payment-authorized")`
** Uses the existing `InventoryService` to validate products and update quantities
** Emits `inventory-reserved` or `inventory-rejected`
* `LowStockPublisher`
** Emits optional low-stock notifications when quantity drops below a threshold

*Recommended responsibility:*

* Keep inventory ownership local to the Inventory service.
* Publish stock reservation outcomes instead of requiring the Order service to poll or chain REST calls.

=== Shipment Service

The Shipment service should start logistics only after inventory is successfully reserved.

*Suggested bean classes:*

* `ShipmentRequestHandler`
** `@Incoming("inventory-reserved")`
** Creates a shipment record and emits `shipment-created`
* `ShipmentLifecyclePublisher`
** Emits `shipment-dispatched` and `shipment-delivered`

*Recommended responsibility:*

* Replace parts of the current direct HTTP-based coordination with event-driven shipment updates.
* Keep shipment lifecycle state local to the Shipment service.

=== User Service

The User service does not need to sit in the core order-processing path, but it can subscribe to status updates for communication and personalization.

*Suggested bean classes:*

* `UserNotificationHandler`
** `@Incoming("order-status-events")`
** Sends order progress notifications such as paid, shipped, or delivered

*Recommended responsibility:*

* Consume events for user-facing notifications rather than participating in transactional order workflow.

=== Catalog Service

The Catalog service can remain mostly REST-based for product browsing and product lookup.

*Suggested bean classes:*

* `ProductChangedPublisher` (optional)
** Emits product update events when price or availability metadata changes
* `InventoryProjectionHandler` (optional)
** Consumes low-stock or inventory-summary events if a denormalized read model is needed

*Recommended responsibility:*

* Keep product reads synchronous and simple.
* Add messaging only when product updates need to be broadcast to other services.

== End-to-end workflow

. Shopping Cart initiates checkout using a synchronous REST call to the Order Service
. Order Service persists the order and emits `order-created`
. Payment Service consumes `order-created` and emits `payment-authorized` or `payment-failed`
. Inventory Service consumes `payment-authorized` and emits `inventory-reserved` or `inventory-rejected`
. Shipment Service consumes `inventory-reserved` and emits `shipment-created`, then `shipment-dispatched`, then `shipment-delivered`
. Order Service consumes all result events and emits normalized `order-status-events`
. User-facing and observability components subscribe to `order-status-events`

This approach gives the caller an immediate response at checkout time while still allowing the fulfillment workflow to proceed asynchronously across services.

== Practical adoption plan

=== Step 1: Introduce payment messaging

* Order Service publishes `order-created`
* Payment Service consumes it and publishes `payment-authorized`

=== Step 2: Add inventory reservation

* Inventory Service consumes `payment-authorized`
* Publish `inventory-reserved` and `inventory-rejected`

=== Step 3: Add shipment lifecycle events

* Shipment Service consumes `inventory-reserved`
* Emit shipment lifecycle messages

=== Step 4: Publish normalized order state

* Order Service becomes the single place that converts downstream outcomes into official order statuses
Empty file.
82 changes: 82 additions & 0 deletions code/chapter13/order/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.microprofile.tutorial</groupId>
<artifactId>order</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<name>order</name>

<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<liberty.var.http.port>8050</liberty.var.http.port>
<liberty.var.https.port>8051</liberty.var.https.port>
<lombok.version>1.18.36</lombok.version>
</properties>

<dependencies>
<dependency>
<groupId>org.eclipse.microprofile</groupId>
<artifactId>microprofile</artifactId>
<version>7.1</version>
<type>pom</type>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>10.0.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.eclipse.microprofile.reactive.messaging</groupId>
<artifactId>microprofile-reactive-messaging-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.7.0</version>
</dependency>

</dependencies>

<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>

<plugin>
<groupId>io.openliberty.tools</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<version>3.10</version>
<configuration>
<serverName>OrderServer</serverName>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.microprofile.tutorial.store.order;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition;
import org.eclipse.microprofile.openapi.annotations.info.Contact;
import org.eclipse.microprofile.openapi.annotations.info.Info;
import org.eclipse.microprofile.openapi.annotations.info.License;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

/**
* JAX-RS application for order management.
*/
@ApplicationPath("/api")
@OpenAPIDefinition(
info = @Info(
title = "Order API",
version = "1.0.0",
description = "API for managing orders and order items",
license = @License(
name = "Eclipse Public License 2.0",
url = "https://www.eclipse.org/legal/epl-2.0/"),
contact = @Contact(
name = "Order API Support",
email = "support@example.com")),
tags = {
@Tag(name = "Order", description = "Operations related to order management"),
@Tag(name = "OrderItem", description = "Operations related to order item management")
}
)
public class OrderApplication extends Application {
// The resources will be discovered automatically
}
Loading
Loading