3.1 Лабораторийн зорилго
Энэ лабораторид та:
- 3 микросервис үүсгэх (Student, Teacher, Gateway)
- Service Discovery (Eureka) тохируулах
- Сервис хоорондын REST дуудлага хийх
- Circuit Breaker (Resilience4j) нэмэх
- Docker Compose-оор бүгдийг ажиллуулах
Хэл: Java 17+ / Spring Boot 3.x | Framework: Spring Cloud | Хэрэгсэл: Docker, Docker Compose
3.2 Лаб 1: Eureka Service Registry
Алхам 1: Eureka Server үүсгэх
pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
Application class:
@SpringBootApplication
@EnableEurekaServer
public class RegistryApplication {
public static void main(String[] args) {
SpringApplication.run(RegistryApplication.class, args);
}
}
application.yml:
server:
port: 8761
eureka:
client:
register-with-eureka: false # Өөрийгөө бүртгэхгүй
fetch-registry: false
server:
enable-self-preservation: false
Алхам 2: Student Service — Eureka Client
pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml:
server:
port: 8081
spring:
application:
name: student-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
Алхам 3: Teacher Service — Eureka Client
server:
port: 8082
spring:
application:
name: teacher-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
Шалгах:
# Eureka Dashboard
# http://localhost:8761 → student-service, teacher-service харагдана
3.3 Лаб 2: Сервис хоорондын REST дуудлага
Student Service → Teacher Service дуудах
// Student Service — RestClient тохиргоо
@Configuration
public class RestClientConfig {
@Bean
@LoadBalanced // Eureka-аар IP хаяг олно
public RestClient.Builder restClientBuilder() {
return RestClient.builder();
}
}
// Student Service — Teacher Client
@Service
public class TeacherClient {
private final RestClient restClient;
public TeacherClient(RestClient.Builder builder) {
this.restClient = builder
.baseUrl("http://teacher-service") // Eureka нэр (IP биш!)
.build();
}
public TeacherResponse getTeacher(Long id) {
return restClient.get()
.uri("/api/teachers/{id}", id)
.retrieve()
.body(TeacherResponse.class);
}
}
// Student Service — Controller
@RestController
@RequestMapping("/api/students")
public class StudentController {
private final StudentService studentService;
private final TeacherClient teacherClient;
@GetMapping("/{id}/with-teacher")
public StudentWithTeacherResponse getStudentWithTeacher(@PathVariable Long id) {
StudentResponse student = studentService.getById(id);
TeacherResponse teacher = teacherClient.getTeacher(student.teacherId());
return new StudentWithTeacherResponse(student, teacher);
}
}
public record StudentWithTeacherResponse(
StudentResponse student,
TeacherResponse teacher
) {}
Тест хийх:
# Student + Teacher мэдээлэл хамт авах
curl http://localhost:8081/api/students/1/with-teacher
# Хариу:
# {
# "student": {"id":1,"name":"Бат","teacherId":5},
# "teacher": {"id":5,"name":"Дорж","department":"CS"}
# }
3.4 Лаб 3: Circuit Breaker (Resilience4j)
Алхам 1: Dependency нэмэх
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
Алхам 2: Circuit Breaker нэмэх
@Service
public class TeacherClient {
private final RestClient restClient;
@CircuitBreaker(name = "teacherService", fallbackMethod = "getTeacherFallback")
@Retry(name = "teacherService")
public TeacherResponse getTeacher(Long id) {
return restClient.get()
.uri("/api/teachers/{id}", id)
.retrieve()
.body(TeacherResponse.class);
}
// Teacher Service унтарсан бол → Fallback хариу
private TeacherResponse getTeacherFallback(Long id, Throwable ex) {
return new TeacherResponse(id, "Багшийн мэдээлэл түр авах боломжгүй", "N/A");
}
}
Алхам 3: Тохиргоо
# application.yml
resilience4j:
circuitbreaker:
instances:
teacherService:
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
permitted-number-of-calls-in-half-open-state: 3
retry:
instances:
teacherService:
max-attempts: 3
wait-duration: 1s
Тест хийх:
# Teacher Service ажиллаж байхад
curl http://localhost:8081/api/students/1/with-teacher
# → {"student":{...},"teacher":{"id":5,"name":"Дорж"}}
# Teacher Service ЗОГСООХ (docker stop teacher-service)
curl http://localhost:8081/api/students/1/with-teacher
# → {"student":{...},"teacher":{"id":5,"name":"Багшийн мэдээлэл түр авах боломжгүй"}}
# Circuit Breaker → Fallback ажиллалаа!
3.5 Лаб 4: API Gateway (Spring Cloud Gateway)
Алхам 1: Gateway үүсгэх
pom.xml:
<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>
Алхам 2: Gateway тохиргоо
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: student-service
uri: lb://student-service
predicates:
- Path=/api/students/**
- id: teacher-service
uri: lb://teacher-service
predicates:
- Path=/api/teachers/**
default-filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
Тест хийх:
# Gateway-р дамжуулж дуудах (8080 port)
curl http://localhost:8080/api/students
# → Student Service-ийн хариу
curl http://localhost:8080/api/teachers
# → Teacher Service-ийн хариу
# Client зөвхөн 8080 порт мэдэхэд хангалттай!
3.6 Лаб 5: Docker Compose — Бүгдийг нэгтгэх
docker-compose.yml
version: '3.8'
services:
# Eureka Registry
registry:
build: ./registry-service
ports:
- "8761:8761"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8761/actuator/health"]
interval: 10s
retries: 5
# Student Service
student-service:
build: ./student-service
ports:
- "8081:8081"
environment:
- EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://registry:8761/eureka/
- SPRING_DATASOURCE_URL=jdbc:postgresql://student-db:5432/studentdb
- SPRING_DATASOURCE_USERNAME=${DB_USERNAME}
- SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD}
depends_on:
registry:
condition: service_healthy
student-db:
condition: service_healthy
# Teacher Service
teacher-service:
build: ./teacher-service
ports:
- "8082:8082"
environment:
- EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://registry:8761/eureka/
- SPRING_DATASOURCE_URL=jdbc:postgresql://teacher-db:5432/teacherdb
- SPRING_DATASOURCE_USERNAME=${DB_USERNAME}
- SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD}
depends_on:
registry:
condition: service_healthy
teacher-db:
condition: service_healthy
# API Gateway
api-gateway:
build: ./api-gateway
ports:
- "8080:8080"
environment:
- EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://registry:8761/eureka/
depends_on:
registry:
condition: service_healthy
student-service:
condition: service_started
teacher-service:
condition: service_started
# Student DB
student-db:
image: postgres:15-alpine
environment:
POSTGRES_DB: studentdb
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- student-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME}"]
interval: 5s
retries: 5
# Teacher DB
teacher-db:
image: postgres:15-alpine
environment:
POSTGRES_DB: teacherdb
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- teacher-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME}"]
interval: 5s
retries: 5
volumes:
student-data:
teacher-data:
Ажиллуулах:
# .env файл
echo "DB_USERNAME=admin" > .env
echo "DB_PASSWORD=secret123" >> .env
# Бүгдийг эхлүүлэх
docker-compose up -d --build
# Шалгах
curl http://localhost:8080/api/students # Gateway → Student Service
curl http://localhost:8080/api/teachers # Gateway → Teacher Service
curl http://localhost:8761 # Eureka Dashboard
# Зогсоох
docker-compose down -v