API Gateway Server는 MSA 환경에서 모든 클라이언트 요청의 단일 진입점(Single Entry Point) 역할을 수행합니다. 각 서비스로의 라우팅, 인증(JWT 검증), 공통 필터링을 담당하며 시스템의 보안과 흐름을 제어 합니다.
- JWT 기반 인증 처리 (Keycloak 연동)
- 서비스별 요청 라우팅 (Spring Cloud Gateway)
- 유레카 기반 서비스 디스커버리 연동
- 사용자 정보 헤더 전달
- 인증되지 않은 요청 차단
| Service ID | Target URI | Path |
|---|---|---|
| user-service | lb://user-service | /api/v1/users/** |
| auth-service | lb://user-service | /api/v1/auth/** |
| hub-service | lb://hub-service | /api/v1/hubs/, /api/v1/hub-inventories/ |
| delivery-service | lb://delivery-service | /api/v1/deliveries/** |
| delivery-manager-service | lb://delivery-manager-service | /api/v1/delivery-managers/** |
| company-service | lb://company-service | /api/v1/companies/** |
| order-service | lb://order-service | /api/v1/orders/** |
| notification-service | lb://notification-service | /api/v1/slackmessages/** |
| product-service | lb://product-service | /api/v1/products/** |
⚠️ internal/**경로는 내부 서비스 간 통신 전용으로 Gateway 라우팅에서 제외합니다.
Gateway는 JWT 검증 후 사용자 정보를 추출하여 내부 서비스로 전달합니다.
Authorization: Bearer {JWT_TOKEN}| 헤더 키 | 설명 | 예시 값 |
|---|---|---|
| X-User-Id | 사용자의 UUID (Keycloak sub) | UUID 값 |
| X-User-Email | 사용자의 이메일 주소 | test@test.com |
| X-User-Role | 사용자 권한 | ADMIN or HUB_ADMIN or DELIVERY or COMPANY |
각 서비스에서는 @RequestHeader를 통해 사용자 정보를 간단하게 사용할 수 있습니다.
@GetMapping("/api/v1/users/test")
public String test(
@RequestHeader(value = "X-User-Id", required = false) String userId,
@RequestHeader(value = "X-User-Email", required = false) String email,
@RequestHeader(value = "X-User-Role", required = false) String role
) {
if("ADMIN".equals(role)) {
return "userId=" + userId + ", email=" + email + ", role=" + role;
}
return "userId=" + userId + ", email=" + email;
}1. Client 요청
↓
2. API Gateway 진입
↓
3. JWT 토큰 검증 (Keycloak)
↓
4. 사용자 정보 추출
↓
5. 내부 헤더 생성 (X-User-Id 등)
↓
6. Eureka 통해 대상 서비스 조회
↓
7. 해당 서비스로 요청 전달
↓
8. 서비스에서 비즈니스 로직 처리
↓
9. 응답 반환
1. Infra-repo
2. Eureka Server
3. Config Server
4. 각 서비스 (user, order, etc.)
5. API Gateway (마지막)
-
Redis를 사용하여 유저별/IP별 트래픽을 제어합니다.
-
로그인 유저는 userId 기반, 비로그인 유저는 Remote IP 기반으로 키를 생성하여 무분별한 API 호출을 방지합니다.
-
특정 서비스 응답이 지연되거나 장애가 발생하면 즉시 Fallback 응답을 반환합니다.
-
응답 규격 (503 Service Unavailable):
{
"status": 503,
"service": "user-service",
"message": "다잇다 서비스가 일시적으로 지연되고 있습니다. 잠시 후 다시 시도해주세요.",
"timestamp": "2026-04-03T..."
}
⚠️ Circuit Breaker (Resilience4j)를 사용하기 위해서는 각자 서비스에 등록해야합니다.
- 사용법: 외부 호출이 발생하는 Service 메서드에
@CircuitBreaker를 붙이세요. - Fallback: 호출 실패 시 사용자에게 돌려줄 기본 응답 로직을 반드시 작성하세요.
- 주의: Fallback 메서드는 원본 메서드와 반환 타입이 같아야 하며, 마지막 파라미터로
Throwable을 받아야 합니다.
dependencies {
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
// 아직 infra-repo에는 prometheus가 없습니다. 추가하시고 주석처리 해주세요.
// docker에 올라가면 공지 하겠습니다!
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
}혹시 위 예시처럼 github에서 의존성을 가져왔는데 에러가 발생한다면 아래 의존성을 추가해주세요.
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
// runtimeOnly 'io.micrometer:micrometer-registry-prometheus'각 서비스의 로직의 필요한 곳에 추가하시면 됩니다. 아래는 예시이며 각자 비즈니스 로직에 맞게 사용하시면 됩니다.
@Service
@Slf4j
public class OrderService {
// 1. Circuit Breaker 적용 (이름은 설정파일과 매칭)
// 2. fallbackMethod는 같은 클래스 내에 정의되어야 함
@CircuitBreaker(name = "orderService", fallbackMethod = "fallbackGetProductDetails")
public ProductResponse getProductDetails(Long productId) {
// 외부 서비스(Product Service) 호출 로직 (RestTemplate or FeignClient)
// 여기서 에러가 나거나 응답이 늦으면 Circuit이 열립니다.
return productClient.getProduct(productId);
}
// Fallback 메서드 (원본 메서드와 파라미터가 같아야 하며, Exception 객체가 추가됨)
public ProductResponse fallbackGetProductDetails(Long productId, Throwable t) {
log.error("Product service is down! Cause: {}", t.getMessage());
// 에러 발생 시 돌려줄 가짜(Mock) 데이터 혹은 기본값
return new ProductResponse(productId, "임시 상품 정보", 0L, "현재 정보를 불러올 수 없습니다.");
}
}project-configs/configs/common 에서 일괄적으로 관리하겠습니다.
혹시 각 서비스마다 실패율이나 설정을 조정하고 싶다면 저에게 말씀해주세요.
- 내부 서비스 간 호출은
/internal/**경로 사용 - Gateway를 거치지 않는 내부 호출은 인증 헤더 사용하지 않음
- 모든 외부 요청은 반드시 Gateway를 통해 진입