diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..cfaa0b2
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,54 @@
+name: CI
+
+on:
+ push:
+ branches: [dev, main, master]
+ pull_request:
+ branches: [dev, main, master]
+
+jobs:
+ build:
+ name: Build all services
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout source
+ uses: actions/checkout@v4
+
+ - name: Set up Java 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Build all services
+ run: mvn clean package -DskipTests
+
+ - name: Upload API Gateway JAR
+ uses: actions/upload-artifact@v4
+ with:
+ name: api-gateway
+ path: api-gateway/target/api-gateway.jar
+ retention-days: 7
+
+ - name: Upload Order Service JAR
+ uses: actions/upload-artifact@v4
+ with:
+ name: order-service
+ path: order-service/target/order-service.jar
+ retention-days: 7
+
+ - name: Upload Payment Service JAR
+ uses: actions/upload-artifact@v4
+ with:
+ name: payment-service
+ path: payment-service/target/payment-service.jar
+ retention-days: 7
+
+ - name: Upload Notification Service JAR
+ uses: actions/upload-artifact@v4
+ with:
+ name: notification-service
+ path: notification-service/target/notification-service.jar
+ retention-days: 7
diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
new file mode 100644
index 0000000..8330447
--- /dev/null
+++ b/.github/workflows/integration-test.yml
@@ -0,0 +1,95 @@
+name: Integration Tests
+
+on:
+ push:
+ branches: [dev, main, master]
+ pull_request:
+ branches: [dev, main, master]
+
+jobs:
+ integration-test:
+ name: Full pipeline test with RabbitMQ
+ runs-on: ubuntu-latest
+
+ services:
+ rabbitmq:
+ image: rabbitmq:3-management
+ ports:
+ - 5672:5672
+ - 15672:15672
+ options: >-
+ --health-cmd "rabbitmq-diagnostics -q ping"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+
+ steps:
+ - name: Checkout source
+ uses: actions/checkout@v4
+
+ - name: Set up Java 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Build all services
+ run: mvn clean package -DskipTests
+
+ - name: Start background services
+ run: |
+ java -jar order-service/target/order-service.jar > order-service.log 2>&1 &
+ java -jar payment-service/target/payment-service.jar > payment-service.log 2>&1 &
+ java -jar notification-service/target/notification-service.jar > notification-service.log 2>&1 &
+ java -jar api-gateway/target/api-gateway.jar > api-gateway.log 2>&1 &
+
+ - name: Wait for API Gateway to be ready
+ run: |
+ echo "Waiting for API Gateway..."
+ for i in $(seq 1 30); do
+ if curl -sf http://localhost:8080/api > /dev/null 2>&1; then
+ echo "API Gateway is up after ${i} attempts"
+ exit 0
+ fi
+ sleep 2
+ done
+ echo "API Gateway did not start in time"
+ cat api-gateway.log
+ exit 1
+
+ - name: GET /api — health check
+ run: |
+ response=$(curl -sf http://localhost:8080/api)
+ echo "Response: $response"
+ [ "$response" = "Hello World!" ]
+
+ - name: POST /api/order — full message flow
+ run: |
+ response=$(curl -s -X POST http://localhost:8080/api/order \
+ -H "Content-Type: application/json" \
+ -d '{"id":1,"product":"Widget","quantity":2,"price":19.99}')
+ echo "Response: $response"
+ echo "$response" | grep -q "Order sent to RabbitMQ"
+
+ - name: Wait for messages to propagate
+ run: sleep 3
+
+ - name: Verify Order Service processed the order
+ run: grep -q "\[Order-Service\]" order-service.log && echo "Order Service OK"
+
+ - name: Verify Payment Service processed the payment
+ run: grep -q "\[Payment-Service\]" payment-service.log && echo "Payment Service OK"
+
+ - name: Verify Notification Service sent both emails
+ run: |
+ grep -q "Sending Order Created Email" notification-service.log && echo "Order email OK"
+ grep -q "Sending Payment Succeed Email" notification-service.log && echo "Payment email OK"
+
+ - name: Print all logs on failure
+ if: failure()
+ run: |
+ echo "=== API Gateway ===" && cat api-gateway.log || true
+ echo "=== Order Service ===" && cat order-service.log || true
+ echo "=== Payment Service ===" && cat payment-service.log || true
+ echo "=== Notification Service ===" && cat notification-service.log || true
diff --git a/.gitignore b/.gitignore
index 4b56acf..7147ca7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,26 +1,22 @@
-# compiled output
-/dist
-/node_modules
-/build
+# Maven build output
+target/
+*.jar
+*.war
+*.ear
+
+# Maven wrapper download cache
+.mvn/wrapper/maven-wrapper.jar
# Logs
-logs
*.log
-npm-debug.log*
-pnpm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
# OS
.DS_Store
-
-# Tests
-/coverage
-/.nyc_output
+Thumbs.db
# IDEs and editors
-/.idea
+.idea/
+*.iml
.project
.classpath
.c9/
@@ -37,20 +33,13 @@ lerna-debug.log*
# dotenv environment variable files
.env
-.env.development.local
-.env.test.local
-.env.production.local
-.env.local
-
-# temp directory
-.temp
-.tmp
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Diagnostic reports (https://nodejs.org/api/report.html)
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+.env.*
+
+# Spring Boot
+HELP.md
+
+# Claude Code config (local only)
+.claude/
+
+# Service runtime logs
+*.log
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..69f85ba
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index a20502b..0000000
--- a/.prettierrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "singleQuote": true,
- "trailingComma": "all"
-}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f50a838
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,73 @@
+# ─── OS Detection ────────────────────────────────────────────────────────────
+ifeq ($(OS),Windows_NT)
+ MVN := mvnw.cmd
+ KILL_JAVA := taskkill /F /IM java.exe
+else
+ MVN := ./mvnw
+ KILL_JAVA := pkill -f "java -jar" || true
+endif
+
+JAVA := java
+JAR_GW := api-gateway/target/api-gateway.jar
+JAR_ORD := order-service/target/order-service.jar
+JAR_PAY := payment-service/target/payment-service.jar
+JAR_NOT := notification-service/target/notification-service.jar
+
+.PHONY: all build start stop docker-up docker-down test clean help
+
+# ─── Targets ─────────────────────────────────────────────────────────────────
+
+all: docker-up build start
+
+build:
+ifeq ($(OS),Windows_NT)
+ $(MVN) clean package -DskipTests
+else
+ chmod +x mvnw
+ $(MVN) clean package -DskipTests
+endif
+
+docker-up:
+ docker-compose up -d
+
+docker-down:
+ docker-compose down
+
+# Windows: opens each service in a new terminal window.
+# Linux/Mac: runs each service in the background, logging to *.log files.
+start:
+ifeq ($(OS),Windows_NT)
+ cmd /C start "API Gateway" $(JAVA) -jar $(JAR_GW)
+ cmd /C start "Order Service" $(JAVA) -jar $(JAR_ORD)
+ cmd /C start "Payment Service" $(JAVA) -jar $(JAR_PAY)
+ cmd /C start "Notification Service" $(JAVA) -jar $(JAR_NOT)
+else
+ $(JAVA) -jar $(JAR_GW) > api-gateway.log 2>&1 &
+ $(JAVA) -jar $(JAR_ORD) > order-service.log 2>&1 &
+ $(JAVA) -jar $(JAR_PAY) > payment-service.log 2>&1 &
+ $(JAVA) -jar $(JAR_NOT) > notification-service.log 2>&1 &
+ @echo "Services started. Follow logs with: tail -f api-gateway.log"
+endif
+
+stop:
+ -$(KILL_JAVA)
+
+test:
+ curl -s -X POST http://localhost:8080/api/order \
+ -H "Content-Type: application/json" \
+ -d '{"id":1,"product":"Widget","quantity":2,"price":19.99}'
+
+clean:
+ $(MVN) clean
+
+help:
+ @echo ""
+ @echo " make all - docker-up + build + start"
+ @echo " make build - compile and package all JARs"
+ @echo " make docker-up - start RabbitMQ"
+ @echo " make docker-down - stop RabbitMQ"
+ @echo " make start - launch all four services"
+ @echo " make stop - kill all Java processes"
+ @echo " make test - POST a test order to the API"
+ @echo " make clean - remove build artifacts"
+ @echo ""
diff --git a/api-gateway/pom.xml b/api-gateway/pom.xml
new file mode 100644
index 0000000..9d4d218
--- /dev/null
+++ b/api-gateway/pom.xml
@@ -0,0 +1,40 @@
+
+
+ 4.0.0
+
+
+ com.microservices.rabbitmq
+ microservices-rabbitmq-parent
+ 1.0.0
+
+
+ api-gateway
+ API Gateway
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-amqp
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+
+ api-gateway
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/api-gateway/src/main/java/com/microservices/rabbitmq/apigateway/ApiGatewayApplication.java b/api-gateway/src/main/java/com/microservices/rabbitmq/apigateway/ApiGatewayApplication.java
new file mode 100644
index 0000000..cf8d216
--- /dev/null
+++ b/api-gateway/src/main/java/com/microservices/rabbitmq/apigateway/ApiGatewayApplication.java
@@ -0,0 +1,11 @@
+package com.microservices.rabbitmq.apigateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ApiGatewayApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(ApiGatewayApplication.class, args);
+ }
+}
diff --git a/api-gateway/src/main/java/com/microservices/rabbitmq/apigateway/config/RabbitMQConfig.java b/api-gateway/src/main/java/com/microservices/rabbitmq/apigateway/config/RabbitMQConfig.java
new file mode 100644
index 0000000..08679d5
--- /dev/null
+++ b/api-gateway/src/main/java/com/microservices/rabbitmq/apigateway/config/RabbitMQConfig.java
@@ -0,0 +1,39 @@
+package com.microservices.rabbitmq.apigateway.config;
+
+import org.springframework.amqp.core.Queue;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.amqp.support.converter.Jackson2JavaTypeMapper;
+import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
+import org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RabbitMQConfig {
+
+ public static final String ORDER_QUEUE = "order_queue";
+
+ @Bean
+ public Queue orderQueue() {
+ return new Queue(ORDER_QUEUE, true);
+ }
+
+ @Bean
+ public MessageConverter messageConverter() {
+ Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
+ DefaultJackson2JavaTypeMapper typeMapper = new DefaultJackson2JavaTypeMapper();
+ typeMapper.setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence.INFERRED);
+ typeMapper.setTrustedPackages("*");
+ converter.setJavaTypeMapper(typeMapper);
+ return converter;
+ }
+
+ @Bean
+ public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
+ RabbitTemplate template = new RabbitTemplate(connectionFactory);
+ template.setMessageConverter(messageConverter());
+ return template;
+ }
+}
diff --git a/api-gateway/src/main/java/com/microservices/rabbitmq/apigateway/controller/ApiGatewayController.java b/api-gateway/src/main/java/com/microservices/rabbitmq/apigateway/controller/ApiGatewayController.java
new file mode 100644
index 0000000..b62e381
--- /dev/null
+++ b/api-gateway/src/main/java/com/microservices/rabbitmq/apigateway/controller/ApiGatewayController.java
@@ -0,0 +1,30 @@
+package com.microservices.rabbitmq.apigateway.controller;
+
+import com.microservices.rabbitmq.apigateway.model.Order;
+import com.microservices.rabbitmq.apigateway.service.ApiGatewayService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api")
+public class ApiGatewayController {
+
+ private final ApiGatewayService apiGatewayService;
+
+ public ApiGatewayController(ApiGatewayService apiGatewayService) {
+ this.apiGatewayService = apiGatewayService;
+ }
+
+ @GetMapping
+ public String getHello() {
+ return apiGatewayService.getHello();
+ }
+
+ @PostMapping("/order")
+ public ResponseEntity