Sunday, 27 January 2019

Java Microservices Project Architecture

let’s put the Java Microservices Project Architecture together in a way that shows the structure of all projects, their roles, and how they connect.

1️⃣ High-Level Architecture Overview

+--------------------+ | Client App | | (Web / Mobile) | +--------------------+ | v +--------------------+ | API Gateway | | (Routing + Security)| +--------------------+ | -------------------------------------- | | | v v v +-----------+ +-----------+ +-----------+ | User Svc | | Order Svc | | PaymentSvc| +-----------+ +-----------+ +-----------+ | | | ------------------|------------------- v +--------------------+ | Service Registry | | (Eureka) | +--------------------+ | +--------------------+ | Config Server | | (Central Config) | +--------------------+ | +--------------------+ | Message Broker | | (Kafka/RabbitMQ) | +--------------------+

2️⃣ Project Structure in Workspace

You would usually have multiple Spring Boot projects, one for each component:

microservices-architecture/ │ ├── api-gateway/ # API Gateway project │ ├── src/main/java │ ├── src/main/resources │ └── pom.xml │ ├── service-registry/ # Eureka Server project │ ├── src/main/java │ ├── src/main/resources │ └── pom.xml │ ├── config-server/ # Centralized Config Server │ ├── src/main/java │ ├── src/main/resources │ └── pom.xml │ ├── user-service/ # User Management microservice │ ├── src/main/java │ ├── src/main/resources │ └── pom.xml │ ├── order-service/ # Order Handling microservice │ ├── src/main/java │ ├── src/main/resources │ └── pom.xml │ ├── payment-service/ # Payment Processing microservice │ ├── src/main/java │ ├── src/main/resources │ └── pom.xml │ └── pom.xml # Parent Maven config (optional)

3️⃣ Responsibilities of Each Project

1. API Gateway

  • Purpose: Single entry point for all clients.

  • Tasks:

    • Route requests to respective microservices.

    • Apply authentication (JWT/OAuth2).

    • Load balancing with Eureka.

  • Tech: Spring Cloud Gateway, Spring Security.


2. Service Registry (Eureka)

  • Purpose: Service discovery and registration.

  • Tasks:

    • Each service registers here with name + port.

    • API Gateway & services fetch other services’ locations dynamically.


3. Config Server

  • Purpose: Central config management.

  • Tasks:

    • Stores all application.properties in Git or local folder.

    • Pushes updates to services without restart.


4. User Service

  • Purpose: Manages users, authentication, profile.

  • Communication: REST API → Order Service.

  • Database: MS SQL/PostgreSQL.


5. Order Service

  • Purpose: Handles orders & cart operations.

  • Communication: REST API → Payment Service.

  • Database: MySQL/MongoDB.


6. Payment Service

  • Purpose: Processes payments, communicates with gateways.

  • Communication: Kafka event consumption from Order Service.

  • Database: PostgreSQL.


4️⃣ Communication Flow

  1. Client → API Gateway → Routes request to target microservice.

  2. Microservices discover each other via Eureka.

  3. Configurations loaded from Config Server.

  4. Async operations handled via Kafka.

  5. Each service uses its own DB (no sharing).


5️⃣ Deployment

  • Local Dev: Run each service on different port.

  • Cloud: Dockerize each service → Deploy to Kubernetes.

  • CI/CD: GitHub Actions, Jenkins pipelines.

Java Microservices — Ready-to-Run Architecture Blueprint

A complete, minimal, ready-to-run blueprint with project layout, sample code, configuration, Docker/Docker Compose, and run instructions for a microservices system: Eureka (Service Registry), Config Server, API Gateway, User Service, Order Service, Payment Service + Kafka and MySQL. Meant for local development and easy transition to Docker/Kubernetes.


Overview

This blueprint shows how to structure multiple Spring Boot projects as independent microservices, how they discover each other (Eureka), share configuration (Config Server), expose a single entry point (API Gateway), communicate synchronously (Feign/WebClient/REST) and asynchronously (Kafka), and how to containerize them with Docker + Docker Compose for local testing.

Projects included:

  • service-registry (Eureka)

  • config-server (Spring Cloud Config)

  • api-gateway (Spring Cloud Gateway)

  • user-service (business service)

  • order-service (business service) — uses Feign to call user-service

  • payment-service (business service) — consumes Kafka events from order-service


Prerequisites

  • Java 17+ or 21 (adjust java.version accordingly)

  • Maven 3.6+

  • Docker & Docker Compose (for running containers)

  • Optional: IntelliJ / VSCode


Parent pom.xml (optional)

<!-- Parent pom to manage common versions (optional) -->
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>microservices-parent</artifactId>
  <packaging>pom</packaging>
  <version>1.0.0</version>
  <modules>
    <module>service-registry</module>
    <module>config-server</module>
    <module>api-gateway</module>
    <module>user-service</module>
    <module>order-service</module>
    <module>payment-service</module>
  </modules>
  <properties>
    <java.version>17</java.version>
    <spring.boot.version>3.1.4</spring.boot.version>
    <spring.cloud.version>2023.0.6</spring.cloud.version>
  </properties>
</project>

Use Spring Boot 3.x and Spring Cloud compatible release for best support.

1) service-registry (Eureka)

Purpose: Service discovery.

pom.xml (important deps)

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

Main class

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

application.yml

server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  server:
    enable-self-preservation: false
spring:
  application:
    name: service-registry

2) config-server

Purpose: Centralized configuration (backed by local config folder or Git).

pom.xml deps (key)

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Main class

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

application.yml

server:
  port: 8888
spring:
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/config, file:./config
eureka:
  client:
    enabled: false

Config files

  • Place service property files in config/ (or Git). Example: user-service.ymlorder-service.yml etc.

Example config/user-service.yml:

spring:
  datasource:
    url: jdbc:mysql://mysql:3306/userdb?createDatabaseIfNotExist=true
    username: user
    password: pass

3) api-gateway

Purpose: Routing, authentication, rate-limiting (optional)

pom.xml deps (key)

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

application.yml

server:
  port: 8080
spring:
  application:
    name: api-gateway
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/users/**
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/orders/**

Main class is plain @SpringBootApplication.


4) user-service

Purpose: User management & authentication.

pom.xml deps (important)

<dependency>spring-boot-starter-web</dependency>
<dependency>spring-boot-starter-data-jpa</dependency>
<dependency>mysql-connector-java</dependency>
<dependency>spring-cloud-starter-netflix-eureka-client</dependency>

Application

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

application.yml (uses config server)

spring:
  application:
    name: user-service
  cloud:
    config:
      uri: http://localhost:8888
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8081

Simple entity & repo

@Entity
public class User {
  @Id @GeneratedValue private Long id;
  private String username;
  private String role;
  // getters/setters
}
public interface UserRepository extends JpaRepository<User, Long> {}

Controller

@RestController
@RequestMapping("/users")
public class UserController {
  private final UserRepository repo;
  public UserController(UserRepository r){this.repo=r;}

  @GetMapping("/{id}")
  public ResponseEntity<User> get(@PathVariable Long id){
    return repo.findById(id).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
  }
}

5) order-service

Purpose: Orders; calls user-service via Feign and publishes events to Kafka.

pom.xml deps (important)

<dependency>spring-boot-starter-web</dependency>
<dependency>spring-boot-starter-data-jpa</dependency>
<dependency>org.springframework.cloud:spring-cloud-starter-openfeign</dependency>
<dependency>org.springframework.kafka:spring-kafka</dependency>
<dependency>spring-cloud-starter-netflix-eureka-client</dependency>

Main class

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

Feign client to user-service

@FeignClient(name="user-service")
public interface UserClient {
  @GetMapping("/users/{id}")
  User getUser(@PathVariable("id") Long id);
}

Controller

@RestController
@RequestMapping("/orders")
public class OrderController {
  private final KafkaTemplate<String,String> kafka;
  private final UserClient userClient;
  public OrderController(KafkaTemplate<String,String> k, UserClient u){this.kafka=k;this.userClient=u;}

  @PostMapping
  public ResponseEntity<String> create(@RequestParam Long userId){
    User user = userClient.getUser(userId);
    // create order (omitted for brevity)
    kafka.send("orders", "ORDER_CREATED:"+userId);
    return ResponseEntity.ok("order created for " + user.getUsername());
  }
}

application.yml

server:
  port: 8082
spring:
  application:
    name: order-service
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  kafka:
    bootstrap-servers: localhost:9092

6) payment-service

Purpose: Consume ORDER_CREATED events and process payments.

pom.xml deps (key)

<dependency>spring-boot-starter-web</dependency>
<dependency>org.springframework.kafka:spring-kafka</dependency>
<dependency>spring-cloud-starter-netflix-eureka-client</dependency>

Main class

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

Kafka listener

@Service
public class PaymentListener {
  @KafkaListener(topics = "orders", groupId = "payment_group")
  public void onMessage(String msg){
    if(msg.startsWith("ORDER_CREATED:")){
      String userId = msg.split(":")[1];
      // process payment (omitted)
      System.out.println("Processed payment for userId=" + userId);
    }
  }
}

application.yml

server:
  port: 8083
spring:
  application:
    name: payment-service
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  kafka:
    bootstrap-servers: localhost:9092

Docker & Docker Compose (local)

Dockerfiles: each service can use a minimal Dockerfile:

FROM eclipse-temurin:17-jdk-jammy
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

docker-compose.yml (essential services)

version: '3.8'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
    ports: ["2181:2181"]
  kafka:
    image: confluentinc/cp-kafka:latest
    depends_on: [zookeeper]
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports: ["9092:9092"]
  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: userdb
      MYSQL_USER: user
      MYSQL_PASSWORD: pass
    ports: ["3306:3306"]
  service-registry:
    build: ./service-registry
    ports: ["8761:8761"]
    depends_on: [mysql]
  config-server:
    build: ./config-server
    ports: ["8888:8888"]
    depends_on: [service-registry]
  api-gateway:
    build: ./api-gateway
    ports: ["8080:8080"]
    depends_on: [service-registry, config-server]
  user-service:
    build: ./user-service
    ports: ["8081:8081"]
    depends_on: [config-server, service-registry, mysql]
  order-service:
    build: