728x90
반응형
SMALL
학습내용
- 트랜잭션과 외부 API 연동
- 트랜잭션 내 외부 API 호출 처리 전략
- OpenFeign 사용
학습정리
1. 트랜잭션과 외부 API 연동
- 트랜잭션과 외부 API 호출의 관계
- 트랜잭션 내 API 호출
- 네트워크 오류나, 외부 시스템 장애로 전체 트랜잭션이 실패 할 수 있음.
- API 호출 실패 시 데이터 정합성 문제
- API 호출 실패시 이미 수행된 데이터베이스 작업이 롤백 되지 않는다면 데이터 정합성이 깨질 수 있음.
- 분산 트랜잭션 관리의 어려움
- 데이터베이스 트랜잭션과 외부 API 호출을 하나의 트랜잭션으로 관리하기는 어렵다.
보통 외부 API 호출 먼저 실행 후에 작업 진행 (트레이드오프)
- 데이터베이스 트랜잭션과 외부 API 호출을 하나의 트랜잭션으로 관리하기는 어렵다.
- 트랜잭션 내 API 호출
- 네티워크 오류 및 외부 시스템 장애 상황 고려
- 네트워크 오류
- 외부 시스템 장애
- 타임아웃 및 예외 처리
- 재시도 로직 적용
2. 트랜잭션 내 외부 API 호출 처리 전략
Retry 기벌 활용 (재시도 전략)
의존성 추가
implementation 'org.springframework.retry:spring-retry'
Main 클래스 설정 추가
@EnableRetry //추가 @SpringBootApplication public class SeungApplication { public static void main(String[] args) { SpringApplication.run(SeungApplication.class, args); } }
사용방법 (해당 서비스에 적용)
@Transactional //value: ServiceException.class 예외가 발생했을 때 //maxAttempts: 최대 3번 재시도 //backoff : 재시도 간격 딜레이는 3초 (3000ms) 기다린 후 다시 시도 @Retryable(value = ServiceException.class, maxAttempts = 3, backoff = @Backoff(delay = 3000)) public void save() { // ... 비즈니스 로직 throw new ServiceException(ServiceExceptionCode.NOT_FOUND_PRODUCT); // retry 실행 }
타임아웃 설정 (FeignClient 활용)
private static final long CONNECT_TIMEOUT = 10000; private static final long READ_TIMEOUT = 60000; private Builder feignBuilder() { return Feign.builder() .client(new OkHttpClient()) .encoder(new JacksonEncoder()) .decoder(new JacksonDecoder()) .options(new Request.Options( //연결시간 10초 CONNECT_TIMEOUT, TimeUnit.MILLISECONDS, //읽기시간 60초 READ_TIMEOUT, TimeUnit.MILLISECONDS, true )) //재시도 정책은 사용 안함, 위에 사용한 Retry 사용 .retryer(Retryer.NEVER_RETRY); }
3. OpenFeign 사용
의존성 추가
implementation 'io.github.openfeign:feign-core:13.0' implementation 'io.github.openfeign:feign-jackson:13.0' implementation 'io.github.openfeign:feign-okhttp:13.0' implementation 'io.github.openfeign.form:feign-form:3.8.0'
외부 API 응답 구조 작성
@Getter @Setter @ToString @NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public class ExternalProductResponse { Boolean result; ExternalError error; ExternalPage message; @Getter @Setter @ToString @NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public static class ExternalError { String errorCode; String errorMessage; } @Getter @Setter @ToString @NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public static class ExternalPage { List<ExternalResponse> contents; ExternalPageable pageable; } @Getter @Setter @ToString @NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public static class ExternalPageable { ...생략 } @Getter @Setter @ToString @NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public static class ExternalResponse { ...생략 } }
OpenFeign 인터페이스 작성
import feign.Headers; import feign.Param; import feign.RequestLine; //Headers : 모든 요청의 헤더에 컨텐트타입 추가 @Headers("Content-Type: application/json") public interface ExternalShopClient { //Feign의 요청 형식(RequestLine) 을 정의 @RequestLine("GET /products?page={page}&size={size}") ExternalProductResponse getProducts(@Param("page") Integer page, @Param("size") Integer size); }
Feign config 설정파일
@Configuration public class OpenFeignConfig { //application.yml에 설정한 외부 api url @Value("${external.external-shop.url}") private String externalShop; private static final long CONNECT_TIMEOUT = 10000; private static final long READ_TIMEOUT = 60000; //Feign클라이언트 Bean 생성 @Bean public ExternalShopClient externalShop() { return feignBuilder() .target(ExternalShopClient.class, externalShop); } private Builder feignBuilder() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return Feign.builder() .client(new OkHttpClient()) //OkHttpClient 사용 .encoder(new JacksonEncoder(objectMapper)) //Jackson기반 json변환 설정 .decoder(new JacksonDecoder(objectMapper)) //앞서 타임아웃 설정과 동일 .options(new Request.Options( CONNECT_TIMEOUT, TimeUnit.MILLISECONDS, READ_TIMEOUT, TimeUnit.MILLISECONDS, true )) .retryer(Retryer.NEVER_RETRY); } }
- 외부 API가 여러개 일 경우 Feign클라이언트 Bean 생성을 추가 작성
사용방법
@Slf4j @Service @RequiredArgsConstructor public class ProductExternalService { //해당 인터페이스 주입 private final ExternalShopClient externalShopClient; @Transactional //retry 사용 @Retryable(value = ServiceException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) public void save() { //사용 ExternalProductResponse responses = externalShopClient.getProducts(1, 10); ...생략 } }
추가적으로
- 해당 예시에서 사용된 OpenFeign은 github에서 가져온 것으로 SpringCloud와는 조금 다를 수 있음.
728x90
반응형
LIST
'TIL' 카테고리의 다른 글
8_1.MSA와 모놀리틱 아키텍처 비교와 MSA 장단점 및 도입 시 고려사항 (0) | 2025.02.10 |
---|---|
7_5.성능 검증을 위한 NGrinder 설정과 실습 (0) | 2025.02.10 |
7_3.롤백 전략과 커밋 관리 (0) | 2025.02.07 |
7_2.트랜잭션 전파 옵션과 DB Read/Write 분리 (0) | 2025.02.07 |
7_1.트랜잭션 개념과 선언적/프로그래밍 방식 비교 (0) | 2025.02.04 |