- Published on
9 - Simple Microservices part one
- Authors

- Name
- Samreach YAN
Table of Contents
- Project Structure
- Docker Compose Setup
- Microservice Architecture Principles
- Service Discovery with Eureka
- Configuration Management
- Circuit Breakers with Resilience4j
- API Gateway Implementation
- Swagger/OpenAPI Documentation
- Spring Security with OAuth2 and Keycloak
- Rate Limiting with Redis
- Failover and Retry Policies
- Keycloak Integration
- Final Deployment
Prerequisites
- Java 17+
- Spring Boot 3.x
- Basic Spring Security knowledge
- Docker (for PostgreSQL setup)
1. Project Structure
microservices-advanced/
│
├── api-gateway/
│ ├── src/main/
│ │ ├── java/com/example/apigateway/
│ │ │ ├── config/
│ │ │ ├── controller/
│ │ │ └── ApiGatewayApplication.java
│ │ └── resources/
│ │ ├── application.yml
│ │ └── bootstrap.yml
│ └── pom.xml
│
├── config-server/
│ ├── src/main/
│ │ ├── java/com/example/configserver/
│ │ │ └── ConfigServerApplication.java
│ │ └── resources/
│ │ ├── application.yml
│ │ └── bootstrap.yml
│ └── pom.xml
│
├── discovery-server/
│ ├── src/main/
│ │ ├── java/com/example/discoveryserver/
│ │ │ ├── config/
│ │ │ └── DiscoveryServerApplication.java
│ │ └── resources/
│ │ └── application.yml
│ └── pom.xml
│
├── user-service/
│ ├── src/main/
│ │ ├── java/com/example/userservice/
│ │ │ ├── config/
│ │ │ ├── controller/
│ │ │ ├── dto/
│ │ │ ├── model/
│ │ │ ├── repository/
│ │ │ ├── service/
│ │ │ └── UserServiceApplication.java
│ │ └── resources/
│ │ ├── application.yml
│ │ └── bootstrap.yml
│ └── pom.xml
│
├── order-service/
│ ├── src/main/
│ │ ├── java/com/example/orderservice/
│ │ │ ├── config/
│ │ │ ├── controller/
│ │ │ ├── dto/
│ │ │ ├── model/
│ │ │ ├── repository/
│ │ │ ├── service/
│ │ │ └── OrderServiceApplication.java
│ │ └── resources/
│ │ ├── application.yml
│ │ └── bootstrap.yml
│ └── pom.xml
│
├── docker-compose.yml
└── README.md
2. Docker Compose Setup
services:
postgres:
image: postgres:15-alpine
container_name: postgres
environment:
POSTGRES_PASSWORD: admin
POSTGRES_USER: admin
POSTGRES_DB: microservices
ports:
- '5432:5432'
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U admin']
interval: 5s
timeout: 5s
retries: 5
networks:
- microservices-network
redis:
image: redis:7-alpine
container_name: redis
ports:
- '6379:6379'
volumes:
- redis_data:/data
networks:
- microservices-network
keycloak:
image: quay.io/keycloak/keycloak:21.1.1
container_name: keycloak
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/microservices
KC_DB_USERNAME: admin
KC_DB_PASSWORD: admin
KC_HOSTNAME: keycloak
ports:
- '8181:8080'
depends_on:
postgres:
condition: service_healthy
networks:
- microservices-network
command: ['start-dev']
discovery-server:
build: ./discovery-server
container_name: discovery-server
ports:
- '8761:8761'
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- postgres
networks:
- microservices-network
config-server:
build: ./config-server
container_name: config-server
ports:
- '8888:8888'
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- discovery-server
networks:
- microservices-network
api-gateway:
build: ./api-gateway
container_name: api-gateway
ports:
- '8080:8080'
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- discovery-server
- config-server
- redis
networks:
- microservices-network
user-service:
build: ./user-service
container_name: user-service
ports:
- '9001:9001'
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- discovery-server
- config-server
- postgres
- keycloak
networks:
- microservices-network
order-service:
build: ./order-service
container_name: order-service
ports:
- '9002:9002'
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- discovery-server
- config-server
- postgres
- keycloak
networks:
- microservices-network
volumes:
postgres_data:
redis_data:
networks:
microservices-network:
driver: bridge
3. Microservice Architecture Principles
Key principles implemented:
- Single Responsibility Principle: Each service handles one business capability
- Decentralized Data Management: Each service has its own database schema
- Infrastructure Automation: Dockerized with health checks
- Design for Failure: Circuit breakers, retries, fallbacks
- Evolutionary Design: Independent deployability
4. Service Discovery with Eureka
discovery-server/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 https://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.1.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>discovery-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>discovery-server</name>
<description>Service Discovery Server</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<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>
discovery-server/src/main/java/com/example/discoveryserver/config/SecurityConfig.java
package com.example.discoveryserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.httpBasic(withDefaults());
return http.build();
}
}
discovery-server/src/main/resources/application.yml
server:
port: 8761
eureka:
instance:
hostname: discovery-server
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 5000
spring:
application:
name: discovery-server
security:
user:
name: admin
password: admin
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
5. Configuration Management
config-server/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 https://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.1.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>config-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>config-server</name>
<description>Configuration Server</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<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>
config-server/src/main/resources/application.yml
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/your-repo/microservices-config
clone-on-start: true
search-paths: '{application}'
security:
user:
name: configUser
password: configPassword
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@discovery-server:8761/eureka/
instance:
preferIpAddress: true
management:
endpoints:
web:
exposure:
include: '*'
6. Circuit Breakers with Resilience4j
order-service/pom.xml (additional dependencies)
<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-aop</artifactId>
</dependency>
order-service/src/main/resources/application.yml
resilience4j:
circuitbreaker:
configs:
default:
registerHealthIndicator: true
slidingWindowType: COUNT_BASED
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
instances:
productService:
baseConfig: default
order-service/src/main/java/com/example/orderservice/service/OrderService.java
package com.example.orderservice.service;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class OrderService {
private static final String PRODUCT_SERVICE = "productService";
private final RestTemplate restTemplate;
public OrderService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@CircuitBreaker(name = PRODUCT_SERVICE, fallbackMethod = "getProductDetailsFallback")
public String getProductDetails(String productId) {
return restTemplate.getForObject(
"http://product-service/api/products/" + productId,
String.class
);
}
public String getProductDetailsFallback(String productId, Exception e) {
return "Product service is currently unavailable. Please try again later.";
}
}
7. API Gateway Implementation
api-gateway/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 https://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.1.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>api-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api-gateway</name>
<description>API Gateway</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
<resilience4j.version>2.1.0</resilience4j.version>
</properties>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
<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>
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: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- name: CircuitBreaker
args:
name: userService
fallbackUri: forward:/fallback/user-service
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
- StripPrefix=2
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- name: CircuitBreaker
args:
name: orderService
fallbackUri: forward:/fallback/order-service
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY, INTERNAL_SERVER_ERROR
methods: GET,POST
backoff:
firstBackoff: 10ms
maxBackoff: 1000ms
factor: 2
basedOnPreviousValue: false
- StripPrefix=2
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://keycloak:8080/realms/microservices-realm
jwk-set-uri: http://keycloak:8080/realms/microservices-realm/protocol/openid-connect/certs
redis:
host: redis
port: 6379
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@discovery-server:8761/eureka/
instance:
preferIpAddress: true
resilience4j:
circuitbreaker:
configs:
default:
registerHealthIndicator: true
slidingWindowType: COUNT_BASED
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
instances:
userService:
baseConfig: default
orderService:
baseConfig: default
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
8. Swagger/OpenAPI Documentation
api-gateway/src/main/java/com/example/apigateway/config/OpenApiConfig.java
package com.example.apigateway.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
private static final String SECURITY_SCHEME_NAME = "bearerAuth";
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
.components(
new Components()
.addSecuritySchemes(SECURITY_SCHEME_NAME,
new SecurityScheme()
.name(SECURITY_SCHEME_NAME)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
)
)
.info(new Info()
.title("Microservices API Gateway")
.version("1.0")
.description("Documentation for all microservices via API Gateway")
.contact(new Contact()
.name("API Support")
.email("support@example.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0.html")));
}
}
9. Spring Security with OAuth2 and Keycloak
user-service/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 https://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.1.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>user-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>user-service</name>
<description>User Service</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
<keycloak.version>21.1.1</keycloak.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</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.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
<version>${keycloak.version}</version>
</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.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
<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>
user-service/src/main/java/com/example/userservice/config/SecurityConfig.java
package com.example.userservice.config;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
@KeycloakConfiguration
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = new KeycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Bean
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/users/register").permitAll()
.requestMatchers("/actuator/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt());
return http.build();
}
}
10. Rate Limiting with Redis
The rate limiting is already configured in the API Gateway's application.yml:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
11. Failover and Retry Policies
api-gateway/src/main/java/com/example/apigateway/controller/FallbackController.java
package com.example.apigateway.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/fallback")
public class FallbackController {
@GetMapping("/user-service")
public Mono<ResponseEntity<Map<String, String>>> userServiceFallback() {
Map<String, String> response = new HashMap<>();
response.put("message", "User Service is taking too long to respond or is down. Please try again later");
response.put("status", HttpStatus.SERVICE_UNAVAILABLE.toString());
return Mono.just(ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response));
}
@GetMapping("/order-service")
public Mono<ResponseEntity<Map<String, String>>> orderServiceFallback() {
Map<String, String> response = new HashMap<>();
response.put("message", "Order Service is taking too long to respond or is down. Please try again later");
response.put("status", HttpStatus.SERVICE_UNAVAILABLE.toString());
return Mono.just(ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response));
}
}
12. Keycloak Integration
Keycloak Initial Setup
- Access Keycloak admin console at http://localhost:8181
- Login with admin/admin
- Create a new realm called "microservices-realm"
- Create a client for each service:
API Gateway Client
- Client ID: api-gateway
- Client Protocol: openid-connect
- Access Type: confidential
- Valid Redirect URIs: *
- Web Origins: *
- Authorization Enabled: On
User Service Client
- Client ID: user-service
- Client Protocol: openid-connect
- Access Type: confidential
- Service Accounts Enabled: On
- Authorization Enabled: On
Order Service Client
- Client ID: order-service
- Client Protocol: openid-connect
- Access Type: confidential
- Service Accounts Enabled: On
- Authorization Enabled: On
user-service/src/main/resources/application.yml
server:
port: 9001
spring:
application:
name: user-service
datasource:
url: jdbc:postgresql://postgres:5432/microservices
username: admin
password: admin
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
keycloak:
auth-server-url: http://keycloak:8080
realm: microservices-realm
resource: user-service
credentials:
secret: ${KEYCLOAK_CLIENT_SECRET}
bearer-only: true
ssl-required: external
use-resource-role-mappings: true
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@discovery-server:8761/eureka/
instance:
preferIpAddress: true
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
13. Final Deployment
Build and Run Commands
# Build all services
docker-compose build
# Start all services
docker-compose up -d
# Scale services
docker-compose up -d --scale user-service=2 --scale order-service=2
# View logs
docker-compose logs -f
# Check service health
docker-compose ps
# Stop services
docker-compose down -v
Testing the System
- Access Eureka Dashboard: http://localhost:8761 (admin/admin)
- Access API Gateway Swagger: http://localhost:8080/swagger-ui.html
- Get Keycloak token:
curl -X POST \
http://localhost:8181/realms/microservices-realm/protocol/openid-connect/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=api-gateway&client_secret=<YOUR_CLIENT_SECRET>&grant_type=password&username=<USERNAME>&password=<PASSWORD>'
- Use the access token to call protected endpoints through the API Gateway.
- Eureka Dashboard: http://localhost:8761
- Swagger UI: http://localhost:8080/swagger-ui.html
- User Service (via Gateway): http://localhost:8080/users
- Order Service (via Gateway): http://localhost:8080/orders
- Keycloak Admin: http://localhost:8181