API 게이트웨이는 클라이언트와 백엔드 마이크로서비스들 사이의 중간 계층으로, 모든 API 요청이 처음으로 도달하는 진입점입니다. 쉽게 말해, 클라이언트가 여러 개의 마이크로서비스에 직접 접근하는 대신, API 게이트웨이를 통해 요청을 전달하게 됩니다.
Java와 Spring에서는 Spring Cloud Gateway가 대표적인 API 게이트웨이 솔루션입니다. Spring Cloud Gateway는 다음과 같은 특징을 가지고 있음
// build.gradle에 의존성 추가
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
}
// application.yml 설정
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/users/**
filters:
- StripPrefix=1
- id: order-service
uri: lb://ORDER-SERVICE
predicates:
- Path=/orders/**
filters:
- StripPrefix=1
위 설정은 /users/ 경로의 요청을 USER-SERVICE로, /orders/ 경로의 요청을 ORDER-SERVICE로 라우팅합니다. lb://는 로드 밸런싱을 의미하며, StripPrefix 필터는 경로의 첫 번째 부분을 제거하여 마이크로서비스에 전달합니다.
취업 준비를 위해 실습을 통해 API 게이트웨이를 이해하고 경험을 쌓는 것은 매우 효과적입니다. Java와 Spring을 사용하여 API 게이트웨이를 구현하는 실습을 단계별로 안내해 드리겠습니다. 이 실습을 통해 API 게이트웨이의 주요 기능을 직접 구현하고, 마이크로서비스 아키텍처에서의 역할을 체험할 수 있습니다.
실습을 위해 간단한 마이크로서비스 아키텍처를 설계합니다. 예를 들어, 두 개의 마이크로서비스와 하나의 API 게이트웨이를 구축합니다.
project-root/
├── api-gateway/
├── user-service/
└── order-service/
각 마이크로서비스를 Spring Boot로 구현합니다.
프로젝트 생성
엔티티 및 리포지토리 설정
// User.java
package com.example.userservice.model;
import javax.persistence.*;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String role;
// Getters and Setters
}
// UserRepository.java
package com.example.userservice.repository;
import com.example.userservice.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
컨트롤러 구현
// UserController.java
package com.example.userservice.controller;
import com.example.userservice.model.User;
import com.example.userservice.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping
public List<User> getAllUsers() {
return userRepository.findAll();
}
@PostMapping
public User createUser(@RequestBody User user) {
return userRepository.save(user);
}
}
애플리케이션 설정 (application.yml)
spring:
datasource:
url: jdbc:h2:mem:testdb
driverClassName: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: update
server:
port: 8081
User Service와 유사하게 Order Service를 구현합니다.
프로젝트 생성
엔티티 및 리포지토리 설정
// Order.java
package com.example.orderservice.model;
import javax.persistence.*;
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String product;
private Integer quantity;
// Getters and Setters
}
// OrderRepository.java
package com.example.orderservice.repository;
import com.example.orderservice.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long> {
}
컨트롤러 구현
// OrderController.java
package com.example.orderservice.controller;
import com.example.orderservice.model.Order;
import com.example.orderservice.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderRepository orderRepository;
@GetMapping
public List<Order> getAllOrders() {
return orderRepository.findAll();
}
@PostMapping
public Order createOrder(@RequestBody Order order) {
return orderRepository.save(order);
}
}
애플리케이션 설정 (application.yml)
spring:
datasource:
url: jdbc:h2:mem:testdb
driverClassName: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: update
server:
port: 8082
Spring Cloud Gateway를 사용하여 API 게이트웨이를 구현합니다.
프로젝트 생성
의존성 추가 (build.gradle)
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
// 기타 필요한 의존성
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:2023.0.1"
}
}
애플리케이션 설정 (application.yml)
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service
uri: http://localhost:8081
predicates:
- Path=/users/**
filters:
- StripPrefix=1
- id: order-service
uri: http://localhost:8082
predicates:
- Path=/orders/**
filters:
- StripPrefix=1
server:
port: 8080
보안 설정 (SecurityConfig.java)
API 게이트웨이에서 JWT 인증을 처리하기 위해 Spring Security를 설정합니다.
package com.example.apigateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/auth/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer().jwt()
.and().and().build();
}
}
JWT 인증 필터 구현 (JwtAuthenticationFilter.java)
JWT 토큰을 검증하고 사용자 정보를 설정하는 필터를 구현합니다.
package com.example.apigateway.filter;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
@Component
public class JwtAuthenticationFilter implements WebFilter {
@Value("${jwt.secret}")
private String secretKey;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
if (request.getURI().getPath().startsWith("/auth")) {
return chain.filter(exchange);
}
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
String token = authHeader.substring(7);
try {
Claims claims = Jwts.parser()
.setSigningKey(secretKey.getBytes())
.parseClaimsJws(token)
.getBody();
// 사용자 정보 설정 등 추가 처리 가능
} catch (Exception e) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
return chain.filter(exchange);
}
}
JWT 생성 컨트롤러 (AuthController.java)
테스트를 위해 간단히 JWT 토큰을 생성하는 엔드포인트를 추가합니다.
package com.example.apigateway.controller;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
@RestController
@RequestMapping("/auth")
public class AuthController {
@Value("${jwt.secret}")
private String secretKey;
@PostMapping("/token")
public String generateToken(@RequestParam String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1일 유효
.signWith(SignatureAlgorithm.HS256, secretKey.getBytes())
.compact();
}
}
JWT 비밀 키 설정 (application.yml)
jwt:
secret: mysecretkey1234567890
각 서비스 실행
8081 포트8082 포트8080 포트JWT 토큰 생성
POST http://localhost:8080/auth/token?username=testuserAPI 요청 테스트
curl -H "Authorization: Bearer <JWT_TOKEN>" http://localhost:8080/userscurl -X POST -H "Authorization: Bearer <JWT_TOKEN>" -H "Content-Type: application/json" -d '{"product":"Laptop","quantity":2}' http://localhost:8080/ordersAggregate 필터 사용 또는 커스텀 구현각 서비스를 Docker 이미지로 빌드하고, Docker Compose를 사용하여 서비스를 오케스트레이션합니다.
각 서비스에 Dockerfile 추가
# 예: api-gateway/Dockerfile
FROM openjdk:17-jdk-alpine
VOLUME /tmp
COPY target/api-gateway.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Docker Compose 파일 작성 (docker-compose.yml)
version: '3.8'
services:
api-gateway:
build: ./api-gateway
ports:
- "8080:8080"
depends_on:
- user-service
- order-service
user-service:
build: ./user-service
ports:
- "8081:8081"
order-service:
build: ./order-service
ports:
- "8082:8082"
Docker Compose 실행
docker-compose up --build
이 실습을 통해 API 게이트웨이의 개념과 구현 방법을 체험할 수 있습니다. 실제 프로젝트에서 API 게이트웨이를 적용하는 경험은 백엔드 개발자로서의 역량을 크게 향상시킬 것입니다. 실습을 진행하면서 발생하는 문제나 추가적인 질문이 있다면 언제든지 문의해 주세요. 성공적인 취업 준비를 응원합니다!