Enrique Valdivia Rios Portafolio - Senior Backend Engineer | Java · Spring Boot · Kubernetes | Fintech & Payments | Scalable Distributed Systems

E-Commerce Microservices Platform

An e-commerce platform built with real microservices architecture: independent services, asynchronous communication via Kafka, polyglot persistence, and Kubernetes deployment.

Full source code available on my GitHub repository.


Architecture

Client → API Gateway (8080)
              ├── /api/orders/**     → Order Service    (PostgreSQL + Kafka producer)
              ├── /api/products/**   → Product Service  (MongoDB)
              ├── /api/payments/**   → Payment Service  (PostgreSQL + Kafka consumer/producer)
              └── /api/notifications → Notification Service (MongoDB + Kafka consumer)

The event flow is fully asynchronous:

Order Service  →[order.created]→  Payment Service  →[payment.processed]→  Notification Service

Order Service  →[order.created]→──────────────────────────────────────────┘

Tech Stack

ComponentTechnology
FrameworkSpring Boot 3.2 + Spring Cloud
API GatewaySpring Cloud Gateway + Resilience4j
MessagingApache Kafka
Relational DBPostgreSQL 16 (orders, payments)
Document DBMongoDB 7 (products, notifications)
ObservabilityPrometheus + Grafana + Micrometer
ContainersDocker + Docker Compose
OrchestrationKubernetes 1.28

Implemented Services

Order Service — Port 8081

Manages the order lifecycle. On creation, publishes an order.created event to Kafka:

@Transactional
public OrderResponse createOrder(CreateOrderRequest request) {
    Order saved = orderRepository.save(order);

    kafkaTemplate.send("order.created",
        saved.getId().toString(),
        new OrderCreatedEvent(saved.getId(), saved.getCustomerId(),
            saved.getProductId(), saved.getQuantity(), saved.getAmount()));

    return OrderResponse.from(saved);
}

Endpoints: POST /api/orders · GET /api/orders/{id} · GET /api/orders?customerId= · POST /api/orders/{id}/cancel

Product Service — Port 8082

Full product catalog CRUD backed by MongoDB:

Endpoints: POST /api/products · GET /api/products · GET /api/products/{id} · GET /api/products?category= · PUT /api/products/{id} · DELETE /api/products/{id}

Payment Service — Port 8083

Consumes order.created, processes the payment, and publishes payment.processed:

@KafkaListener(topics = "order.created", groupId = "payment-group")
public void onOrderCreated(OrderCreatedEvent event) {
    paymentService.processPayment(event);
}

The service simulates approval/rejection by amount (>10,000 is declined):

if (event.amount().intValue() > 10000) {
    payment.setStatus(PaymentStatus.FAILED);
    payment.setFailureReason("Amount exceeds limit");
} else {
    payment.setStatus(PaymentStatus.COMPLETED);
}
kafkaTemplate.send("payment.processed", ...);

Endpoints: GET /api/payments/{id} · GET /api/payments/order/{orderId}

Notification Service — Port 8084

Consumes both events and persists notifications in MongoDB:

@KafkaListener(topics = "order.created", groupId = "notification-group")
public void onOrderCreated(OrderCreatedEvent event) { ... }

@KafkaListener(topics = "payment.processed", groupId = "notification-group")
public void onPaymentProcessed(PaymentProcessedEvent event) { ... }

API Gateway — Port 8080

Request routing with Resilience4j circuit breaker on all routes:


Testing

Order Service: 7 unit tests (service) + 6 controller tests with MockMvc

Product Service: 8 unit tests (service)

Payment Service: 6 unit tests — covering completed payment, failed payment, lookup by ID and by orderId

Notification Service: 4 tests — verifying notification content for each event type

@Test
void handlePaymentProcessed_failed_savesFailureNotification() {
    PaymentProcessedEvent event = new PaymentProcessedEvent(
        UUID.randomUUID(), orderId, "customer-1", "FAILED",
        new BigDecimal("20000.00"), "Amount exceeds limit");

    notificationService.handlePaymentProcessed(event);

    ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
    verify(notificationRepository).save(captor.capture());
    assertThat(captor.getValue().getMessage()).contains("Amount exceeds limit");
}

How to Run

git clone https://github.com/enriquevaldivia1988/microservices-k8s.git
cd microservices-k8s

# Start all infrastructure and services
docker-compose up -d

# Verify services are healthy
curl http://localhost:8080/actuator/health  # API Gateway
curl http://localhost:8081/actuator/health  # Order Service

# Create an order
curl -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{"customerId":"customer-1","productId":"prod-1","quantity":2,"amount":99.99}'

# Deploy to Kubernetes
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/kafka-deployment.yaml
kubectl apply -f k8s/

Prometheus: http://localhost:9090 | Grafana: http://localhost:3000 (admin/admin)


Conclusion

This project demonstrates a production-ready microservices architecture: decoupled services, real asynchronous communication with Kafka, polyglot persistence (PostgreSQL + MongoDB), observability with Prometheus/Grafana, and Kubernetes deployment with health probes and resource limits.


Enrique Valdivia

View source on GitHub