Microservices Architecture with Java

A hands-on tutorial covering the implementation, containerization, and deployment of an e-commerce microservices system

Overview

This tutorial demonstrates the implementation of a complete e-commerce microservices system with the following components:

Prerequisites

Install Java 17

Windows (using Chocolatey):

# Install Chocolatey (PowerShell as Administrator)
Set-ExecutionPolicy Bypass -Scope Process -Force
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

# Install Java 17
choco install openjdk17

# Verify installation
java -version

macOS (using Homebrew):

# Install Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Install Java 17
brew install openjdk@17

# Verify
java -version

Linux (Ubuntu/Debian):

sudo apt update
sudo apt install openjdk-17-jdk -y

# Set JAVA_HOME
echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

# Verify
java -version

Install Maven

# Windows
choco install maven

# macOS
brew install maven

# Linux (Ubuntu)
sudo apt install maven -y

# Verify
mvn -version

Install Docker Desktop

# Verify Docker installation
docker --version
docker-compose --version

IDE Setup

IntelliJ IDEA Community Edition

Cloud Deployment Options

The following platforms offer free tiers suitable for deploying and testing microservices:

Railway.app

$5 free credit per month, GitHub integration with auto-deploy, built-in PostgreSQL and RabbitMQ templates. No credit card required to start.

Sign up: railway.app

Render.com

Free tier for web services, auto-deploy from Git, built-in PostgreSQL, free SSL certificates.

Sign up: render.com

Google Cloud Platform (GKE)

$300 credit for 90 days (new accounts), always-free tier includes Compute Engine, full Kubernetes (GKE) support.

Sign up: cloud.google.com/free

AWS Free Tier

12 months free tier, 750 hours/month EC2, ECS and EKS for containers, RDS for databases.

Sign up: aws.amazon.com/free

Microsoft Azure

$200 credit for 30 days, 12 months of free services, Azure Kubernetes Service (AKS), Container Apps for serverless containers.

Sign up: azure.microsoft.com/free

Note: This tutorial demonstrates deployment to Railway.app and Google Kubernetes Engine.

Project Architecture

The system consists of the following services:

Service Port Purpose Database
Config Server 8888 Centralized configuration management -
Eureka Server 8761 Service discovery and registration -
API Gateway 8080 Single entry point, routing -
Product Service 8081 Manage product catalog PostgreSQL
Order Service 8082 Handle order processing PostgreSQL
Inventory Service 8083 Track stock levels PostgreSQL

Technologies:

Step 1: Project Setup

Create Project Directory

mkdir microservices-ecommerce
cd microservices-ecommerce
git init

# Create .gitignore
cat > .gitignore << 'EOF'
target/
*.iml
.idea/
*.class
.DS_Store
EOF

Parent POM Configuration

Create: pom.xml

<?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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.1</version>
    </parent>

    <groupId>com.ecommerce</groupId>
    <artifactId>microservices-parent</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>

    <modules>
        <module>config-server</module>
        <module>eureka-server</module>
        <module>api-gateway</module>
        <module>product-service</module>
        <module>order-service</module>
        <module>inventory-service</module>
    </modules>

    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.0</spring-cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Step 2: Config Server

The Config Server provides centralized configuration management for all microservices.

Create Module

mkdir -p config-server/src/main/java/com/ecommerce/config
mkdir -p config-server/src/main/resources/config

POM Configuration

Create: config-server/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.ecommerce</groupId>
        <artifactId>microservices-parent</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>config-server</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
    </dependencies>
</project>

Main Application Class

Create: config-server/src/main/java/com/ecommerce/config/ConfigServerApplication.java

package com.ecommerce.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

Configuration

Create: config-server/src/main/resources/application.yml

server:
  port: 8888

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/config
  profiles:
    active: native

Step 3: Eureka Server

Eureka Server handles service discovery - services automatically register and find each other.

Create Module

mkdir -p eureka-server/src/main/java/com/ecommerce/eureka
mkdir -p eureka-server/src/main/resources

POM Configuration

Create: eureka-server/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.ecommerce</groupId>
        <artifactId>microservices-parent</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>eureka-server</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>

Main Application Class

Create: eureka-server/src/main/java/com/ecommerce/eureka/EurekaServerApplication.java

package com.ecommerce.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

Configuration

Create: eureka-server/src/main/resources/application.yml

server:
  port: 8761

spring:
  application:
    name: eureka-server

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka/

Step 4: Product Service

Our first business microservice - manages the product catalog.

Create Module Structure

mkdir -p product-service/src/main/java/com/ecommerce/product/{model,repository,service,controller}
mkdir -p product-service/src/main/resources

POM Configuration

Create: product-service/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.ecommerce</groupId>
        <artifactId>microservices-parent</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>product-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

Product Entity

Create: product-service/src/main/java/com/ecommerce/product/model/Product.java

package com.ecommerce.product.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    private String description;

    @Column(nullable = false)
    private BigDecimal price;

    private String category;
}

Repository

Create: product-service/src/main/java/com/ecommerce/product/repository/ProductRepository.java

package com.ecommerce.product.repository;

import com.ecommerce.product.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByCategory(String category);
    List<Product> findByNameContainingIgnoreCase(String name);
}

Service Layer

Create: product-service/src/main/java/com/ecommerce/product/service/ProductService.java

package com.ecommerce.product.service;

import com.ecommerce.product.model.Product;
import com.ecommerce.product.repository.ProductRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
@RequiredArgsConstructor
public class ProductService {
    private final ProductRepository productRepository;

    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }

    public Product getProductById(Long id) {
        return productRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Product not found"));
    }

    public Product createProduct(Product product) {
        return productRepository.save(product);
    }

    public List<Product> searchProducts(String query) {
        return productRepository.findByNameContainingIgnoreCase(query);
    }
}

REST Controller

Create: product-service/src/main/java/com/ecommerce/product/controller/ProductController.java

package com.ecommerce.product.controller;

import com.ecommerce.product.model.Product;
import com.ecommerce.product.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {
    private final ProductService productService;

    @GetMapping
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }

    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productService.getProductById(id);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Product createProduct(@RequestBody Product product) {
        return productService.createProduct(product);
    }

    @GetMapping("/search")
    public List<Product> searchProducts(@RequestParam String q) {
        return productService.searchProducts(q);
    }
}

Main Application

Create: product-service/src/main/java/com/ecommerce/product/ProductServiceApplication.java

package com.ecommerce.product;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ProductServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductServiceApplication.class, args);
    }
}

Bootstrap Configuration

Create: product-service/src/main/resources/application.yml

spring:
  application:
    name: product-service
  config:
    import: optional:configserver:http://localhost:8888

Service Configuration (in Config Server)

Create: config-server/src/main/resources/config/product-service.yml

server:
  port: 8081

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/productdb
    username: postgres
    password: postgres
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true
Note: Order Service and Inventory Service follow the same structure. Create them with similar code, changing entity fields and business logic as needed.

Step 5: Order Service with Circuit Breaker

The Order Service demonstrates inter-service communication and resilience patterns.

Add Circuit Breaker Dependency

In order-service/pom.xml, add:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.1.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

Order Entity

Create: order-service/src/main/java/com/ecommerce/order/model/Order.java

package com.ecommerce.order.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Entity
@Table(name = "orders")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long productId;
    private Integer quantity;
    private BigDecimal totalAmount;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    private LocalDateTime createdAt;
}

enum OrderStatus {
    PENDING, CONFIRMED, FAILED
}

Service with Circuit Breaker

Create: order-service/src/main/java/com/ecommerce/order/service/OrderService.java

package com.ecommerce.order.service;

import com.ecommerce.order.model.*;
import com.ecommerce.order.repository.OrderRepository;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
    private final OrderRepository orderRepository;
    private final RestTemplate restTemplate;

    @CircuitBreaker(name = "productService", fallbackMethod = "createOrderFallback")
    public Order createOrder(Order order) {
        // Verify product exists (calls Product Service)
        String url = "http://product-service/api/products/" + order.getProductId();
        restTemplate.getForObject(url, Object.class);

        order.setStatus(OrderStatus.PENDING);
        order.setCreatedAt(LocalDateTime.now());
        return orderRepository.save(order);
    }

    private Order createOrderFallback(Order order, Exception ex) {
        log.error("Circuit breaker activated", ex);
        Order fallbackOrder = new Order();
        fallbackOrder.setStatus(OrderStatus.FAILED);
        fallbackOrder.setProductId(order.getProductId());
        fallbackOrder.setQuantity(order.getQuantity());
        return fallbackOrder;
    }
}

RestTemplate Configuration

Create: order-service/src/main/java/com/ecommerce/order/config/RestTemplateConfig.java

package com.ecommerce.order.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Circuit Breaker Configuration

Add to config-server/src/main/resources/config/order-service.yml:

resilience4j:
  circuitbreaker:
    instances:
      productService:
        sliding-window-size: 10
        failure-rate-threshold: 50
        wait-duration-in-open-state: 10s
        permitted-number-of-calls-in-half-open-state: 3

Step 6: Event-Driven Communication with RabbitMQ

Connect Order Service and Inventory Service through async messaging.

RabbitMQ Configuration (Order Service)

Create: order-service/src/main/java/com/ecommerce/order/config/RabbitMQConfig.java

package com.ecommerce.order.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    @Bean
    public TopicExchange orderExchange() {
        return new TopicExchange("order-exchange");
    }

    @Bean
    public Queue inventoryQueue() {
        return new Queue("inventory-queue");
    }

    @Bean
    public Binding inventoryBinding(Queue inventoryQueue, TopicExchange orderExchange) {
        return BindingBuilder.bind(inventoryQueue)
            .to(orderExchange)
            .with("order.placed");
    }
}

Event Publisher

Create: order-service/src/main/java/com/ecommerce/order/event/OrderEventPublisher.java

package com.ecommerce.order.event;

import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class OrderEventPublisher {
    private final RabbitTemplate rabbitTemplate;

    public void publishOrderPlaced(OrderEvent event) {
        event.setEventType("ORDER_PLACED");
        rabbitTemplate.convertAndSend("order-exchange", "order.placed", event);
    }
}

Event Listener (Inventory Service)

Create: inventory-service/src/main/java/com/ecommerce/inventory/event/OrderEventListener.java

package com.ecommerce.inventory.event;

import com.ecommerce.inventory.service.InventoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
@Slf4j
public class OrderEventListener {
    private final InventoryService inventoryService;

    @RabbitListener(queues = "inventory-queue")
    public void handleOrderPlaced(OrderEvent event) {
        log.info("Received order event: {}", event);
        try {
            inventoryService.reduceStock(event.getProductId(), event.getQuantity());
            log.info("Stock reduced successfully");
        } catch (Exception e) {
            log.error("Failed to reduce stock", e);
        }
    }
}

Step 7: API Gateway

Create a single entry point for all microservices.

POM Configuration

Create: api-gateway/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.ecommerce</groupId>
        <artifactId>microservices-parent</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>api-gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
</project>

Configuration with Routes

Create: api-gateway/src/main/resources/application.yml

server:
  port: 8080

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/api/products/**

        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**

        - id: inventory-service
          uri: lb://inventory-service
          predicates:
            - Path=/api/inventory/**

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

Step 8: Dockerization

Containerize all services for consistent deployment.

Dockerfile Template

Create this Dockerfile in each service directory:

FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

# Add non-root user
RUN addgroup -g 1000 appuser && \
    adduser -D -u 1000 -G appuser appuser

# Copy JAR file
COPY target/*.jar app.jar

RUN chown -R appuser:appuser /app
USER appuser

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=3s --start-period=60s \
  CMD wget --no-verbose --tries=1 --spider \
      http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["java", \
            "-XX:MaxRAMPercentage=75.0", \
            "-Djava.security.egd=file:/dev/./urandom", \
            "-jar", "app.jar"]

Docker Compose

Create: docker-compose.yml (project root)

version: '3.8'

services:
  postgres-product:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: productdb
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5432:5432"
    volumes:
      - product-data:/var/lib/postgresql/data

  postgres-order:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: orderdb
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5433:5432"
    volumes:
      - order-data:/var/lib/postgresql/data

  postgres-inventory:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: inventorydb
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5434:5432"
    volumes:
      - inventory-data:/var/lib/postgresql/data

  rabbitmq:
    image: rabbitmq:3-management-alpine
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest

volumes:
  product-data:
  order-data:
  inventory-data:

Step 9: Build and Run Locally

Build All Services

# From project root
mvn clean package -DskipTests

Start Infrastructure

# Start databases and RabbitMQ
docker-compose up -d

# Wait for services to start
sleep 10

Start Services (Terminal Tabs)

# Terminal 1: Config Server
cd config-server
mvn spring-boot:run

# Terminal 2: Eureka Server (wait 15s after config server)
cd eureka-server
mvn spring-boot:run

# Terminal 3: Product Service (wait 20s after eureka)
cd product-service
mvn spring-boot:run

# Terminal 4: Order Service
cd order-service
mvn spring-boot:run

# Terminal 5: Inventory Service
cd inventory-service
mvn spring-boot:run

# Terminal 6: API Gateway
cd api-gateway
mvn spring-boot:run

Verify Services

Test the System

# Create a product
curl -X POST http://localhost:8080/api/products \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Laptop",
    "description": "High-performance laptop",
    "price": 999.99,
    "category": "Electronics"
  }'

# Get all products
curl http://localhost:8080/api/products

# Create inventory entry
curl -X POST http://localhost:8083/api/inventory \
  -H "Content-Type: application/json" \
  -d '{
    "productId": 1,
    "quantity": 100
  }'

# Create an order (triggers event to inventory)
curl -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{
    "productId": 1,
    "quantity": 2,
    "totalAmount": 1999.98
  }'

Step 10: Deploy to Railway.app

Install Railway CLI

# macOS/Linux
curl -fsSL https://railway.app/install.sh | sh

# Windows (PowerShell)
iwr https://railway.app/install.ps1 | iex

# Login
railway login

Create Railway Project

# Initialize project
railway init

# Link to your Railway account
railway link

Add Databases

  1. Go to Railway Dashboard (https://railway.app)
  2. Click "New" → "Database" → "PostgreSQL" (do this 3 times for product, order, inventory DBs)
  3. Click "New" → "Template" → Search "RabbitMQ" → Deploy

Update Configuration

Use environment variables in your application.yml:

spring:
  datasource:
    url: ${DATABASE_URL}
  rabbitmq:
    addresses: ${RABBITMQ_URL}

Deploy Services

# Deploy each service
cd config-server
railway up

cd ../eureka-server
railway up

cd ../product-service
railway up

# Continue for all services...

Alternative: GitHub Integration

  1. Push your code to GitHub
  2. In Railway: "New" → "Deploy from GitHub repo"
  3. Select your repository
  4. Railway auto-deploys on git push!

Step 11: Deploy to Kubernetes (Optional)

Setup Google Kubernetes Engine

# Install Google Cloud SDK
# macOS: brew install google-cloud-sdk
# Linux: curl https://sdk.cloud.google.com | bash

# Login and setup
gcloud init
gcloud auth login

# Set project
export PROJECT_ID=your-project-id
gcloud config set project $PROJECT_ID

# Enable APIs
gcloud services enable container.googleapis.com
gcloud services enable artifactregistry.googleapis.com

# Create GKE cluster (autopilot mode)
gcloud container clusters create-auto ecommerce-cluster \
  --region=us-central1

# Get credentials
gcloud container clusters get-credentials ecommerce-cluster \
  --region=us-central1

Build and Push Docker Images

# Create Artifact Registry repository
gcloud artifacts repositories create ecommerce-repo \
  --repository-format=docker \
  --location=us-central1

# Configure Docker
gcloud auth configure-docker us-central1-docker.pkg.dev

# Build and push images
export REPO=us-central1-docker.pkg.dev/$PROJECT_ID/ecommerce-repo

# For each service:
cd config-server
mvn clean package -DskipTests
docker build -t $REPO/config-server:1.0 .
docker push $REPO/config-server:1.0

# Repeat for all services...

Create Kubernetes Manifests

Create: k8s/product-service.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: product-service
  template:
    metadata:
      labels:
        app: product-service
    spec:
      containers:
      - name: product-service
        image: us-central1-docker.pkg.dev/PROJECT_ID/ecommerce-repo/product-service:1.0
        ports:
        - containerPort: 8081
        env:
        - name: SPRING_DATASOURCE_URL
          value: jdbc:postgresql://postgres-service:5432/productdb
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8081
          initialDelaySeconds: 60
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8081
          initialDelaySeconds: 30
---
apiVersion: v1
kind: Service
metadata:
  name: product-service
spec:
  selector:
    app: product-service
  ports:
  - port: 80
    targetPort: 8081
  type: ClusterIP

Deploy to Kubernetes

# Apply all manifests
kubectl apply -f k8s/

# Check deployments
kubectl get deployments
kubectl get pods
kubectl get services

# View logs
kubectl logs -f deployment/product-service

# Access via port-forward (testing)
kubectl port-forward service/api-gateway 8080:80

Summary

Next Steps

Enhance Your System:

Practice Exercises:

  1. Add Payment Service: Create a new microservice that processes payments and participates in the SAGA pattern
  2. Implement SAGA: Add compensating transactions for order flow (order → inventory → payment → confirmation)
  3. Add Rate Limiting: Implement request throttling in the API Gateway
  4. Add Distributed Tracing: Integrate Zipkin to trace requests across services

Advanced Topics:

Additional Resources

Official Documentation:

Books:

Online Learning:

Community:

← Back to Advanced Java Course View Slides Version