2025.04.01
이전에는 라우팅 기능만 살펴봤다.
이번에는 인증(Authentication), 권한 부여(Authorization), 로깅(Logging) 등 공통 기능을 어떻게 처리하는지 살펴볼 것이다.
서비스 인스턴스 간의 통신은 크게 3가지 방식으로 나눌 수 있다.
실제로 사용할 방법은 1가지로 추려질 것이다.
스프링 Discovery Client
- 서비스 디스커버리를 위한 가장 기본적인 인터페이스
- 로드 밸런서(Spring Cloud Load Balancer)와 통합되어, 서비스 레지스트리에 등록된 인스턴스를 조회
- Discovery Client를 사용하면 등록된 모든 서비스 이름과 해당 서비스의 인스턴스 정보(URL, 포트 등)를 쿼리할 수 있다
예제 코드
@Component
public class OrderDiscoveryClient {
@Autowired
private DiscoveryClient discoveryClient;
public Order getOrder(String orderId) {
RestTemplate restTemplate = new RestTemplate();
<!-- Discovery Client를 사용하여 order-service의 모든 인스턴스 정보를 가져온다. -->
List<ServiceInstance> instances =
discoveryClient.getInstances("order-service");
if (instances.size() == 0) return null;
<!-- 인스턴스의 URI를 사용하여 요청을 보낼 URL을 직접 구성한다.
스케일 아웃해서 인스턴스가 여러 개인 경우, 항상 첫 번째 인스턴스만 호출한다.
로드 밸런서 이용이 안됨 -->
String serviceUri = String.format(
"%s/v1/order/%s",
instances.get(0).getUri().toString(),
orderId
);
<!-- RestTemplate을 사용하여 해당 서비스 인스턴스의 API를 호출한다. -->
ResponseEntity<Order> restExchange =
restTemplate.exchange(serviceUri, HttpMethod.GET, null, Order.class, orderId);
return restExchange.getBody();
}
}
DiscoveryClient
스프링 프레임워크에서 제공하는 HTTP 클라이언트 라이브러리
코드가 많다는 단점
-> 잘 사용하지 않는다.
예제 코드
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
<!-- RestTemplate 빈을 등록하고, @LoadBalanced를 붙여서 로드 밸런싱 기능을 활성화 -->
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
@Component
public class OrganizationRestTemplateClient {
@Autowired
private RestTemplate restTemplate;
<!-- RestTemplate를 주입받을 때 get(0)코드가 없어진 것을 확인 할 수 있다. -->
public Order getOrder(String orderId) {
ResponseEntity<Order> restExchange =
restTemplate.exchange(
<!-- 서비스 이름은 URL에 사용 -->
"http://order-service/v1/order/{orderId}",
HttpMethod.GET,
null,
Order.class,
orderId
);
return restExchange.getBody();
}
}
로드 밸런서를 지원하는 RestTemplate 클래스 동작 방식
-> 가장 많이 사용한다.
예제 코드
<!-- 하나의 인터페이스를 만든다 -->
<!-- 디스커버리에 등록해 놓은 외부의 서비스의 이름을 작성 -->
@FeignClient(name = "order-service")
public interface OrganizationFeignClient {
<!-- 외부 서비스의 API를 get으로 가지고 온다. -->
@RequestMapping(method = RequestMethod.GET, value = "/v1/order/{orderId}")
Order getOrder(@PathVariable("orderId") String orderId);
}
getOrder() 메서드를 정의하는 방법
표준 스프링 RestTemplate 클래스를 사용할 때 모든 서비스 호출 결과에 대한 HTTP상태 코드(status code)를 ResponseEntity 클래스의 getStatus() 메서드로 반환
-> 하지만 Feign 클라이언트를 사용하면 호출된 서비스에서 반환한 모든 HTTP 4xx – 5xx 상태 코드가 FeignException에 매핑
Spring Cloud Gateway 에서 JWT 토큰을 검증하고, 해당 토큰이 유효하면 Authentication 객체를 생성하여 각 서비스로 전달하는 방식
장점
단점

모든 마이크로서비스에서 JWT 검증 로직을 직접 구현
장점
단점
인증(Authentication)과 권한 부여(Authorization)를 위한 표준 프로토콜
MSA 환경에서는 변경되지 않는(불변, immutable) 애플리케이션 이미지를 빌드하고 배포한다
Config Server는 여러 마이크로서비스의 환경 설정 파일(.env 등)을 중앙 집중화한 형태
-> 각 서비스에 민감 정보를 안전하게 제공하는 역할
Spring Colud Config Server dependency를 추가
implementation 'org.springframework.cloud:spring-cloud-config-server'
@EnableConfigServer
@SpringBootApplication
<!-- Config Server 기능을 활성화하는 애너테이션 -->
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
application.yml 파일에 설정에 읽어올 설정파일의 경로를 지정
server:
port: 8888 # Config Server 기본 포트
spring:
application:
# Config Server 이름
name: config-server
cloud:
config:
server:
git:
uri: [설정파일이 담긴 Git 레포지토리 경로]
# local repository path or remote repository path
# Git 저장소로 인식되려면 해당 경로에 .git 폴더가 존재해야 한다
# optional: 브랜치 설정
# default-label: main
bootstrap.yml 사용을 위한 dependency를 추가
implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap' <!-- Spring Boot 3.x 기준은 필요 없음, application.yml만 사용 -->
bootstrap.yml 작성
spring:
cloud:
config:
uri: [Config Server 주소]
# 해당 Config Server에서 읽어올 설정 파일 이름을 작성한다.
name: [읽어올 파일 이름] # 예: msaconfig.yml
application.yml 작성
spring:
config:
import:
- classpath:/bootstrap.yml
.yml 설정
server:
<!-- Gateway 서버 포트 설정 (모든 요청의 진입점) -->
port: 8000
spring:
application:
<!-- 애플리케이션 이름 (Eureka에 등록될 이름) -->
name: gateway-server
cloud:
gateway:
<!-- Gateway 라우팅 설정 (MSA의 각 서비스로 트래픽 전달) -->
routes:
<!-- 라우트 식별자 (사용자 서비스) -->
- id: user-service
<!-- Eureka에서 사용자 서비스 이름 (Load Balancer 사용) -->
uri: lb://USER-SERVICE
<!-- 어떤 요청을 이 서비스로 보낼지 판단 -->
predicates:
<!-- /user-service/** 경로로 오는 요청을 이 서비스로 라우팅 -->
- Path=/user-service/**
<!-- 요청/응답 가로채기 (전/후 처리) -->
filters:
<!-- 경로 재작성 (Prefix 제거) -->
- RewritePath=/user-service/(?<segment>.*), /${segment}
<!-- 라우트 식별자 (주문 서비스) -->
- id: order-service
<!-- Eureka에서 주문 서비스 이름 (Load Balancer 사용) -->
uri: lb://ORDER-SERVICE
<!-- /order-service/** 경로로 오는 요청을 주문 서비스로 라우팅 -->
predicates:
- Path=/order-service/**
filters:
<!-- 경로 재작성 -->
- RewritePath=/order-service/(?<segment>.*), /${segment}
<!-- JWT 토큰 인증 필터 적용 (커스텀 필터) -->
- AuthorizationHeaderFilter
eureka:
client:
<!-- Eureka에 자신을 등록 -->
register-with-eureka: true
<!-- Eureka에서 서비스 목록을 가져옴 -->
fetch-registry: true
service-url:
<!-- Eureka 서버 주소 -->
defaultZone: http://localhost:8761/eureka
<!-- JWT 관련 설정 (다른 서비스와 공유) -->
token:
<!-- JWT 만료 시간 (밀리초 단위) -->
expiration_time: 43200000
<!-- JWT 서명을 위한 비밀 키 (Config Server로 관리할 수도 있음) -->
secret: asdkfe;woifjcdslkcjfa;weofkl
각 서비스의 환경 설정파일(.yml, .properties)을 중앙에서 관리하는 서버
서비스는 실행 시점에 Config Server에 접근해서 자신에게 필요한 설정 파일을 가져와야 한다.
서비스가 실행될 때

Eureka server에서 서비스 인스턴스를 클릭하면 해당 애플리케이션의 기본 주소가 localhost:5000/actuator/info와 같은 형태로 열린다
이 Actuator 엔드포인트를 통해, MSA 환경에서 각 마이크로서비스의 상태를 모니터링하고 관리할 수 있다.
Spring Boot 애플리케이션의 운영 환경에서 모니터링 및 관리를 위한 기능을 제공하는 라이브러리
actuator 사용을 위한 dependency를 추가
implementation 'org.springframework.boot:spring-boot-starter-actuator'
.yml 파일 설정
### 기본 경로 수정
management:
endpoints:
web:
base-path: /management
management:
endpoints:
web:
exposure:
# 노출할 엔드포인트 목록 지정
include: health, info, beans, conditions
# 노출 제외할 엔드포인트 목록 지정
exclude: threaddump, heapdump
# 우선 순위가 exclude가 include보다 우선이다

{
"status": "UP"
}
{
"names": ["jvm.memory.used", "jvm.gc.pause", ...]
}
{
"app": {
"name": "MyApp",
"version": "1.0.0"
}
}
{
"activeProfiles": [],
"propertySources": [...]
}
{
"levels": {
"ROOT": "INFO",
"com.example": "DEBUG"
}
}
{
"threads": [
{
"name": "main",
"state": "RUNNABLE",
...
}
]
}
{
"contexts": {
"application": {
"beanDefinitionCount": 50,
...
}
}
}
{
"exchanges": [
{
"timestamp": "2024-09-26T12:00:00Z",
"method": "GET",
"uri": "/api/example",
...
}
]
}
시큐리티가 적용된 프로젝트는 PostMan 검증 시 발급 된 토큰을 반드시 Authorization 탭에 입력해야한다.
모놀로식 아키텍쳐가 Domain 별로 코드가 명확히 분리되어 있다면 Controller 단위로 분리하여 MSA화하는 것도 문제가 없다.
Domain 분리가 잘 안 되어 있을 때 Controller 단위 분리하게 되면 겉으로는 MSA 구조이지만 실제로는 하나로 뭉친 모놀로식과 다를 바 없는 구조가 된다.
모놀로식 아키텍처는 하나의 애플리케이션 안에 모든 비즈니스 로직과 데이터 처리가 이루어진다.
-> 트랜젝션 어노테이션을 사용하면 여러 도메인/DB 작업이 하나의 트랜잭션으로 묶여 데이터 일관성과 안전성을 쉽게 보장할 수 있다.
하지만 마이크로서비스 아키텍처(MSA)는 각각의 서비스가 자신만의 DB를 독립적으로 가지기 때문에, 서비스 간 호출 과정에서 트랜잭션을 묶기가 어렵다.
-> 어디서 오류가 발생했는지 추적이 어렵고, 일부 서비스는 성공하고 일부는 실패하는 분산 트랜잭션 문제가 발생할 수 있다.